目录
AOP的用法
注解用法
早期的基于接口的用法
递归实现的责任链模式
简单实现
责任链模式就两个关键点
传统的aop实现方式的局限性
Advisor
解决不能精确到方法级别的增强
注解形式的Advisor
解决需要创建多个FactoryBean
纯注解的AOP实现原理
AOP源码解析
AOP入口
@EnableAspectJProxy
AOP中的三大BeanPostProcessor
Spring Aop的三种实现方式
解析切面类
AnnotationAwareAspectJAutoProxxyCreator
Spring代码的对老代码的兼容性
cglib动态代理和jdk动态代理
创建动态代理
调用代理方法
AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。
AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例
Advisor是源码中的概念,应用本身不需要关心。织入,就是通过动态代理来实现将增强逻辑织入到目标对象的目标方法上去的
AOP的用法
注解用法
SpringAop只用了AspectJ的注解,还有切点定义切点解析之类的东西
Spring AOP只能用户ioc容器中的bean实例的方法级别进行增强,如果不是ioc容器中的bean,那么spring AOP就无能为力了
早期的基于接口的用法
基于接口的各种类型的通知Advice
早期没有引入AspectJ的切点、切面这一套时,是没有切面的
这里是通过方法拦截器的方式,来给目标方法做一些增强;
方法拦截器的这种形式,可以理解成为环绕通知;
实现AOP,可以使用Advice通知,也可以使用方法拦截器,因为它们有共同的父类Advice;方法拦截器,也是Advice接口的子实现
这里就是给目标方法,指定了两个通知,tulingLogAdvice和tulingLogInterceptor(tulingLogAdvice和tulingLogInterceptor也就是通过@Bean注入到ioc容器中的两个bean),传入了两个通知,这两个通知就形成了一个通知链
此时,用法非常的死板,只能一次针对一个Bean来创建动态代理对象,如果我有十个bean需要生成动态代理,那么这里就要显示的定义十个不同的ProxyFactoryBean
执行结果:
通过这种方式创建一个动态代理对象,并使用
ProxyFactoryBean#getObject()
就是通过内部生成一个不同通知的责任链,来依次调用不同的通知
可以看到,这里252行在创建动态代理对象之前,首先就是初始化出来一个通知链
递归实现的责任链模式
简单实现
第23行,因为TulingLogAdvice没有实现MethodInterceptor接口,所以我们又自定义了一个委托MethodBeforeAdviceInterceptor
可以看到,这里是适配器和责任链的组合,不同的拦截器形成的责任链。这里,使用的是递归形式的责任链
这里就采用的是,“递归 + 列表索引”的方式,来实现的责任链模式这里的MyMethodInvocation就相当于,标准责任链模式中的FilterChain,FilterChain中也有一个List<Filter>,这里MyMethodInvocation是有一个List<MethodInteceptor
责任链模式就两个关键点
- 统一的节点抽象,继承统一的父类或者实现统一的接口,从而方便统一的调用
- 通过递归,循环,或者next指针的方式来进行调用
可以看到这里56行,是把整个目标对象都传递了进入,而不是传递的目标对象的具体目标方法,这样就会导致写了一套增强逻辑,这套增强逻辑就会对目标对象的每个方法都生效
传统的aop实现方式的局限性
- 问题一,不能精确到方法级别的aop增强,而是为类中的每个方法都进行了增强
- 问题二,需要创建很多FactoryBean,针对每个目标对象都要创建一个FactoryBean
Advisor
解决不能精确到方法级别的增强
因为问题一,不能精确到方法级别的aop增强,引入了Advisor的概念
这里一个我们通过接口自定义的Advice,就被封装成了一个的Advisor,后面每个定义的不同的Advice都会被封装成了一个个的Advisor
后面通过注解定义的aop,这里每个@Before、@After,也都会被封装成一个个的Advisor
按照正则表达式匹配,或者按照方法名进行匹配,
执行结果:
这里,就精确到了目标对象的div方法
所以,有了Advisor后就能让aop增强精确到方法级别,Advisor的作用:
- 包含前置通知、后置通知等增强逻辑
- 指定要增强的方法名
注意,Advisor并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上
注解形式的Advisor
后面通过注解定义的aop,这里每个@Before、@After都会被封装成一个个的Advisor
解决需要创建多个FactoryBean
问题二:需要创建很多FactoryBean,从而引入BeanPostProcessor
因为每创建一个目标对象的动态代理,就要重新创建一个ProxyFactoryBean。通过ProxyFactoryBean这种手动的方式来创建动态代理对象
通过BeanNameAutoProxyCreator这个BeanPostProcessor这种方式,动态扫描ioc中的所有bean,只要这个bean的beanName是以“tuling”开头的,那么就给这个bean来创建动态代理对象,动态代理的逻辑就是tulingLogAspectAdvisor中的通知逻辑(注意,这里使用了beanName通配符),指定的tuling*实际上也就是指定目标对象(Advisor本身并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上)
纯注解的AOP实现原理
纯注解的方式的aop的实现原理,每个通知,都对应创建一个Advisor,每个Advisor中都有自己专属的有Advice和Pointcut
因为当前,通过BeanPostProcessor来生成动态代理对象时,是以传入的每个Adisor为基本增强单位的
首先,扫描ioc中的所有bean,看哪些bean上被标注了@Aspect注解,如果被标注了,则把里面的@Before、@After等全部变成一个个的Advisor
其次,在ioc的getBean创建bean时,就会通过给BeanNameAutoProxyCreator这个BeanPostProcessor来创建目标对象的动态代理对象,动态代理的给原实例增强的逻辑,就是传进BeanNameAutoProxyCreator中的一个个Adisor。而这些Adisor也就是上面扫描出来的一个个Adisor
ioc加载doCreateBean()时创建当前bean时,会先拿到提前解析好的所有的Advisor,然后循环所有的Advisor,一一和当前bean的配对,如果配对上,那么就需要给当前bean做动态代理增强,增强的逻辑就是匹配上的Advisor中Advice的逻辑
Advisor的PointCut有三种不同的类型
- 按方法名的
- 按正则表达式的
- 按AspectJ表达式的
这里的PointCut的匹配策略有很多种,也就是策略模式,
AOP源码解析
Aop核心三个步骤
- 解析切面
- 创建动态代理
- 调用代理方法
AOP入口
@EnableAspectJProxy
以后,只要是看spring ioc集成什么自身组件,或者第三方组件,一般都会加上一个@EnableXxxxx的注解,我们要找这个组件入口,就从这个注解开始,这也就是spring的一个灵活性
@EnableXxxxx注解中又有一个@Import注解
AnnotationAwareAspectJAutoProxxyCreator这个BeanPostProcessor,在这里被注册到ioc容器的beanDefinitionMap中去,然后ApplicationContext#refresh()中有一步registerBeanPostProcessor()就会实例化这个BeanPostProcessor。
后续这些实例化好的BeanPostProcessor,就会在9大BeanPostProcessor执行的时机被分别的调起
AOP中的三大BeanPostProcessor
看这个实现类,实现的三个接口,就是实现aop的三个关键接口,实现这三个接口的作用分别是:
- 生成动态代理对象
- 解析切面
- 解决循环依赖中aop动态代理对象生成问题
AspectJ 本身是不支持运行期织入的,日常使用时候,我们经常回听说,spring 使用了aspectJ实现了aop,听起来好像spring的aop完全是依赖于aspectJ
其实spring对于aop的实现是通过动态代理(jdk的动态代理或者cglib的动态代理),它只是使用了aspectJ的Annotation,并没有使用它的编译期和织入器
aspectJ是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。
3)spring aop的代理必须依赖于bean被spring管理,所以如果项目没有使用spring,又想使用aop,那就只能使用aspectJ了(不过现在没有用spring的项目应该挺少的吧。。。)
4)aspectJ由于是编译期进行的织入,性能会比spring好一点
Spring Aop的三种实现方式
- 基于接口的方式
- 基于注解的方式
- Xml配置的方式
解析切面类
AnnotationAwareAspectJAutoProxxyCreator
这个bean的后置处理器,实现了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation()方法,也就是bean后置处理器的9次调用中的第一次,就是在这里完成Advisor的解析填充工作
后续BeanPostProcessor的postProcessAfterInitialization()方法中,就会循环遍历这些填充好的Advisor,一个个匹配,然后创建bean的动态代理
可以看到这里,会执行9次Bean后置处理器的第一次调用,也就是在第一次调用这里会进行解析切面
解析切面是很耗费性能的,所以需要保证只解析一次,所以就创建了缓存,解析好后就缓存起来,下次就不解析了
如果是这几个类就不需要解析了,因为我们只解析标注了@Aspect的类,要做的工作只是把 @Aspect的类中的一个个通知,解析成一个个的Advisor存入list中
Spring代码的对老代码的兼容性
这段代码就是为了兼容老的代码(向下兼容),因为当前的注解实现的aop的方式,是不会往ioc容器中手动显示的注入Advisor接口的实现类对象的,所以这里就会找不到。如果不是为了兼容老代码,那么这段84行开始的代码,就可以删除了
先去容器中找有没有实现了Advisor接口的类,因为我们当前都已经使用全注解的方式了,所以自然就在ioc容器中找不到实现了Advisor接口的类了
上面传统的aop的实现方式时,是自己手动往ioc容器中注入一个Advisor类型的bean,如果这个时候,ioc容器中就能找到实现了Advisor接口的类了
这里,就开始判断ioc中的bean,哪些bean有@Aspect注解,若果是切面类则把里面的所有通知都解析成一个个Advisor
判断当前类,是不是切面类
使用缓存,每个切面类的类名作为key,切面类下的List<Advisor>作为value
切面类
会扫描所有标注了@Aspect注解的Bean,解析切面类中的每一个方法,只要这个bean的哪个方法上标注了@Before,@After等注解,就生成一个对应的Advisor
cglib动态代理和jdk动态代理
cglib生成的动态代理对象,调用自身的方法,也是要经过动态代理的逻辑
jdk动态代理对象的方法内部,调用自己的另一个方法,则不会走动态代理增强的逻辑
jdk和cglib代理现在都是修改的字节码,所以现在两者性能方面都是差不多的
创建动态代理
doCreateBean()内每个bean都会经历实例化、属性注入、初始化,在初始化后就会调用一堆的后置处理器的postProcessAfterInitialization()方法,当调用到AbstractAutoProxyCreator这个后置处理器的postProcessAfterInitialization()方法时,这个方法内部就会拿到前面解析并缓存好的所有的切面类的所有Advisors,然后循环遍历Advisors是否能匹配上当前bean,如果能匹配上就开始创建当前bean的动态代理对象,创建好的动态代理对象,就会交给ioc容器
调用代理方法
aop的运行原理就是,先把切面所有通知变成统一的Advisor,然后通过AspectJ的表达式匹配算法,判断出当前bean与哪些Advisor匹配,将这些匹配的Advisor们保存起来,在创建jdk动态代理对象时,将这些匹配的匹配的Advisor们,传入ReflctiveMethodInvocation中,然后就是通过递归调用,依次调用各个Advice,最后调到目标方法,然后再一步步的返回
不管是不是有异常抛出,后置通知都是会执行的,因为后置通知是写在finally 中的
疑问:
上面的流程,好像还是只到了类级别的控制,没有到方法级别的控制。
jdk动态代理内一个方法直接调用另一个方法,是不会触发另一个方法的动态代理逻辑的,我们只有通过这种方法,先拿到动态代理对象,然后在调用动态代理的方法