一、前言
面向切面的编程(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 环绕通知
环绕通知可在目标方法执行前后织入通知。接口实现如下:
我们只需要实现 invoke(MethodInvocation)
方法即可。
注意,需要在方法内部调用
Joinpoint#proceed()
调用目标对象的方法。
继承关系图如下:
Advice:
通知标志接口。任意类型的通知都可以实现。Interceptor:
通用拦截器标志接口。
自定义环绕通知
/**
* #2 定义通知-环绕通知
*/
class MyAroundAdvice implements MethodInterceptor {
@Nullable
@Override
public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
System.out.println("(环绕通知1) 方法调用前触发,当前方法名称: " + invocation.getMethod().getName());
// 重要,不能忘记
Object result = invocation.proceed();
System.out.println("(环绕通知1) 获取方法调用返回值: " + result);
return result;
}
}
3.2 前置通知
在目标方法执行之前调用。接口定义如下:
继承关系如下图:BeforeAdvice
是一个标志接口。
自定义前置通知:
/**
* #2 定义通知-前置通知
*/
class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("(前置通知) 方法调用之前回调此方法");
}
}
3.3 后置通知
在目标方法正常返回之前回调。若抛出异常,则不会回调。
这些后置通知只能查看返回值,但不能改变。
接口定义如下:
继承关系如下图:AfterAdvice
也是一个标记接口。
自定义后置通知:
/**
* #2 定义通知-后置通知
*/
class MyAfterReturnAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue,
Method method,
Object[] args, Object target) throws Throwable {
System.out.println("(后置通知) 方法调用结束后触发通知,当前方法名称: " + method.getName());
}
}
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)
3.5 引入通知
引入通知的主要作用是可以让生成的代理类实现额外的接口。底层是基于 MethoInterceptord
实现。简单来说,就是当方法调用时,判断当前接口是否属于目标对象所拥有的方法,是则直接调用目标对象方法,否则调用委托类的方法。
核心代码如下:
继承体系如下
3.6 Advice 小结
- 分类: 通知按实现接口分类
- 未实现
MethodInterceptor
接口- 前置通知
- 后置通知
- 异常通知
- 实现
MethodInterceptor
接口- 环绕通知
- 引入通知
- 未实现
MethodInterceptor
是 前置/后置/异常 等通知的基础实现,它们的底层也是使用 MethodInterceptor 实现的。Spring-AOP 提供适配器完成适配。- 比如
org.springframework.aop.framework.adapter.AfterReturningAdviceAdapter
完成AfterReturningAdvice
和MethodInterceptor
的适配。
- 比如
- 不同的
MethodInterceptor
封装了MethodInvocation#proceed()
不同的调用时机。
后置通知适配者
注意其中的 invoke(MethodInvocation)
方法,里面最重要的是 mi.proceed()
何时执行。需要根据当前通知类型确定。
四、Advisor
一个 Advisor
由一个切点和一个通知组成。它支持不同类型的通知(Advice),比如前置通知、后置通知等。这些并不需要使用拦截器链实现。
没有指定切点的通知是毫无意义的。
五、拦截器回调
拦截器回调的执行顺序 Spring 设计得很巧妙。
拦截器 MethodInterceptor
和 Advice
通知是独立的。而 Advice
底层是通过适配器适配成 MethodInterceptor
,成为拦截器链中的一员,从而实现 AOP 切面的功能。MethodInterceptor#invoke()
方法控制拦截器相对原始目标对象方法执行的顺序。比如前置通知,需要在目标方法执行之前被回调,后置通知则需要在目标方法执行之后被回调。
拦截器回调步骤为:
- 根据目标对象和方法获取合适的拦截器链。
- 对 CGLib 代理而言,创建
CglibMethodInvocation
并执行proceed()
方法调用。 - 判断拦截器链集合中最后一个拦截器是否已被回调,如果是,则通过反射执行目标对象的方法,结束递归调用。否则执行下一步。
- 获取下一个将要执行拦截器链,如果为动态则动态匹配,静态直接调用
MethodInterceptor#invoke(this)
方法。(通知也是被适配成 MethodInterceptor 实现对象,里面封装了相应的调用顺序)。 - 通过调用
MethodInvocation#proceed()
继续回到ReflectiveMethodInvocation#proceed()
方法中,回到步骤3。
六、Spring AOP 动态代理的创建
Spring AOP 动态代理创建有三种方式,分别是
ProxyFactory
proxyFactoryBean
@EnableAspectJAutoProxy
ProxyFactory
、ProxyFactoryBean
这两种创建 AOP 方式由 spring-aop 提供,而注解是 spring-context 所提供,目的是让 spring-aop 与 spring 方便进行整合。
6.1 ProxyFactory
继承关系如下
ProxyCreatorSupport:
创建代理对象工厂,通过该工厂可获取代理对象。6.2 ProxyFactoryBean
ProxyFactoryBean
基于 Spring BeanFactory 中的 Bean 构建 AOP 代理。内部通过列表interceptorNames
标识MethodInterceptor
和Advisor
。
他做的事情其实是和ProxyFactory
是同性质的,只不过是借助 Spring 的FactoryBean
实现,这样就可以和 Spring 进行整合。6.3 @EnableAspectJAutoProxy
@EnableAspectJAutoProxy
属于Enable
模式之一。开启则表示支持使用AspectJ
相关注解标记组件(components)。与在 XML 配置<aop:aspectj-autoproxy>
效果一样。
当注解开启,我们就可以使用以下编程方式编写自定义的 AOP。
更多内容详见 Spring 官方文档之 AOP
而使用@EnableXX
模式就离不开@Import
注解,它是 Spring 提供的功能强大的扩展之一。我们看一下该注解源码是长什么样子的:
可以看到通过@Import
引入了一个新的类AspectJAutoProxyRegistrar
。AspectJAutoProxyRegistrar
主要是构建
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
的 BeanDefinition 信息并注册到 BeanFactory 对象工厂中。AnnotationAwareAspectJAutoProxyCreator
AspectJAwareAdvisorAutoProxyCreator
的子类,它处理当前应用程序上下文中所有的AspectJ
注解、Advisors。
继承关系如下:
可以看到,AnnotationAwareAspectJAutoProxyCreator
的继承体系十分复杂,不过我们可以适当拆解。BeanPostProcessor:
后置处理器。Spring BeanFactory 每当创建一个对象时都需要经过后置处理器处理。这是 Spring 提供的扩展接口之一。Spring AOP 利用这个扩展在对象创建的时候判断当前对象是否有资格被代理,如果有则返回代理对象。Aware:
意识接口,spring 扩展接口之一。BeanFactoryAware 可以获取 BeanFactory 对象工厂。BeanClassLoaderAware 可以获取 Bean 加载器。Ordered:
排序接口。表明 AOP 可自定义切面顺序。ProxyConfig:
创建代理工厂所需要的配置。比如选择CGLib 还是 JDK 创建代理呢?
总述 Spring AOP 是如何自动生成代理对象的。
- 开启自动代理注解。
@EnableAspectJAutoProxy
- 该注解会导致 BeanFactory 自动装配
AnnotationAwareAspectJAutoProxyCreator
对象。 AnnotationAwareAspectJAutoProxyCreator
属于 BeanPostProcessor 后置处理器。在 BeanFactory 创建后置处理器时会判断当前类是否有资格被代理。- 若 @Aspect 注解切面配置类未被解析则解析解析动作。
- 解析步骤在
BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()
- 解析步骤在
- 根据当前类获取适配的拦截器链并进行回调
- 若 @Aspect 注解切面配置类未被解析则解析解析动作。