⭐AOP
AOP就是按照一定的规则去匹配方法,然后在方法的执行前后添加一些额外的通用逻辑。
常用的场景有:日志、接口鉴权、异常处理、Spring中的事务也是通过AOP实现的。
- 切面(Aspect):可以用
@Aspect
注解或者<aop:aspect>
标签定义一个切面。 - 切点(Pointcut):可以用来定义方法的匹配规则,当符合规则的方法被调用时,会触发对应的通知。
- 通知(Advice):
- 环绕通知(around):目标方法执行前和执行后都会触发。用
@Around
声明。 - 前置通知(before):目标方法执行前触发。用
@Before
声明。 - 后置通知(after):目标方法执行后触发。用
@After
声明。 - 异常通知(exception):目标方法执行抛异常时触发。用
@AfterThrowing
声明。 - 返回通知(return):目标方法返回时触发。用
@AfterReturning
声明。
- 环绕通知(around):目标方法执行前和执行后都会触发。用
1 | // 声明切面 |
⭐IOC
如果没有IOC,假设A依赖B,就需要自己手动创建B的实例,如果后面B又需要依赖C,这个时候就需要调整A的代码,让A再创建C的实例并注入给B,这样对象之间的耦合度就太高了。
1 | public class A { |
如果之后B又需要依赖C,就需要调整A的代码
1 | public class A { |
⭐Spring事务
⭐事务的隔离级别
TransactionDefinition接口中定义了五个表示隔离级别的常量。
- TransactionDefinition.ISOLATION_DEFAULT:数据库默认的隔离级别(Mysql是RR,Oracle是RC)。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:读未提交,可能会出现脏读。
- TransactionDefinition.ISOLATION_READ_COMMITTED:读提交,可以解决脏读,会出现不可重复读。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:可重复读,可以解决不可重复读,会出现幻读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:串行化。
⭐事务的传播方式
TransactionDefinition.PROPAGATION_REQUIRED:(默认的隔离级别)如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
适用于对于一些不会单独执行,一旦被调用就必须包含事务的方法。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起,等新事物执行完之后,再恢复之前的事务继续执行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起,方法执行完之后,再恢复之前的事务继续执行。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新事务。
嵌套事务是指:子事务是父事务的一部分,父事务执行到子事务时,会创建一个回滚点,如果子事务回滚,父事务只会回滚到回滚点,也就是说,父事务之前的操作不会受到影响。
如果父事务回滚,子事务也会一起回滚,因为子事务需要和父事务统一提交。
⭐事务失效的情况
@Transactional注解只能在public修饰的方法上才起作用。
因为Spring是通过动态代理的方式实现AOP,private修饰的方法无法被代理到。
如果数据库不支持事务,事务也会不起作用。
比如MySQL的MyISAM引擎。
默认情况下,如果抛出了非RuntimeException异常,事务不会回滚。
可以在@Transactional注解中指定rollbackFor为特定的异常类。@Transactional(rollbackFor = Exception.class)。
如果用try-catch捕获了异常,但是却没有手动回滚事物,事务不会回滚。
在Spring的TransactionAspectSupport类中有个
invokeWithTransaction()
方法,这里面就是处理事物的逻辑,只有在捕获到异常之后才会进行后续的事物处理。可以在catch代码块中使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();手动提交事务。
在同一个类中有a()和b()两个方法,在b()方法上加@Transactional注解,然后在a()方法中调用b()方法,这种情况b()方法中得事务不会生效。
因为必须通过代理过的类从外部调用目标方法才能生效。可以在代码中注入一个this,然后通过this在调用b()方法。
⭐循环依赖
多个bean之间相互引用,比如A依赖B,B也依赖A。
出现循环引用的bean必须是单例的,多例模式(propotype)的bean是不支持循环依赖的,会抛异常。因为propotype多例模式每次都会创建一个新的bean,当创建A时,发现引用了B就会创建B,发现B引用了A,这时又会去创建A,这就套娃了。
Spring内部维护了三个Map,就是通常说的三级缓存。
1 | private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>; |
- singletonObjects:一级缓存,缓存的是创建完成的Bean。
- earlySingletonObjects:二级缓存,缓存的是提前暴露出来的Bean,在这个Map里的Bean还未完成初始化,属于半成品。
- singletonFactories:三级缓存,映射了创建Bean的原始工厂。
假设A和B互相依赖:
首先A会被创建出来,并将自己暴露到三级缓存中,然后进行初始化。
1
2
3
4
5
6
7
8
9
10protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory); // 将bean添加到三级缓存中
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}在初始化的过程中发现A依赖B,此时就会尝试从一级缓存中去获取B,然后发现B还没有被创建,这个时候就会触发B的创建。
B在初始化的时候发现依赖A,就会尝试从一级缓存中获取A。这个时候A还没有初始化完成,所以一级缓存中肯定没有,然后尝试从二级缓存中获取,此时也没有,最后尝试从三级缓存中获取A的工厂类,然后调用其
getObject()
就可以获取到A。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName); // 1.从一级缓存中获取bean
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName); // 2.一级缓存如果没有,就从二级缓存中获取
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 3.如果二级缓存中也没有,就从三级缓存中获取其工厂类
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 4.调用其工厂类的getObject()方法获取bean
this.earlySingletonObjects.put(beanName, singletonObject); // 5.将其加入到二级缓存
this.singletonFactories.remove(beanName); // 6.从三级缓存中删除
}
}
}
}
return singletonObject;
}B拿到A对象的引用之后可以顺利的完成自己的初始化,并将自己加入到一级缓存中。
然后A得到B对象的引用之后也可以完成初始化,进入到一级缓存中。
⭐Bean生命周期
- 实例化Bean对象,设置Bean属性。
- 如果实现了各种Aware接口,则会对Bean注入一些其它信息。比如BeanNameAware会注入Bean ID、BeanFactoryAware会注入Bean Factory、ApplicationContextAware会注入ApplicationContext。
- 如果实现了BeanPostProcessor接口,则会调用
postProcessBeforeInitialization()
和postProcessAfterInitialization()
方法。 - 调用Bean自身定义的init方法。
- 最后依次调用DisposableBean的
destroy()
方法和Bean自身定制的destroy()
方法。