本文是SpringAop
系列文章第一篇,本文将展示该系列文章的大致脉络以及SpringAop
的大致介绍。本系列文章默认读者使用过SpringAop
以及了解简单的原理。
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
:建议通知的标记接口。实现可以是任意类型,比如下面的Interceptor
AspectException
:所有的AOP框架产生异常的父类。它是个RuntimeException
org.aopalliance.intercept
包
Interceptor
:它继承自Advice
,它通过拦截器得方式实现通知的效果(也属于标记接口)MethodInterceptor
:具体的接口。拦截方法 (Spring提供了非常多的具体实现类ConstructorInterceptor
:具体接口。拦截构造器 (Spring并没有提供实现类)Joinpoint
:AOP运行时的连接点(顶层接口)Invocation
:继承自Joinpoint
。 表示执行,提供了Object[] getArguments()
来获取执行所需的参数MethodInvocation
:(和MethodInterceptor
对应,它的invoke
方法入参就是它)表示一个和方法有关的执行器。这就是AOP
联盟为我们提供的所有的类,它里面全部是接口(那个异常类除外),相当于它定义了一套AOP
的标准。SpringAop
实际上是根据这些接口自己实现了一套AOP
.
AOP
(Aspect-Oriented-Programming
面向方面编程),可以说是OOP
(Object-Oriented Programing
面向对象编程)的补充和完善.
实现AOP
的技术,主要分为两大类:一是采用动态代理技术(典型代表为SpringAop
),以及利用截取消息的方式(典型代表为AspectJ-AOP
),对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AspectJ
可以成为是AOP
的鼻祖,规范的制定者。Spring AOP
与ApectJ
的目的一致,都是为了统一处理横切业务,但与AspectJ
不同的是,SpringAop
并不尝试提供完整的AOP
功能(即使它完全可以实现),SpringAop
更注重的是与Spring IOC
容器的结合,并结合该优势来解决横切业务的问题.
AspectJ
是一个面向切面的框架,它扩展了Java语言。AspectJ
定义了AOP
语法,它有一个专门的编译器(ajc
编译器)用来生成遵守Java字节编码规范的Class文件(Spring一般只用到它的注解,其余都是Spring自己实现的)。
使用AspectJ
的acj
编译器(类似javac
)把aspect
类编译成class
字节码后,在java
目标类编译时织入,即先编译aspect
类再编译目标类:
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
中的核心概念:
组件名称 | 介绍 |
---|---|
连接点 JoinPoint |
程序执行过程中的一个点,例如方法的执行或异常的处理。在Spring AOP 中,一个连接点总是代表一个方法的执行。 |
切入点 Pointcut |
通过切入点表达式匹配连接点。由切入点表达式匹配的连接点概念是 SpringAop 的核心,Spring 默认使用 AspectJ 切入点表达式语言。 |
建议 Advice |
在特定连接点采取的行动。不同类型的建议包括“周围”、“之前”和“之后”建议。 (通知类型将在后面讨论)许多 AOP 框架,包括 Spring ,将通知建模为拦截器,并在连接点周围维护一个拦截器链。 |
切面Advisor |
Advisor 是SpringAop 框架中的“切面”概念。用于定义对连接点横切面的拦截与执行逻辑, 将那些影响了多个类的公共行为封装到一个可重用模块。 |
代理 Proxy |
Spring 启动期间会根据切入点上的表达式以及Advice 为相应的Bean生成代理对象。这样在运行期间就可以对切入点感兴趣的连接点执行相应的Advice 。 |
桥梁 Advised |
Advised 是代理对象(包含Pointcut、Advice (ADvisor ))与目标对象之间的绑定的桥梁。 |
连接点是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
的概念即可。