AOP
springAop大体分为两种技术方式,一种是基于动态代理的,一种是基于字节码增强的
- 动态代理的有基于
- jdk的
- 基于CGLIB的
- 字节码增强的有
- 在编译时做增强的
- class加载的时候做增强的
1 | public enum AdviceMode { |
基于动态代理-PROXY
AbstractAutoProxyCreator
它是个抽象类,并且是bean的后置处理器,在bean创建的时候拦截,并寻找合适的切入点返回对应的proxy
其中有3个实现类
- InfrastructureAdvisorAutoProxyCreator
如果只是开启事务则会用到此实现
AOP生效规则:bean必须是Advisor
类型,且role为BeanDefinition.ROLE_INFRASTRUCTURE
才会生效 - AspectJAwareAdvisorAutoProxyCreator
基于xml配置方式<aop:config>...</aop:config>
AOP生效规则:只要是Advisor
类型的bean就会生效,不关心role - AnnotationAwareAspectJAutoProxyCreator
继承上面那个类
AOP生效规则:同上且支持注解表达式@Aspect
优先级从上到下,只要注册了优先级比较高的class,低优先级的会自动失效
不论是事务@EnableTransactionManagement
,还是缓存@EnableCaching
,或者自定义的AOP@EnableAspectJAutoProxy
,只会往beanFactory注册一个AbstractAutoProxyCreator
类型的bean
beanName为org.springframework.aop.config.internalAutoProxyCreator
1 | public abstract class AopConfigUtils { |
- 注册完之后会在bean实例化的拦截,寻找能够切入的点,返回对应的proxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
public Object postProcessAfterInitialization( Object bean, String beanName){
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 如果没有提供过早期的引用(循环引用),则可以进行proxy(早期的引用已经proxy了)
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 过滤掉不必代理的类,
// 如果是这些接口的实现Advice、Pointcut、Advisor、AopInfrastructureBean,则不会代理
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 根据当前的class寻找对应的AOP
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource){
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 查找可以匹配的切入器
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 创建代理,动态判断是用cglib还是jdk
return proxyFactory.getProxy(getProxyClassLoader());
}
...
}
基于字节码增强
不同于proxy,字节码增强运行效率是比较快的,因为相当于我们手写的代码,且没有这么复杂的动态代理逻辑。更不会出现内嵌方法调用时aop不生效的情况
但这种技术为什么没有流行起来呢?原因有两个
1是需要通过运维支持在jvm 启动的时候添加 -javaagent:'jarPath' 命令,比较复杂(也可以通过技术手段在运行中获取到Instrumentation
实例,不用运维配置)
2是配置起来复杂,需要在代码层面提前配置好切入的表达式和对应的逻辑,且要保证切入的class没有提前加载才行
基于agent在class加载的时候进行动态的替换字节码
字节码增强技术-ASPECTJ基于编译阶段做切入操作
具体可google搜索class编译时做字节码切入
事务的原理
springBoot: @EnableTransactionManagement
spring.xml: <tx:annotation-driven/>
以上配置表明@Transactional
注解即可生效,那么他的原理是什么呢?
基于PROXY
注册一个aop实例,类型为Advisor
,判断有@Transaction注解的就会自动动态代理
在方法调用的时候,aop拦截方法并执行
TransactionAspectSupport#invokeWithinTransaction
,根据@Transaction
的配置进行开始事务、隔离配置、回滚等操作
xml注册的代码
AnnotationDrivenBeanDefinitionParser#parse
注解注册的代码
TransactionManagementConfigurationSelector#selectImports
基于字节码增强
字节码增强,spring兼容的不太好。因为字节码需要agent,并且需要class字节器转换器。
基于字节码的切入,即使不用配置事务的注解@EnableTransactionManagement
,也会生效。
只要保证要切入的类在加载之前agent能正常运行且有class转换器org.springframework.transaction.aspectj.AnnotationTransactionAspect
即可
设置事务对所有的异常进行回滚
默认为RuntimeException和Error的类型才会事务回滚
1 |
|
通过分析事务的核心代码TransactionAspectSupport#invokeWithinTransaction
(开启、回退、隔离级别等),我们发现注解的配置由TransactionAttributeSource
提供
我们只需要重写TransactionAttributeSource
,并应用即可
但是要注意这种方式只支持Proxy方式的事务,如果是基于字节码那么修改源码了(字节码的切入不受spring管控)
代码
1 |
|
原理,在spring注册完所有的beanDefinition之后,获取"transactionAttributeSource"的definition,并setInstanceSupplier,这样在实例化的时候就会用自己的了