前期认识
- 切面(Aspect):切面编程的完整定义模块,包含了何时、对谁、如何等等所有的内容
- 连接点(Join point):能够植入切面的部分,被运用到哪些方法上
- 通知(Advice):要对切面添加的功能代码,比如权限、guava 限流、事务,日志等功能代码
- 切入点(Pointcut):针对哪些方法植入通知,也就是指定具体的拦截地点
- 引入(Introduction):对目标类添加新方法及属性
- 目标对象(Target object):被切面处理的对象,目标对象无法感知到切面的存在
- 代理(proxy):实现 AOP 方式,基于 JDK、CGLIB
- 织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程,有三种方式:spring 采用的是运行时
通知(Advice)五种类型
- @Before:前置通知,在调用目标方法之前执行通知定义的任务
- @After:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
- @AfterReturning:最终通知,在目标方法执行结束后,无论执行结果如何,都要执行通知定义的任务
- @AfterThrowing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
- @Around:环绕通知,在目前方法执行的前后,都需要执行通知定义的任务
我们如何做?
- 先编写额外的逻辑类,也就是切面:AspectJ
- 具体的哪些方法要被执行处理,需要配置表达式:expression、point-cut
- 额外的逻辑处理,有几个通知消息或者说有哪些逻辑可以被执行
AOP 注解方式读取准备工作
先介绍在使用 AOP 时的一些核心接口、类、方法:
- Advisor:AOP 顶级接口,用来管理 advice、point-cut,其下有两个子接口:
PointcutAdvisor、IntroductionAdvisor
,它们之间的区别在于
IntroductionAdvisor 应用于类级别的拦截,只能使用 Introduction 类型的 Advice
PointcutAdvisor 应用于方法级别的拦截 ,可以使用任何类型的 Pointcut,以及几乎任何类型的 Advice
- AspectJPointcutAdvisor:最外层的通知者类,内部含有 Advice > 包含五种实现增强器、AspectJExpressionPointcut 类型
- AspectJExpressionPointcut:该类属于原型模式,每一个 advisor 都会依赖一个新的该类型,它还实现了两个接口:分别是类表达式匹配器 ClassFilter > 只有一个匹配方法函数式编程接口,只应用于接口和类上的匹配、方法表达式匹配器 MethodMatcher > 内部有三个方法,可以支持无参数的静态匹配,也支持有参数的动态匹配
- AnnotationAwareAspectJAutoProxyCreator:AspectJAwareAdvisorAutoProxyCreator 子类,使用 AspectJ 语法创建 Advisor 和代理对象的类,<aop:aspectj-autoproxy /> 标签默认注入到 BeanDefinitionNames、BeanDefinitionMaps 中,SpringBoot 方式也是会通过该类型去进行相关类型的解析工作.
Advisor、Advice
resolveBeforeInstantiation -> applyBeanPostProcessorsBeforeInstantiation -> AbstractAutoProxyCreator#postProcessBeforeInstantiation -> shouldSkip(beanClass, beanName)
// 查找通知器 @Override protected List<Advisor> findCandidateAdvisors() { // 找到系统中实现了Advisor接口的bean,如果存在就放入缓存,并进行创建,然后返回 List<Advisor> advisors = super.findCandidateAdvisors(); // 找到系统中使用 @Aspect 标注 bean,并且找到该 bean 中使用 @Before,@After 等标注的方法 if (this.aspectJAdvisorsBuilder != null) { // 将这些方法封装为一个个 Advisor advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors; }
先是找到所有实现了 Advisor 接口 Bean,再获取到有标识 @AspectJ 注解的类,然后扫描它下面的所有除了标识 @PointCut 注解的 advisor 方法后,设置表达式:可能是方法也可能是表达式,取决于配置的方式,再进行 advisor 实例化,advisor 实例化也是需要三个对象的,分别是 MetadataAwareAspectInstanceFactory、Method、AspectJExpressionPointcut
buildAspectJAdvisors->this.advisorFactory.isAspect(beanType)->this.advisorFactory.getAdvisors(factory)->getAdvisor(method,…) ->new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut,…)->instantiateAdvice(this.declaredPointcut)
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) { // 入参为切点表达式类 // 这里是通过调用 aspectJAdvisorFactory 获取 Advice // aspectJAdvisorFactory 实例是 ReflectiveAspectJAdvisorFactory // 所以最终我们还是要到 ReflectiveAspectJAdvisorFactory 中去分析 Advice 获取过程 // ReflectiveAspectJAdvisorFactory 是一个重要的类 Advisor 和 Advice 获取都是在这个类中完成的 // 入参为:通知方法、切点表达式类、切面实例、切面的一个顺序、切面类名 Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,this.aspectInstanceFactory, this.declarationOrder, this.aspectName); return (advice != null ? advice : EMPTY_ADVICE); }
到这里,就会提前把每个 Bean 需要增强的通知提前准备好,存入到缓存中
总结创建过程
- 创建 AspectJPointcutAdvisor#0-4,先通过
带参的构造方法
进行对象的创建,但是想使用带参数的构造参数,必须要把参数对象提前准备好,因此要先准备创建好内置包含的对象,例如:AspectJAroundAdvice - 创建 AspectJAroundAdvice,也需要通过带参的构造函数进行创建,提前准备好具体的参数对象,包含三个参数:MethodLocatingFactoryBean、AspectJExpressionPointcut、SimpleBeanFactoryAwareAspectInstanceFactory
- 分别创建好上述的三个对象,其创建过程都是调用无参的构造方法,直接反射生成即可
- 总体来说,XML 文件配置 AOP、注解配置 AOP 在流程上是差不多的,只是注解是一次性全部到位,但 XML 要提前做 BeanDefinition 准备工作
代码部分
顶级接口
public interface Calculator { public Integer add(Integer i,Integer j) throws NoSuchMethodException; public Integer sub(Integer i,Integer j) throws NoSuchMethodException; public Integer mul(Integer i,Integer j) throws NoSuchMethodException; public Integer div(Integer i,Integer j) throws NoSuchMethodException; }
接口实现类
// 实现接口采用的就是 JDK 代理 public class MyCalculator /*implements Calculator*/ { public Integer add(Integer i, Integer j) throws NoSuchMethodException { Integer result = i+j; return result; } public Integer sub(Integer i, Integer j) throws NoSuchMethodException { Integer result = i-j; return result; } public Integer mul(Integer i, Integer j) throws NoSuchMethodException { Integer result = i*j; return result; } public Integer div(Integer i, Integer j) throws NoSuchMethodException { Integer result = i/j; return result; } public Integer show(Integer i){ System.out.println("show ....."); return i; } @Override public String toString() { return "super.toString()"; } }
配置 XML 文件
<bean id="logUtil" class="com.vnjohn.aop.xml.util.LogUtil"/> <bean id="myCalculator" class="com.vnjohn.aop.xml.service.MyCalculator"/> <!-- <aop:config> <aop:aspect ref="logUtil"> <aop:pointcut id="myPoint" expression="execution( Integer com.vnjohn.aop.xml.service.MyCalculator.* (..))"/> <aop:around method="around" pointcut-ref="myPoint"/> <aop:before method="start" pointcut-ref="myPoint"/> <aop:after method="logFinally" pointcut-ref="myPoint"/> <aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"/> <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/> </aop:aspect> </aop:config>--> <aop:aspectj-autoproxy/>
AOP 代理增强类
@Aspect public class LogUtil { @Pointcut("execution(public Integer com.vnjohn.aop.xml.service.MyCalculator.*(Integer,Integer))") public void myPointCut(){} // @Pointcut("execution(* *(..))") public void myPointCut1(){} @Before(value = "myPointCut()") private int start(JoinPoint joinPoint){ //获取方法签名 Signature signature = joinPoint.getSignature(); //获取参数信息 Object[] args = joinPoint.getArgs(); System.out.println("log---"+signature.getName()+"方法开始执行:参数是"+Arrays.asList(args)); return 100; } // @AfterReturning(value = "myPointCut()",returning = "result") public static void stop(JoinPoint joinPoint,Object result){ Signature signature = joinPoint.getSignature(); System.out.println("log---"+signature.getName()+"方法执行结束,结果是:"+result); } // @AfterThrowing(value = "myPointCut()",throwing = "e") public static void logException(JoinPoint joinPoint,Exception e){ Signature signature = joinPoint.getSignature(); System.out.println("log---"+signature.getName()+"方法抛出异常:"+e.getMessage()); } // @After("myPointCut()") public static void logFinally(JoinPoint joinPoint){ Signature signature = joinPoint.getSignature(); System.out.println("log---"+signature.getName()+"方法执行结束。。。。。over"); } // @Around("myPointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Signature signature = pjp.getSignature(); Object[] args = pjp.getArgs(); Object result = null; try { System.out.println("log---环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+Arrays.asList(args)); //通过反射的方式调用目标的方法,相当于执行method.invoke(),可以自己修改结果值 result = pjp.proceed(args); // result=100; System.out.println("log---环绕通知stop"+signature.getName()+"方法执行结束"); } catch (Throwable throwable) { System.out.println("log---环绕异常通知:"+signature.getName()+"出现异常"); throw throwable; }finally { System.out.println("log---环绕返回通知:"+signature.getName()+"方法返回结果是:"+result); } return result; } }
测试基础类
public class TestAop { public static void main(String[] args) throws Exception { saveGeneratedCGlibProxyFiles(System.getProperty("user.dir")+"/proxy"); ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml"); MyCalculator bean = ac.getBean(MyCalculator.class); System.out.println(bean); bean.add(1,1); bean.sub(1,1); } public static void saveGeneratedCGlibProxyFiles(String dir) throws Exception { Field field = System.class.getDeclaredField("props"); field.setAccessible(true); Properties props = (Properties) field.get(null); System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, dir);//dir为保存文件路径 props.put("net.sf.cglib.core.DebuggingClassWriter.traceEnabled", "true"); } }
FAQ
当使用 AOP 时,需要进行 N 多个对象的创建,但是在创建过程中需要做很多判断,判断当前对象是否需要被代理,而代理之前,需要的 advisor 对象必须提前准备好,才能进行后续的判断
若定义了一个普通的对象,会进入 resolveBeforeInstantation 方法的处理吗?
不会,普通对象会在执行初始化方法时,调用
BeanPostProcessor#after
方法进行处理;判断是否需要被代理,需要才返回代理对象,否则仍然是普通对象