手写AOP:把AspectJ从Spring中扣出来!

125 阅读5分钟

简易的AOP

  • 不依赖IoC容器,可独立使用
  • 支持Spring AOP和AspectJ

学习方式

  • 先把掌握AOP的核心原理实现
  • 然后pull dummy-aop到本地,把测试案例都跑一遍,沿着测试案例debug几遍,把流程搞清楚
  • 最后,可以尝试把dummy-ioc和dummy-aop两个项目整合起来,得到完整的dummy-spring(通过BeanPostProcessor)

AOP的核心原理简介

这里就不扯什么是AOP以及AspectJ的历史了,在一般大众的认知中,AOP就是在方法的前后做一些事情(Spring默认也就是方法级别的AOP)。

接下来,让我们一步步实现AOP。

这是一个很普通的方法UserService#sayHello。

图片名称

如何在sayHello前后做一些事情呢?最粗暴的做法是:

图片名称

但这并不符合开闭原则,也不通用。如果需要在OrderService#order方法前后也做一些事情,就要把代码拷贝一份。

所以,我们必须将sayHellosayHello前后要做的事情进行分离,而分离的目的是为了下次更好的相聚(组合)。

图片名称

Spring AOP的做法是,抽象出以下概念:

  • Pointcut
  • Advice
  • Advisor

Pointcut俗称切点,可以简单理解为“怎么切/切哪里”,通常是一个匹配规则。 比如AspectJ的AspectJExpressionPointcut,允许我们配置切点表达式:

execution(* com.bravo.test.service.UserService.sayHello(..))

Advice则是具体的增强逻辑,即前面说的“方法前后要做的事情”。

Advisor = Pointcut + Advice对于符合Pointcut规则的目标方法应用Advice,以求在目标方法前后做一些事情。

所以,上面拆开的sayHello和sayHello前后要做的事现在要产生联系了:

图片名称

Spring把这个匹配过程封装在了AdvisorChainFactory中,代码大致如下:

图片名称

Advisor持有Pointcut,方法匹配成功,则返回advice用于增强当前方法。

尽管在我看来这些AdvisorAdvicePointcut这些概念已经很清晰,但对于初学者来说可能仍然有点绕。

所以,下面的讲解中,我再做一步简化,只留下Advice的概念,丢弃Pointcut和Advisor(dummy-aop会保留这些概念)。

没有Pointcut怎么知道哪些方法需要增强呢?交给调用者手动组装。比如:

图片名称

讲到这,对于如何实现AOP应该有一个模糊的概念了。

接下来我们讨论最难的两个问题:

  • 如何把advice嵌入目标方法前后
  • 如何链式执行advice

所谓AOP,就是在调用目标方法前后额外执行一些逻辑。整个过程大致如下:

图片名称

Method#invoke负责执行目标方法,额外的一些事情交给Advice去做。

在这里,Spring又抽象出了一个新的概念:MethodInvocation。它把上面整个流程进行了封装。

即把“Method执行前后需要额外做一些操作”这个过程抽象成MethodInvocation(method和method之前的操作打包在一块)。

图片名称

Method是Java对方法的抽象,它包含一个方法的所有信息,如名称、参数和返回类型。 而MethodInvocation则是Spring对方法调用的抽象,更偏向动态的概念,调用proceed时会额外执行Advice。

MethodInvocation最重要的实现类是ReflectiveMethodInvocation,其主要成员变量包括:

  • Method
  • MethodInterceptorChain
图片名称

想要使用MethodInvocation调用方法,则必然要先经过一串Interceptor。

MethodInterceptor是AOP联盟的,Advice是Spring自己定义的。Spring底层把Advice适配成MethodInterceptor。 MethodInterceptor比Advice更加见名知意,也和MethodInvocation更搭。

图片名称

接着,我们使用JDK动态代理为userService生成代理对象。代理对象会将一次方法调用委托到目标对象,但在此之前会执行advice:

图片名称

那么,如何产生链式调用呢?

图片名称

MethodInvocation就是Filter模式中的FilterChain,持有Interceptor并且负责推进下一个Interceptor。

图片名称

建议先看简化版的AOP代码:设计模式那些事儿

图片名称

搞懂以后再看dummy-aop 的代码。

推荐