前言
之前用十几篇文章介绍了 Spring IoC的源码,作为与 IoC 齐名的 AOP 自然也不能错过。同样的,接下去将会通过几篇文章来解析 Spring AOP 的源码。
如何将 Spring 源码导入 IDEA,请参考:Spring IoC源码学习:总览
注:本文的内容以 AspectJ 来进行介绍。
关于AOP
百度百科:AOP 即 Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
我们有两个接口,一个用于进行加法计算,一个用于进行减法计算,为了避免计算出现问题,我们需要对每次接口调用的入参进行日志记录,于是我们有了以下的第一版实现。
看起来还不错,简单明了。但是这个方案有个问题,就是后续每次新增一个接口,就需要拷贝一次 “记录入参” 的代码。对于一个 “懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个方法,于是我们有了以下第二版实现。这里有点切面的味道了。
这个方案看起来更好了,但是同还是存在问题,虽然不用每次都拷贝代码了,但是,每个接口总得要调用这个方法吧,有办法让 “调用” 也省掉吗。我们设想一下,我们可以通过策略识别出所有要加入日志记录的接口,然后在接口调用时,将日志记录注入到接口调用的地方(切点),这就是 AOP 的核心思想。按这个思想,我们有了第三版的实现。
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。 红框处,就是面向切面编程的思想。
通过上面的例子,大家应该对 AOP 有了初步的认识,下面介绍下 AOP 涉及的相关概念。
Joinpoint(连接点):在系统运行之前,AOP 的功能模块都需要织入到具体的功能模块中。要进行这种织入过程,我们需要知道在系统的哪些执行点上进行织入过程,这些将要在其之上进行织入操作的系统执行点就称之为 Joinpoint,最常见的 Joinpoint 就是方法调用。
Pointcut(切点):用于指定一组 Joinpoint,代表要在这一组Joinpoint 中织入我们的逻辑,它定义了相应 Advice 将要发生的地方。通常使用正则表达式来表示。对于上面的例子,Pointcut 就是表示 “所有要加入日志记录的接口” 的一个 “表达式”。例如:“execution(* com.joonwhee.open.demo.service..*.*(..))”。
Advice(通知/增强):Advice 定义了将会织入到 Joinpoint 的具体逻辑,通过 @Before、@After、@Around 来区别在 JointPoint 之前、之后还是环绕执行的代码。
Aspect(切面):Aspect 是对系统中的横切关注点逻辑进行模块化封装的 AOP 概念实体。类似于 Java 中的类声明,在 Aspect 中可以包含多个 Pointcut 以及相关的 Advice 定义。
Weaving(织入):织入指的是将 Advice 连接到 Pointcut 指定的 Joinpoint 处的过程,也称为:将 Advice 织入到 Pointcut 指定的 Joinpoint 处。
Target(目标对象):符合 Pointcut 所指定的条件,被织入 Advice 的对象。
对于上面的例子来说:
· “加法接口” 或 “减法接口” 每次被调用时所处的程序执行点都是一个 Jointpoint
· Pointcut 就是用于指定 “加法接口” 和 “减法接口” 的一个 “表达式”,当然这个表达式还可以指定很多其他的接口,表达式常见的格式为:“execution(* com.joonwhee.open.demo.service..*.*(..))”
· Aspect 是定义 Advice、Pointcut 的地方
· Advice 就是我们要在 “加法接口” 和 “减法接口” 织入的日志记录逻辑
· Weaving 就是指将日记记录逻辑加到 “加法接口” 和 “减法接口” 的过程
· Target 就是定义了 “加法接口” 和 “减法接口” 的对象实例
整体如下图所示:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * @author joonwhee * @date 2019/3/3 */ @Component @Aspect public class AopAspect { private static final Logger LOGGER = LoggerFactory.getLogger(AopAspect.class); @Pointcut("execution(* com.joonwhee.open.demo.service..*.*(..))") public void pointcut() { } @Before("pointcut()") public void before() { LOGGER.info("before advice"); } @After("pointcut()") public void after() { LOGGER.info("after advice"); } @Around("pointcut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException { System.out.println("around advice start"); try { Object result = proceedingJoinPoint.proceed(); System.out.println("result: " + result); System.out.println("around advice end"); return result; } catch (Throwable throwable) { throwable.printStackTrace(); return null; } } }
Spring AOP 底层实现机制目前有两种:JDK 动态代理、CGLIB 动态字节码生成。在阅读源码前对这两种机制的使用有个认识,有利于更好的理解源码。
JDK 动态代理
public class MyInvocationHandler implements InvocationHandler { private Object origin; public MyInvocationHandler(Object origin) { this.origin = origin; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke start"); Object result = method.invoke(origin, args); System.out.println("invoke end"); return result; } } public class JdkProxyTest { public static void main(String[] args) { UserService proxy = (UserService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{UserService.class}, new MyInvocationHandler(new UserServiceImpl())); proxy.doSomething(); } }
CGLIB 代理
public class CglibInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("intercept start"); Object result = proxy.invokeSuper(obj, args); System.out.println("intercept end"); return result; } } public class CglibProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(CglibObject.class); enhancer.setCallback(new CglibInterceptor()); CglibObject proxy = (CglibObject) enhancer.create(); proxy.doSomething(); } }