一、前言

面向切面的编程(Aspect-oriented Programming, AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(Object-oriented Programming, OOP)。 OOP 中模块化的关键单元是类,而在 AOP 中模块化是切面。切面使关注点(例如事务管理)的模块化可以跨越多种类型和对象。
Spring 的一个关键组件是 AOP 框架,虽然 Spring IoC 容器不依赖于 AOP,但是 AOP 补充了 IoC,从而提供了一个非常强大的中间件解决方案。
Spring 官方文档可见
官方文档之使用AOP
官方文档之Spring AOP APIS

二、相关概念

专有名词 含义
Advice
通知
自定义的功能代码。
比如你想在某个方法前打印一段关于方法名称、参数等日志详情,
这就属于 通知
JoinPoint
连接点
允许你使用通知(Advice)的地方。
方法执行前后(包括抛出异常)。
一个方法属于一个连接点。
Pointcut
切入点
针对多个方法而设置。
即批量设置。
Aspect
切面
切面是 **通知** **切入点** 的结合。
两者结合就是一个完整的切面定义
Introduction
引入
向目标类添加额外的 接口 以及 新的字段
Target
目标
引入所提及的目标类,也就是要被通知的对象。
是否被引入通知它是不清楚的
Weaving
织入
三种方式
- 编译时织入
- 类加载时期织入
- 运行时织入

SpringAOP 属于运行时织入
同时Spring 提供 Load Time Weaving, LTW 加载期织入。 |

其实我们只需要关注切入点(Pointcut)和切面(Aspect)即可。在代码中定义这两项,基本实现 AOP。

三、从源码角度看 Advice/MethodInterceptor(通知)

3.1 环绕通知

环绕通知可在目标方法执行前后织入通知。接口实现如下:
MethodInterceptor.png
我们只需要实现 invoke(MethodInvocation) 方法即可。

注意,需要在方法内部调用 Joinpoint#proceed() 调用目标对象的方法。

继承关系图如下:
MethodInterceptor#uml.png

  • Advice: 通知标志接口。任意类型的通知都可以实现。
  • Interceptor: 通用拦截器标志接口。

自定义环绕通知

  1. /**
  2. * #2 定义通知-环绕通知
  3. */
  4. class MyAroundAdvice implements MethodInterceptor {
  5. @Nullable
  6. @Override
  7. public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
  8. System.out.println("(环绕通知1) 方法调用前触发,当前方法名称: " + invocation.getMethod().getName());
  9. // 重要,不能忘记
  10. Object result = invocation.proceed();
  11. System.out.println("(环绕通知1) 获取方法调用返回值: " + result);
  12. return result;
  13. }
  14. }

3.2 前置通知

在目标方法执行之前调用。接口定义如下:
MethodBeforeAdvice.png
继承关系如下图:
MethodBeforeAdvice#uml.png
BeforeAdvice 是一个标志接口。
自定义前置通知:

  1. /**
  2. * #2 定义通知-前置通知
  3. */
  4. class MyBeforeAdvice implements MethodBeforeAdvice {
  5. @Override
  6. public void before(Method method, Object[] args, Object target) throws Throwable {
  7. System.out.println("(前置通知) 方法调用之前回调此方法");
  8. }
  9. }

3.3 后置通知

在目标方法正常返回之前回调。若抛出异常,则不会回调。
这些后置通知只能查看返回值,但不能改变。
接口定义如下:
AfterReturningAdvice.png
继承关系如下图:
AfterReturningAdvice#uml.png
AfterAdvice 也是一个标记接口。
自定义后置通知:

  1. /**
  2. * #2 定义通知-后置通知
  3. */
  4. class MyAfterReturnAdvice implements AfterReturningAdvice {
  5. @Override
  6. public void afterReturning(Object returnValue,
  7. Method method,
  8. Object[] args, Object target) throws Throwable {
  9. System.out.println("(后置通知) 方法调用结束后触发通知,当前方法名称: " + method.getName());
  10. }
  11. }

3.4 异常通知

异常通知中没有定义任何方法,它只是一个标记接口。但是方法的签名是有要求的。

  • public void afterThrowing(Exception ex)
  • public void afterThrowing(RemoteException)
  • public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
  • public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)

ThrowsAdvice.png
继承关系图如下:
ThrowsAdvice#uml.png

3.5 引入通知

引入通知的主要作用是可以让生成的代理类实现额外的接口。底层是基于 MethoInterceptord 实现。简单来说,就是当方法调用时,判断当前接口是否属于目标对象所拥有的方法,是则直接调用目标对象方法,否则调用委托类的方法。
核心代码如下:
DelegatingIntroductionInterceptor#invoke.png
继承体系如下
DelegatingIntroductionInterceptor.png

3.6 Advice 小结

  • 分类: 通知按实现接口分类
    • 未实现 MethodInterceptor 接口
      • 前置通知
      • 后置通知
      • 异常通知
    • 实现 MethodInterceptor 接口
      • 环绕通知
      • 引入通知
  • MethodInterceptor 是 前置/后置/异常 等通知的基础实现,它们的底层也是使用 MethodInterceptor 实现的。Spring-AOP 提供适配器完成适配。
    • 比如 org.springframework.aop.framework.adapter.AfterReturningAdviceAdapter 完成 AfterReturningAdviceMethodInterceptor 的适配。
  • 不同的 MethodInterceptor 封装了 MethodInvocation#proceed() 不同的调用时机。

后置通知适配者
AfterReturningAdviceInterceptor.png
注意其中的 invoke(MethodInvocation) 方法,里面最重要的是 mi.proceed() 何时执行。需要根据当前通知类型确定。

四、Advisor

一个 Advisor 由一个切点和一个通知组成。它支持不同类型的通知(Advice),比如前置通知、后置通知等。这些并不需要使用拦截器链实现。
没有指定切点的通知是毫无意义的。

五、拦截器回调

拦截器回调的执行顺序 Spring 设计得很巧妙。
拦截器 MethodInterceptorAdvice 通知是独立的。而 Advice 底层是通过适配器适配成 MethodInterceptor ,成为拦截器链中的一员,从而实现 AOP 切面的功能。
MethodInterceptor#invoke() 方法控制拦截器相对原始目标对象方法执行的顺序。比如前置通知,需要在目标方法执行之前被回调,后置通知则需要在目标方法执行之后被回调。
拦截器回调步骤为:

  1. 根据目标对象和方法获取合适的拦截器链。
  2. 对 CGLib 代理而言,创建 CglibMethodInvocation 并执行 proceed() 方法调用。
  3. 判断拦截器链集合中最后一个拦截器是否已被回调,如果是,则通过反射执行目标对象的方法,结束递归调用。否则执行下一步。
  4. 获取下一个将要执行拦截器链,如果为动态则动态匹配,静态直接调用 MethodInterceptor#invoke(this) 方法。(通知也是被适配成 MethodInterceptor 实现对象,里面封装了相应的调用顺序)。
  5. 通过调用 MethodInvocation#proceed() 继续回到 ReflectiveMethodInvocation#proceed() 方法中,回到步骤3。

核心代码片段如下:
ReflectiveMethodInvocation#proceed.png

六、Spring AOP 动态代理的创建

Spring AOP 动态代理创建有三种方式,分别是

  • ProxyFactory
  • proxyFactoryBean
  • @EnableAspectJAutoProxy

ProxyFactoryProxyFactoryBean 这两种创建 AOP 方式由 spring-aop 提供,而注解是 spring-context 所提供,目的是让 spring-aop 与 spring 方便进行整合。

6.1 ProxyFactory

ProxyFactory_demo.png
继承关系如下
ProxyFactoryBean.png

  • ProxyCreatorSupport: 创建代理对象工厂,通过该工厂可获取代理对象。

    6.2 ProxyFactoryBean

    ProxyFactoryBean 基于 Spring BeanFactory 中的 Bean 构建 AOP 代理。内部通过列表 interceptorNames 标识 MethodInterceptorAdvisor
    ProxyFactoryBeanTest.png
    他做的事情其实是和 ProxyFactory 是同性质的,只不过是借助 Spring 的 FactoryBean 实现,这样就可以和 Spring 进行整合。

    6.3 @EnableAspectJAutoProxy

    @EnableAspectJAutoProxy 属于 Enable 模式之一。开启则表示支持使用 AspectJ 相关注解标记组件(components)。与在 XML 配置 <aop:aspectj-autoproxy> 效果一样。
    当注解开启,我们就可以使用以下编程方式编写自定义的 AOP。
    更多内容详见 Spring 官方文档之 AOP
    Annotation_aspect.png
    而使用 @EnableXX 模式就离不开 @Import 注解,它是 Spring 提供的功能强大的扩展之一。我们看一下该注解源码是长什么样子的:
    EnableAspectJAutoProxy.png
    可以看到通过 @Import 引入了一个新的类 AspectJAutoProxyRegistrar

    AspectJAutoProxyRegistrar

    主要是构建 org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator 的 BeanDefinition 信息并注册到 BeanFactory 对象工厂中。

    AnnotationAwareAspectJAutoProxyCreator

    AspectJAwareAdvisorAutoProxyCreator 的子类,它处理当前应用程序上下文中所有的 AspectJ 注解、Advisors。
    继承关系如下:
    AnnotationAwareAspectJAutoProxyCreator.png
    可以看到,AnnotationAwareAspectJAutoProxyCreator 的继承体系十分复杂,不过我们可以适当拆解。

  • BeanPostProcessor: 后置处理器。Spring BeanFactory 每当创建一个对象时都需要经过后置处理器处理。这是 Spring 提供的扩展接口之一。Spring AOP 利用这个扩展在对象创建的时候判断当前对象是否有资格被代理,如果有则返回代理对象。

  • Aware: 意识接口,spring 扩展接口之一。BeanFactoryAware 可以获取 BeanFactory 对象工厂。BeanClassLoaderAware 可以获取 Bean 加载器。
  • Ordered: 排序接口。表明 AOP 可自定义切面顺序。
  • ProxyConfig: 创建代理工厂所需要的配置。比如选择CGLib 还是 JDK 创建代理呢?

总述 Spring AOP 是如何自动生成代理对象的。

  1. 开启自动代理注解。@EnableAspectJAutoProxy
  2. 该注解会导致 BeanFactory 自动装配 AnnotationAwareAspectJAutoProxyCreator 对象。
  3. AnnotationAwareAspectJAutoProxyCreator 属于 BeanPostProcessor 后置处理器。在 BeanFactory 创建后置处理器时会判断当前类是否有资格被代理。
    1. 若 @Aspect 注解切面配置类未被解析则解析解析动作。
      1. 解析步骤在 BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()
    2. 根据当前类获取适配的拦截器链并进行回调