1、Spring的工作原理及使用场景
什么是AOP
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AOP使用场景
AOP用来封装横切关注点,具体可以在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
2、数据库索引类型,优缺点?
索引的几种类型分别是普通索引、唯一索引、聚集索引、主键索引、全文索引几种。
使用索引的优点:
提高数据的搜索速度
加快表与表之间的连接速度
在信息检索过程中,若使用分组及排序子句进行时,通过建立索引能有效的减少检索过程中所需的分组及排序时间,提高检索效率。
使用索引的缺点:
在我们建立数据库的时候,需要花费的时间去建立和维护索引,而且随着数据量的增加,需要维护它的时间也会增加。
在创建索引的时候会占用存储空间。
在我们需要修改表中的数据时,索引还需要进行动态的维护,所以对数据库的维护带来了一定的麻烦。
唯一索引:在创建唯一索引时要不能给具有相同的索引值。
主键索引:在我们给一个字段设置主键的时候,它就会自动创建主键索引,用来确保每一个值都是唯一的。
聚集索引:我们在表中添加数据的顺序,与我们创建的索引键值相同,而且一个表中只能有一个聚集索引。
普通索引:它的结构主要以B+树和哈希索引为主,主要是对数据表中的数据进行精确查找。
全文索引:它的作用是搜索数据表中的字段是不是包含我们搜索的关键字,就像搜索引擎中的模糊查询
3、Spring AOP 事物和synchronized 作用在同一个方法上,同步失效问题
如果使用了AOP 事物注入,并且service类单例模式,方法钱加上synchronized 标识,大家会认为这个方法是线程安全的。因为类是单例模式,只有这一个类实例。但是,如果在高度并发的时候,这个同步标志是失效的,原因就是事物在执行的时候,是由spring 生产的代理类执行,但在执行这个事物的时候虽然还没有退出调用方法,但已经不是锁的同一个类对象,如果此时有高并发,请求依然会知道到这个同步的方法内部,造成的结果就是数据库幻读,由于数据还没有提交,读取到的数据不正确造成后续程序都无法正确执行。
解决方法有几种:
加大同步锁返位,可以锁住这个事物方法的外层,在事物方法的外层方法加锁避免并发,由于这个方法没有事物,也就不会有AOP 置入问题。
还有一个就是数据库表的关键列设置为主键,并发写库时候,最后由数据库解决重复数据问题。
4、Spring解决单例bean线程不安全问题
(1)线程安全问题一般出现在成员变量里面,这是为什么?
因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题
因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题。下面我们就来研究下如何解决Spring中单例Bean的线程安全问题
(2)如何判断线程是否安全(再次调用该接口。或者多次调用该接口,线程不安全便会出现以下控制台所示的结果)
@RestController
//@Scope("prototype")
public class BeanController {
private int content=0; //基本类型 线程不安全
private String test=null;//引用类型 线程不安全
@RequestMapping("testBean")
public Object getSercurity(){
System.out.println(content);
System.out.println(test);
content=20;
test="单例模式是不安全的";
return test;
}
结果:
0
null
20
null
20
单例模式是不安全的
(3)线程安全的处理方法
3.1、在对应的类名上加上该注解@Scope("prototype"),表示每次调用该接口都会生成一个新的Bean。
@RestController
@Scope("prototype")
public class BeanController {
private int content=0; //基本类型 线程不安全
private String test=null;//引用类型 线程不安全
@RequestMapping("testBean")
public Object getSercurity(){
System.out.println(content);
System.out.println(test);
content=20;
test="单例模式是不安全的";
return test;
}
结果:
null
0
null
0
null
3.2、利用ThreadLocal
3.3、尽量不要使用成员变量
3.4、前提:该程序是web应用,可以使用Spring Bean的作用域中的request,就是说在类前面加上@Scope("request"),表明每次请求都会生成一个新的Bean对象。作用于@Scope("prototype")类似。
5、Spring 为啥默认把bean设计成单例的?
Spring开发的朋友都知道Spring提供了5种scope分别是singleton、prototype、request、session、global session。
bean申明为单例模式的好处:
bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
(1)图形解析:
(2)源码解析:
如何判断bean是单例还是原型:
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// Explicitly remove instance from singleton cache:
// It might have been put there
// eagerly by the creation process, to allow for
// circular reference resolution.
// Also remove any beans that received a temporary
// reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName,
mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name,
beanName, mbd);
}
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException(
"No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName,
new ObjectFactory() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name,
beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(
beanName,
"Scope '"
+ scopeName
+ "' is not active for the current thread; "
+ "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
如果是单例的则先尝试从缓存里获取,没有在新创建
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 检查单例缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
// 只有正在创建的bean才进一步的处理
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从早期缓存获取bean实例
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 早期缓存中不存在bean实例,就从singletonFactories获取bean对应的ObjectFactory
// 每个bean在实例化的时候,都会包装一层ObjectFactory放到singletonFactories中,然后使用ObjectFactory的getObject()方法创建bean实例
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用ObjectFactory的getObject()方法创建bean实例
singletonObject = singletonFactory.getObject();
// 放到早期缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// singletonFactories移除该bean对应的ObjectFactory,因为已经使用过了
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
(3)总结:
单例bean的好处:
- 减少了新生成实例的消耗 新生成实例消耗包括两方面,首先,Spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法
- 减少jvm垃圾回收 由于不会给每个请求都新生成bean实例,所以自然回收的对象少了
- 可以快速获取到bean 因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快
单例bean的劣势:
单例的bean一个很大的劣势就是他不能做到线程安全!!!,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
所以Spring将bean设计成单例的用意:提高性能 (少创建实例、垃圾回收、缓存快速获取)