Spring AOP注解的学习与实践
AOP
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法
Spring-aspects
指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式
(1) AOP切面编程主要步骤:
a、将业务逻辑类和切面类都加入到容器中,告诉Spring哪个是切面类(@Aspect)
b、在切面类的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
c、开启基于注解的AOP模式@(@EnableAspectJAutoProxy)
(2) 详细步骤:
1、导入aop模块
2、定义业务逻辑类(业务逻辑运行日志进行记录,方法之前,异常,结束都希望有输出信息)
3、定义一个日志切面类(切面类里面的代码需要动态切入到运行方法中)
/*
* 通知方法:
* 前置通知(@Before):在目标方法执行之前运行
* 后置通知(@After):在目标方法执行之后运行(无论方法正常还是异常结束都执行)
* 环绕通知(@Arround):动态代理,手动推进目标方法运行(jionPoint.procced())
* 异常通知(@AfterThrowing):在目标方法运行出现异常的时候执行
* 返回通知(@AfterReturning):正常返回之后通知
* */
4、给切面类的目标方法标注何时何地运行(通知注解)
5、将切面类和目标类(目标方法所在类)都加入到容器中
6、告知Spring哪个类是切面类(给切面类加注解)
7、开启基于注解版的切面功能:
//(1)传统方式:
//(2)注解方式:给配置类@EnableAspectJAutoProxy
8、在Spring中很多@EnableXxx:开启一项功能来替代以前的XMl方式
@Aspect举例
@Aspect//告诉Spring,当前类是一个切面类 public class LogAspect { /* * 抽取公共的切入点表达式 * 1、本类引用@Pointcut() * 2、其他切面引用@Pointcut("com.phubing.aop.LogAspect.pointCut()") * */ @Pointcut("execution(public int com.phubing.aop.MathCalculator.div(int,int))") public void pointCut(){}; //在目标方法执行之前切入,(切入点表达式)--指定在哪个方法切入 @Before("pointCut()") public void logStart(JoinPoint joinpoint) { //joinpoint.getSignature().getName():获取方法的签名 //joinpoint.getArgs():目标方法运行需要使用的参数列表 Object[] args = joinpoint.getArgs(); System.out.println(""+joinpoint.getSignature().getName()+"运行了,参数列表是:"+args); } @AfterThrowing(value="pointCut()",throwing="exception") //JoinPoint joinpoint:必须写在参数的第一位,否则Spring无法识别 public void logException(JoinPoint joinpoint,Exception exception) { System.out.println(joinpoint.getSignature().getName()+"异常信息: "+exception); } //"pointCut()":切入点引用的表达式 //returning="":指定该参数由谁来封装返回值 @AfterReturning(value="pointCut()",returning="result") //Object result:使用Object封装所有类型的返回值 public void logReturn(Object result) { //方法正常返回,想要得到返回值 System.out.println("除法正常返回了,计算结果:{"+result+"}"); } @After("pointCut()") public void logEnd(JoinPoint joinpoint) { Object[] args = joinpoint.getArgs(); System.out.println(joinpoint.getSignature().getName()+"结束了,参数列表是:"+args); } }
@EnableAspectJAutoProxy:(AOP原理实现重要组件)
给容器中注册了什么组件,这个组件什么时候工作,这个组件工作时的功能是什么
AOP整个功能要起作用,均是由@EnableAspectJAutoProxy入手:
A、在该注解里调用了@Import(AspectJAutoProxyRegistrar.class)
给容器中导入了AspectJAutoProxyRegistrar组件,利用AspectJAutoProxyRegistrar给容器中注册Bean(ImportBeanDefinitionRegistrar:可以将自定义组件注册给 BeanDefinitionRegistry)
① 运行时,先注册registerAspectJAnnotationAutoProxyCreatorIfNecessary组件(如果需要的情况下),
② 然后调用registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null)方法,
③ 上一个方法调用registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source)来注册或者升级
④ registerOrEscalateApcAsRequired方法传入了一个类型为AnnotationAwareAspectJAutoProxyCreator的参数
⑤ 下一步判断,如果容器中已经包含该名字而且这个Creator的名是internalAutoProxyCreator的话,然后进行一系列工作
⑥ 由于是第一次创建,所以会创建一些Bean的定义信息,即AnnotationAwareAspectJAutoProxyCreator等,包括利用registry把将要在容器中创建的Bean的进行定义(即Bean的定义就是要创建AnnotationAwareAspectJAutoProxyCreator),然后这个Bean的名称就是internalAutoProxyCreator,该Bean的注册信息正好注册在BeanDefinitionRegistry中
⑦ 第一步做完之后就相当于给容器注册了一个名为EnableAspectJAutoProxy的Bean,然后把EnableAspectJAutoProxy注解的内容信息取出
⑧ 取出之后检查是否为true,如果是,则做一系列工作
if(enableAspectJAutoProxy.getBoolean("proxyTargetClass")){ AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); }
⑨ 再看如果为true,如果是则暴露一些代理的一些Bean(后面再讲)
if(enableAspectJAutoProxy.getBoolean("exposeProxy")){ AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); }
B、第一步完成
即利用EnableAspectJAutoProxy中的AspectJAutoProxyRegistrar给容器注册一个AnnotationAwareAspectJAutoProxyCreator(注解装配模式的Aspect切面自动代理创建器),相当于帮我们注册一个自动代理创建器,那这个创建器是什么?(子类 -> 父类)
① AnnotationAwareAspectJAutoProxyCreator -> AspectJAwareAdvisorAutoProxyCreator -> AbstractAdvisorAutoProxyCreator
-> AbstractAutoProxyCreator(抽象的自动代理创建器)
-> SmartInstantiationAwareBeanPostProcessor(Bean的后置处理器),BeanFactoryAware(能调用setBeanFactory()--即能把Bean工厂作为参数传递进来)
② 关注SmartInstantiationAwareBeanPostProcessor(Bean的后置处理器):在Bean初始化前后所做的事情、自动装配BeanFactory
C、AnnotationAwareAspectJAutoProxyCreator(步骤)
① AbstractAutoProxyCreator
a、this.setBeanFactory()
b、AbstractAutoProxyCreator有后置处理器的逻辑
② AbstractAdvisorAutoProxyCreator
a、虽然其父类(AbstractAutoProxyCreator)有setBeanFactory(),但在子类中已被重写(调用initBeanFatory())
③ AspectJAwareAdvisorAutoProxyCreator
④ AnnotationAwareAspectJAutoProxyCreator
a、initBeanFatory()---为什么会有?
因为在其父类中使用setBeanFactory()调用该方法,虽然父类中有写,但是被此类重写了,所以父类在进行setBeanFactory()调用的是此类的initBeanFatory()
D、AnnotationAwareAspectJAutoProxyCreator(创建和注册该类的过程)
① 启动时,首先创建一个IOC容器(AnnotationApplicationCOntext()),调用其一个有参构造器,传入配置类
A、其有参构造器分为三步:
a、this();(无参构造器创建对象)
b、register(annotatedClasses);(再来注册一个配置类)
c、refresh();(接着调用此方法,刷新容器)
1) refresh()中重要的是registerBeanPostProcessors(beanFactory);--注册Beanpost控制处理器(注册这些Bean的后置处理器,用于拦截创建操作)
2)registerBeanPostProcessors(beanFactory)会调用PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
3)如何注册Bean的后置处理器?
第一步:
先getBeanNamesForType(BeanPostProcessor.class, true, false);(拿到IOC容器中所有需要创建对象的Bean的类型){
为什么IOC中会有一些已经定义的Bean?
第一步中传入了配置类,然后在解析配置类的时候,有一个注解@EnableAspectJAutoProxy,之前提到过,
它会在IOC容器中创建一个AnnotationAwareAspectJAutoProxyCreator,以及容器中还有一些默认的后置处理器的Bean等,只是还没有创建对象,都只是一些定义(有处理AutoWired、Required、Common以及internal等注解的处理器),前面在分析@EnableAspectJAutoProxy的时候,它会给容器中注册一个internalAutoProxyCreator组件(只是当时注册是BeanDefination--Bean的注册信息)
还有一些其他逻辑{
它会给beanFatory还额外加了一些BeanPostProcessor,从这些BeanProcessor从分离,区分哪些是实现了PriorityOrdered
List priorityOrderedPostProcessors = new ArrayList();
做了一些相应处理:
是否实现了某个接口,如实现了PriorityOrdered(Order子类,有优先级排序功能),则保存在priorityOrderedPostProcessors.add(pp);
分为三步:
First:先注册BeanPostProcessor,实现了PriorityOrdered接口(相当于优先注册该接口)
Next:再实现Orderd接口,因此在容器中注册
Third:普通的BeanPostProcess的注册(没实现优先级接口)
}
而所谓的注册{
1) 拿到BeanPostProcessor名字,然后从beanFatory中获取
A、调用doGetBean方法,然后doGetBean方法调用getSingleton()方法,但是第一次获取的时候容器中又不会目标Bean的名字, 如果有问题导致获取不到,就会调用和getObject()方法,而在getObject()中会创建一个Bean,然后保存在容器中{
a、如何创建internalAutoProxyCreator的BeanPostProcessor(类型为AnnotationAwareAspectJAutoProxyCreator){
1) 创建Bean实例
2) populateBean(beanName,mbd,instanceWrapper)--给Bean的属性赋值
3) initializebean:初始化Bean(后置处理器就是在此Bean的前后进行工作的){
i、invokeAwareMethods()--判断目标Bean是否为Aware接口的实现类,如果是则判断具体是哪一个Aware,然后调用相关的Aware方法(处理Aware接口的回调)
ii、applyBeanpostprocessorsbeforeinitlalization()--应用后置处理器的beforeinitlalization,先拿到所有的后置处理器,调用后置处理器的postProcessorInitialization()
iii、invokeIntitMethods()--执行自定义的初始化方法(使用@Bean指定初始化、销毁等方法是什么)
iiii、applyBeanPostProcessorsAfterInitializationl()执行后置处理器的
postProcessorAfterInitialization() } } } } }
第二步{
① 实现了BeanFacoryAware接口,如果是则调用AbstractAutoCreator的setBeanFactory(),
② 然后再调用InitBeanFactory()方法
A、相当于创建了两个东西
a、一个是ReflectAspectJAdvisorFatory()
b、另一个是Beanfactoryaspectjadvisorsbuilderadapter--将BeanFatory等重新包装
}
第三步BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;--aspectJAdvisorBuilder
第四步把BeanPostProcessor注册到BeanFactory里中:beanFactory.addBeanPostProcessor(postProcessor)
E、AnnotationAwareAspectJAutoProxyCreator(作为后置处理器是如何运作的)
① 先到setBeanFactory()方法,一直到AbstractAutoProxyCreator,然后调用postProcessBeforeInstantiation()方法{
1) 与postProcessBeforeInitzation()有何区别?
A、AbstractAutoProxyCreator实现的是SmartInstantiationAwareBeanPostProcessor,而实现的类继承是InstantiationAwareBeanPostProcessor,而继承的类继承的是BeanPostProcessor,也就是说AnnotationAwareAspectJAutoProxyCreator是InstantiationAwareBeanPostProcessor类型的后置处理器
}
② finishbeanfactoryinitialization(beanfactory);完成BeanFactory初始化工作(创建剩下的Bean实例)--IOC容器中的一些组件例如RegisterBeanPostProceesor早就已经被创建
A、遍历获取容器中所有的Bean,依次创建对象--getBean(beanName) getBean()<--doGetBean()<--getSingleton()
③ 创建Bean
【AnnotationAwareAspectJAutoProxyCreator在所有Bean创建之前会有一个拦截,因为它是InstantiationAwareBeanPostProcessor类型的后置处理器,所以会调用postProcessorsBeforeInstantiation()】
A、先从缓存中获取当前Bean,如果能获取到,说明该Bean之前就被创建了,则直接使用,否则再创建(只要创建了Bean都会被缓存起来)
B、CreateBean()--创建Bean
【BeanPostProcessor是在Bean对象创建完成初始化前后调用】
【AnnotationAwareAspectJAutoProxyCreator是在任何Bean创建实例之前尝试返回Bean的实例】
【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象】
a、拿到要创建Bean的定义信息,然后调用resolveBeforInstantiation(beanName,mbdToUse);--解析BeforInstantiation
1) 给后置处理器一个机会来返回代理对象,来替代目标的实例---也就是希望后置处理器能在此返回一个代理对象,如果能则返回,不能则调用doCreateBean()
i、后置处理器先尝试返回对象:
0、bean = applyBeanPostProcessorsBeforeInstantiation()--拿到所有的后置处理器,如果后置处理器如果是InstantiationAwareBeanPostProcessor,则调用postProcessorsBeforeInstantiation(),如果bean不等于null,则调用applyBeanPostProcessorsAfterInitializationl()
2) doCreateBean(beanName,mbdToUse,args)就是上面提到的真正创建Bean实例的流程
F、postProcessBeforeInstantiation
(1) AnnotationAwareAspectJAutoProxyCreator[InstantiationAwareBeanPostProcessor]的作用就是每一个Bean创建之前调用postProcessBeforeInstantiation()方法
1) 判断当前bean是否在advicedBeans中(保存了所有需要增强的Bean--需要切面来切的)
2) 判断当前bean是否为基础类型(是否实现了Advice || PointCut || Advisor || AopInfrastructureBean接口)或者是否为切面(@Aspect或实现注解接口)
3) 判断是否需要跳过()
A、获取候选的增强器(切面中的通知方法--将这些通知方法的详细信息包装成了【List candidateAdvisors】)
a、每一个封装的通知方法的增强器时InstantiationModelAwarePointcutAdvisor
b、判断每一个增强器是否为AspectJPointcutAdvisor类型
B、永远返回false
(2) 创建对象
//包装如果需要的情况
1) postProcessAfterInstantiation 会 return wrapIfNecessary(bean,beanName,cachekey);
A、获取当前Bean的所有增强器(通知方法)
a、找到候选的能能在当前Bean能用增强器(找哪些通知方法需要切入到当前方法的)
b、获取到能再Bean中使用的增强器
c、给增强器排序
B、保存当前Bean在advisedBean中
(3) 如果当前Bean需要增强,创建当前Bean的代理对象
A、获取所有的增强器(所有通知方法)
B、保存到proxyFactory
C、创建代理对象
a、JdkDynamicAopProxy(config):JDK动态代理(实现了接口)
b、ObjenesisCglibAopProxy(config):Cglib的动态代理
(4) 给容器中返回当前组件使用cglib增强了的代理对象
(5) 以后容器获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程
G、目标方法执行
(1) 容器中所创建的代理对象(cglib增强后的对象),这个对象保存了详细信息(薄如增强器,目标对象,XXX)
1) CglibProxy.intercept();拦截工作逻辑
(2) 根据ProxyFactory对象获取目标方法将要执行方法的拦截器链
List chain = this.advised.getInterceptorsAndDynamicInrterceptorAdvice(method,targetClass);
1) List interceptorList保存所有拦截器(5个)
A、一个ExposeInvocationInterceptor 和 四个增强器
2) 遍历所有的增强器,将他们转成interceptor[] interceptor = registory.getInterceptors(advisor)
3) 将增强器转为List>MethodInterceptor>,如果是该种类型就直接转,如果不是仍需要一些增强器Adaptor再转换为Interceptor,转换完成返回Interceptor数组
(3) 如果没有拦截器链,直接执行目标方法
1) 拦截器链即每一个通知方法又被包装成方法拦截器,利用MethodInterceptor机制
(4) 如果有拦截器链,把要执行的目标对象、方法、拦截器链。。传入CglibMethodInvocation对象并调用Object return mi.proceed()方法
H、拦截器链的触发过程
(1) 如果没有拦截器执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法
(2) 链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器返回之后再执行(拦截器链机制保证通知方法与目标方法的执行顺序)
总结:
1、@EnableAspectJAutoProxy 开启AOP功能
2、@EnableAspectJAutoProxy 会给容器中注册一个组件AnnotationAwareAspectJProxyCreator
3、AnnotationAwareAspectJProxyCreator是一个后置处理器
4、容器的创建流程:
(1) registerBeanPostProcessors()注册后置处理器(AnnotationAwareAspectJProxyCreator对象)
(2) 初始化剩下的单实例Bean
1) 创建业务逻辑组件和切面组件
2) AnnotationAwareAspectJProxyCreator对象拦截组件的创建过程
3) 组件创建完成之后,判断组件是否需要增强
① 需要则将切面的通知方法包装成增强器(Advisor)给业务逻辑组件创建一个代理对象(cglib)
5、执行目标方法
(1) 代理对象执行目标方法
(2) CglibProxy.intercept();
1) 得到目标方法的拦截器链(增强器包装秤拦截器--MethodInterceptor)
2) 利用拦截器链的链式机制,依次进入每一个拦截器进行执行
3) 效果
A、正常执行:前置通知 --> 目标方法 --> 后置通知 --> 正常返回通知
B、出现异常:前置通知 --> 目标方法 --> 后置通知 --> 正常异常通知