SpringAOP核心组件剖析
本文是SpringAop系列文章第一篇,本文将展示该系列文章的大致脉络以及SpringAop的大致介绍。本系列文章默认读者使用过SpringAop以及了解简单的原理。
aopalliance (aop联盟)
AOP联盟项目是几个对 AOP 和 Java感兴趣的软件工程人员之间的联合开源项目。AOP联盟旨在促进和标准化 AOP的使用,以增强现有的中间件环境(例如 J2EE)或开发环境(例如 JBuilder、Eclipse)。 AOP联盟还旨在确保 Java/J2EE AOP 实现之间的互操作性,以构建更大的 AOP 社区。
aopalliance.jar包是AOP联盟规范了一套用于规范AOP实现的底层API,通过这些统一的底层API,可以使得各个AOP实现及工具产品之间实现相互移植。这些API主要以标准接口的形式提供,是AOP编程思想所要解决的横切交叉关注点问题各部件的最高抽象。这个思路和JDBC驱动是一样一样的。

在IDE中找到spring-aop依赖包,如上图所示可以很清楚的看到spring将包划分成了aopalliance和springframework包。其中springframework包是spring对aop切面编程思想的实现,而aopalliance包则是直接将aopalliance.jar中的接口以内嵌的形式放到了sping-aop中。SpringAop框架也是直接以AOP联盟中的API为基础所构建。
如下图所示是aoplliance包中的接口:

org.aopalliance.aop包
Advice:建议通知的标记接口。实现可以是任意类型,比如下面的InterceptorAspectException:所有的AOP框架产生异常的父类。它是个RuntimeException
org.aopalliance.intercept包
Interceptor:它继承自Advice,它通过拦截器得方式实现通知的效果(也属于标记接口)MethodInterceptor:具体的接口。拦截方法 (Spring提供了非常多的具体实现类ConstructorInterceptor:具体接口。拦截构造器 (Spring并没有提供实现类)Joinpoint:AOP运行时的连接点(顶层接口)Invocation:继承自Joinpoint。 表示执行,提供了Object[] getArguments()来获取执行所需的参数MethodInvocation:(和MethodInterceptor对应,它的invoke方法入参就是它)表示一个和方法有关的执行器。
这就是AOP联盟为我们提供的所有的类,它里面全部是接口(那个异常类除外),相当于它定义了一套AOP的标准。SpringAop实际上是根据这些接口自己实现了一套AOP.
SpringAop与AspectJ
AOP(Aspect-Oriented-Programming面向方面编程),可以说是OOP(Object-Oriented Programing面向对象编程)的补充和完善.
实现AOP的技术,主要分为两大类:一是采用动态代理技术(典型代表为SpringAop),以及利用截取消息的方式(典型代表为AspectJ-AOP),对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AspectJ可以成为是AOP的鼻祖,规范的制定者。Spring AOP与ApectJ的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,SpringAop并不尝试提供完整的AOP功能(即使它完全可以实现),SpringAop 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题.
AspectJ
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器(ajc编译器)用来生成遵守Java字节编码规范的Class文件(Spring一般只用到它的注解,其余都是Spring自己实现的)。
使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类:

SpringAop
SpringAop是基于动态代理及AOP联盟API实现的一套类似AspectJ框架中的AOP实现,不依赖AspectJ的任何类。
如果你比较仔细的话可以发现@Aspect、@Before这些注解其实是属于AspectJ框架中的aspectjweaver.jar,而且我们在使用SpringAop时也常常通过这些注解使用AOP。这是因为随着这几年基于注解方式的流行,在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解,并且引入了aspectjweaver这个jar包,目的是使用这个包中的注解。请注意,Spring只是使用了AspectJ 5 中的注解,并没有使用 AspectJ的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ.
Tips :
AspectJ与SpringAop根本区别是 :AspectJ使用特殊编译器生成class;SpringAop使用动态代理实现aop.
SpringAop核心概念介绍
SpringAop框架整体是一个范围很大的框架,可以根据它的运行原理将其剖解为一个个的 ”组件“。这些组件也分别对应了SpringAop中的核心概念,只有搞懂这些概念以及之间的关系才能对SpringAop了如指掌。如下图所示是SpringAop中的核心概念:
| 组件名称 | 介绍 |
|---|---|
连接点 JoinPoint | 程序执行过程中的一个点,例如方法的执行或异常的处理。在Spring AOP 中,一个连接点总是代表一个方法的执行。 |
切入点 Pointcut | 通过切入点表达式匹配连接点。由切入点表达式匹配的连接点概念是 SpringAop的核心,Spring默认使用 AspectJ切入点表达式语言。 |
建议 Advice | 在特定连接点采取的行动。不同类型的建议包括“周围”、“之前”和“之后”建议。 (通知类型将在后面讨论)许多 AOP框架,包括 Spring,将通知建模为拦截器,并在连接点周围维护一个拦截器链。 |
切面Advisor | Advisor是SpringAop框架中的“切面”概念。用于定义对连接点横切面的拦截与执行逻辑, 将那些影响了多个类的公共行为封装到一个可重用模块。 |
代理 Proxy | Spring启动期间会根据切入点上的表达式以及Advice为相应的Bean生成代理对象。这样在运行期间就可以对切入点感兴趣的连接点执行相应的Advice。 |
桥梁 Advised | Advised是代理对象(包含Pointcut、Advice(ADvisor))与目标对象之间的绑定的桥梁。 |
Joinpoint连接点
连接点是SpringAop中的一个抽象概念,其实一个连接点就代表一个方法。SpringAop本质上就是通过拦截连接点(方法)实现对连接点(方法)的增强,也是说SpringAop最小的拦截粒度就是方法级别。
aop联盟API中定义了连接点的抽象: Joinpoint接口。如下图UML所示是SpringAOP对JoinPoint接口的扩展。

其中Joinpoint、Invocation、MethodInvocation、ConstructorInvocation接口都属于AOP联盟API包中的接口,这些接口是为了获取连接点(方法)的基本信息,比如Method、args、Constructor等。ProxyMethodInvocation是SpringAop对aop联盟连接点接口的扩展,增加了获取代理对象的方法。
Pointcut切入点
切入点是SpringAop中特有的概念,aop联盟中并没有定义相关接口。简单来说Spring中切入点就是定义哪些类或方法的执行需要被拦截器拦截。在Spring中与切入点对应的抽象是Pointcut接口,如下所示 :
//切入点用于将通知定位到特定的类和方法
public interface Pointcut {
//获取当前切入点的类匹配器
ClassFilter getClassFilter();
//获取当前切入点的方法匹配器
MethodMatcher getMethodMatcher();
//定义了一个的切点实例,默认对类和方法都返回true,代表可以匹配到
Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ClassFilter {
//判断当前clazz是否满足当前切入点
boolean matches(Class<?> clazz);
//默认返回true的实例
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
public interface MethodMatcher {
//判断某个类的某个方法是否符合切入点表达式
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}
match(Method, Class)方法用于测试此切入点是否曾与目标类上的给定方法匹配。可以在应用启动期间创建AOP代理时执行此评估,以避免运行时对每个方法调用都进行matches判断。
如果match(Method, Class)方法对于给定的方法返回true,并且 MethodMatcher#isRuntime()方法返回 true,则在运行期每次调用方法时都会调用matches(Method m, Class<?> targetClass, Object... args)这个匹配方法。这可以让切入点在目标通知开始之前立即查看传递给方法调用的参数。
Spring中大多数MethodMatcher实现都是静态的,这意味着它们的isRuntime()方法返回 false。在这种情况下,永远不会调用三参数匹配方法。尽量使切入点静态化,这样可以在启动期间创建AOP代理时缓存切入点评估的结果,运行时可以提高一点点性能。
Spring中主要有以下几个切入点的实现类,介绍如下:
NameMatchMethodPointcut:通过方法名进行精确匹配的ControlFlowPointcut:根据在当前线程的堆栈信息中的方法名来决定是否切入某个方法(效率较低)ComposablePointcut:组合模式的Pointcut, 主要用来支持切入点表达式中的&&、||、!组合符号JdkRegexpMethodPointcut:通过 正则表达式来匹配方法AspectJExpressionPointcut:通过AspectJ包中的组件进行方法的匹配(切点表达式)TransactionAttributeSourcePointcut:通过TransactionAttributeSource在 类的方法上提取事务注解的属性@Transactional来判断是否匹配, 提取到则说明匹配, 提取不到则说明匹配不成功AnnotationJCacheOperationSource:支持JSR107的cache相关注解的支持
Advice接口

上面讲了切入点是定义需要被拦截的类和方法,有了切入点还需要拦截器对被拦截到的连接点进行处理。Advice通知其实就是充当一个拦截器的作用,用于对拦截到的连接点进行增强。
如上图所示是Spring对建议通知的抽象,这些接口分别对应连接点的不同的生命周期。使用过SpringAop的框架都知道Advice有方法前通知、环绕通知、方法执行后通知、异常通知。其中MethodInterceptor(环绕通知)是aop联盟中定义的标准接口,其他通知都是Spring自己搞出的一些概念,aop联盟中并不包含. 虽然Spring定义了额外的通知,但是这些通知底层最终都会转为MethodInterceptor,后面会详细剖析。
Advisor切面
当程序员第一次接触到SpringAop都会听到过这样一个概念 : 面向切面编程,为什么是面向“切面”呢? 其实SpringAop并非第一个AOP框架,SpringAop在一定程度上吸取了AspectJ这个AOP框架的精华。AspectJ这个框架提出了面向切面的概念,将那些影响了多个类的公共行为封装到一个可重用模块就可以称为是一个Aspect(切面),本质是用于定义横切面的执行逻辑。
SpringAop作为AspectJ的后起之秀,也引用了面向Aspect(切面)编程的概念,这也是被大家所熟知的。在AspectJ框架中一个切面就对应一个Aspect(@Aspect), 但是在SpringAop中并没有Aspect的概念。你可能会说: 不对呀,我一般使用都是直接使用@Aspect注解修饰一个类代表这个类就是切面的,而且是可以被Spring拦截到的。需要注意的是SpringAop是Spring使用代理技术实现了一套类似AspectJ框架中的AOP的实现, 不依赖AspectJ框架中的任何类。但是随着这几年基于注解方式的流行,SpringAop才引入了AspectJ的aspectjweaver这个jar包,目的是使用AspectJ中的注解,如@Aspect、@Pointcut、@Before等。但是光有注解是不够的,还需要对注解进行处理,所以spring-aop中新增了org.springframework.aop.aspectj包,其包下面的所有类都是专门为了使用@Aspect的方式去服务的,毕竟AOP功能是Spring实现的,而不是依赖于AspectJ这个组件.
SpringAop使用Advisor(顾问)的概念代表切面, AspectJ框架中没没有这个概念。SpringAop中的Advisor相当于AspectJ中的Aspect(@Aspect), 对于SpringAop来说Advisor就是切面、对于AspectJ来说Aspect就是切面。 不同的是使用AspectJ的@Aspect定义一个切面可以在切面中定义多个Advice, 而一个Advisor只能定义一个Advice.
SpringAop抽象了Advisor接口作为一个切面,如下图所示 :
//切面接口 : 这个接口不是供 Spring 用户使用的,而是为了允许支持不同类型的通知的通用性。
public interface Advisor {
//定义一个空的通知
Advice EMPTY_ADVICE = new Advice() {};
//获取当前切面的通知
Advice getAdvice();
//当前切面是否与特定的实例相关联
boolean isPerInstance();
}

如上图所示是Advisor接口的UML图,可以看出SpringAop中存在三个类型的Advisor :
PointcutAdvisor类型IntroductionAdvisor类型,该类型不常用但是很有用,此处先MARK一下,后面会另起篇章分析。- 直接实现
Advisor接口的PrototypePlaceholderAdvisor. 该类主要在代理对象需要设置为Prototype类型时使用。
SpringAop默认提供了非常多的实现,使用频率最高的是DefaultPointcutAdvisor这个实现。该实现用于将建议与切入点绑定到同一个切面中, 后面分析原理时会大量用到这个实现类,此处先mark一下。
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
private Pointcut pointcut = Pointcut.TRUE;
public DefaultPointcutAdvisor() {
}
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
public void setPointcut(@Nullable Pointcut pointcut) {
this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
.......
}
Proxy代理
SpringAop底层使用代理方式实现对连接点的增强。目前可以使用动态代理和CGLIB两种代理方式。此处先mark一下,后面剖析原理时再进行详解。
Advised 代理与切面之间的桥梁
除了上面提到的Advice、Advisor,SpringAop还抽象了一个Advised概念。若不详细看接口注解和官方文档那真的是傻傻分不清楚啊。
如下图所示是SpringAop抽象的Advised接口:
//接口包括:Advice、Advisor和代理接口
//从Spring获得的任何 AOP代理都可以转换到这个接口
public interface Advised extends TargetClassAware {
//是否冻结配置,冻结后不能对Advice、Advisor进行更改
boolean isFrozen();
//是否基于类进行代理(CGLIB)
boolean isProxyTargetClass();
//返回被代理的接口
Class<?>[] getProxiedInterfaces();
//intf是否是代理对象的接口
boolean isInterfaceProxied(Class<?> intf);
//设置被代理类
void setTargetSource(TargetSource targetSource);
TargetSource getTargetSource();
//设置是否暴露代理对象,这里暴露的意思指的是将代理对象放入ThreadLocal中
//以便可以通过AopContext.currentProxy()随时取到代理对象
void setExposeProxy(boolean exposeProxy);
boolean isExposeProxy();
void setPreFiltered(boolean preFiltered);
boolean isPreFiltered();
//获取所有切面
Advisor[] getAdvisors();
//添加切面
void addAdvisor(Advisor advisor) throws AopConfigException;
boolean removeAdvisor(Advisor advisor);
//添加Advice
void addAdvice(Advice advice) throws AopConfigException;
.......
}
可以看出Advised接口不仅包含了Advisor(切面),还包含了代理对象相关的一些配置及元信息。从接口上的注解也可以看出该接口就是连接代理对象与Advisor(切面)的桥梁。该接口一般在SpringAop底层中用的较多,所以只要分清楚Advisor与Advice的概念即可。