目录
2.1 注册AnnotationAwareAspectJAutoProxyCreator
2.3.1 getAdvicesAndAdvisorsForBean-查找匹配切面
一、使用示例
* 1、导入aop模块;Spring AOP:(spring-aspects)
* 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
* 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
* 通知方法:
* 前置通知(@Before):logStart:在目标方法(div)运行之前运行
* 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
* 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
* 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
* 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
* 4、给切面类的目标方法标注何时何地运行(通知注解);
* 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
* 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
* [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
* 在Spring中很多的 @EnableXXX;
*
* 三步:
* 1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
* 2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
* 3)、开启基于注解的aop模式;@EnableAspectJAutoProxy
切面类
/**
* 切面类
* @author lfy
*
* @Aspect: 告诉Spring当前类是一个切面类
*
*/
@Aspect
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
public void pointCut(){};
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
}
@After("com.atguigu.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value="pointCut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
}
@AfterThrowing(value="pointCut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
}
}
业务类
public class MathCalculator {
public int div(int i,int j){
System.out.println("MathCalculator...div...");
return i/j;
}
}
配置类
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
//业务逻辑类加入容器中
@Bean
public MathCalculator calculator(){
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 0);
applicationContext.close();
二、原理
Spring AOP的功能从使用上直白的说,就是根据我们的配置来生成代理类,拦截指定的方法,将指定的advice织入。
2.1 注册AnnotationAwareAspectJAutoProxyCreator
@EnableAspectJAutoProxy
注解用来开启 Spring AOP注解的使用。这个的作用就是自动让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理
@EnableAspectJAutoProxy
注解内部使用@Import
注解将 AspectJAutoProxyRegistrar
注入到 IoC 容器当中,最终给容器中注册一个AnnotationAwareAspectJAutoProxyCreator。
AnnotationAwareAspectJAutoProxyCreator其实是一个后置处理器。它里面有个方法:postProcessBeforeInstantiation
很显然是在bean实例化前调用的。
2.2 Bean实例化前解析并缓存切面
postProcessBeforeInstantiation
-> shouldSkip -> findCandidateAdvisors
若Bean的类型是切面,就解析并且缓存切面的advisor信息。就是是否标注了@Aspect注解,有的话就遍历这个Bean中所有未标注@PointCut的方法,也就是@Before,@After,@Around等标注的方法,对于这些方法都会新建一个Advisor对象,存到advisorsCache中。
Advisor:切面由 Advise 和 Pointcut 组成
Advise:通知方法(就是我们的@Before 注解的方法)、Pointcut:切点(被增强的方法)
此时拦截表达式(切点)还是空的
2.3 Bean初始化后适配切面->生成代理
AbstractAutoProxyCreator的postProcessAfterInitialization执行时(也就是执行Bean的后置处理器),会取出最开始缓存的advisors信息。看是否与当前的Bean适配,决定是否创建代理对象。
这个过程中最核心的就是wrapIfNecessary方法,它又包含两个重要方法:getAdvicesAndAdvisorsForBean、createProxy。
其核心思想如下:
- 获取当前bean所适用的拦截器列表
- 如果拦截器列表为空,说明当前bean不需要进行aop增强,那么直接返回原始bean;
- 如果拦截器列表不为空,说明当前bean需要进行aop增强,那么为它生成代理对象返回,生成代理对象的方式无非就是jdk proxy或者是cglib proxy两种方式,根据bean的情况具体判断
2.3.1 getAdvicesAndAdvisorsForBean-查找匹配切面
1. findEligibleAdvisors 会从缓存取出所有切面信息(candidateAdvisors)。
2.findAdvisorsThatCanApply根据advisor信息中的表达式进行方法对当前Bean的匹配,要先确定类是否匹配,然后确定方法是否匹配。最后将匹配到的advisor排序(为了后续封装成执行链)
如果是第一次执行适配方法findAdvisorsThatCanApply的话,candidateAdvisors中的拦截表达式还是空的,需要进行表达式获取,也就是@Pointcut的value
2.3.2 createProxy-生成代理对象
1.首先要创建一个代理工厂-ProxyFactory
ProxyFactory
在创建过程中封装并保存了筛选后的 Advisor
集合以及其他的一些属性(织入增强器)。而在后面创建代理类的时候,将 ProxyFactory
作为参数传递给了 JdkDynamicAopProxy
和 ObjenesisCglibAopProxy
。
2.接下来获取代理对象,根据Bean类型来判断使用JDK代理还是cglib代理。
3.1 对于JDK代理方式
1)在 JdkDynamicAopProxy的invoke 方法中获取到了当前方法的拦截链(将所有的Advisor中的Advice 转换成 MethodInterceptor),存在数组中。
2)调用 invocation.proceed方法时。对于每个拦截器判断是否适用于当前方法,如果适用则调用拦截器的 invoke 方法来执行增强方法。
3.2 对于cglib代理方式
组件的代理对象(cglib增强后的对象)中保存了详细信息(比如增强器,目标对象,xxx);
1)根据ProxyFactory对象获取所有增强器,将其转为Interceptor存在数组中形成拦截器链。
2)调用 invocation.proceed方法时。链式获取每一个拦截器,拦截器执行 invoke 方法来执行增强方法。