Spring AOP简介一
AOP 已经成为企业应用开发重要的手段, 而 Spring AOP 无疑是其主流, 本文将从 AOP alliance 开始对 spring aop 做一个简单的概括, 同时也会对其中的重要细节做一些说明. 本文将延续 spring1.0 风格.
下图显示了Spring AOP和AOP alliance一些接口和类关系:
从图中可以看出, aop alliance 定义了 Advice, JointPoint 两个核心 interface, Pointcut
接口是在 spring 中定义的, 最常用的 MethodInterceptor 是 Advice 的 sub interface,
下面简单列一下 aop alliance 各核心接口及其作用
1. Advice : Tag interface for Advice. Implementations can be any type of advice, such as Interceptors.
2. Interceptor : This interface represents a generic interceptor.
A generic interceptor can intercept runtime events that occur within a base program.
Those events are materialized by (reified in) joinpoints.
Runtime joinpoints can be invocations, field access, exceptions...
Intercetpor 的三个 sub interface :
3. ConstructorInterceptor, FieldInterceptor, MethodInterceptor
从名字看已经很直白, 分别用于拦截对象构建, 属性访问, 方法调用,
对应的 JoinPoint 分别为 ConstructorInvocation, FieldAccess, MethodInvocation,
其中 spring 只支持 MethodInterceptor
4. Joinpoint : This interface represents a generic runtime joinpoint (in the AOP terminology).
A runtime joinpoint is an event that occurs on a static joinpoint
(i.e. a location in a the program). For instance, an invocation is the
runtime joinpoint on a method (static joinpoint).
The static part of a given joinpoint can be generically retrieved using the getStaticPart() method.
In the context of an interception framework, a runtime joinpoint is
then the reification of an access to an accessible object (a method, a
constructor, a field), i.e. the static part of the joinpoint. It is
passed to the interceptors that are installed on the static joinpoint.
Joinpoint 的三个 sub interface :
5. ConstructorInvocation, FieldAccess, MethodInvocation
也很直白, 同时对应 3 可以知道, spring 只支持 MethodInvocation
aop alliance 定义的接口应该被 aop container 实现, 比如 nanning aop, spring 等
再来看 spring 对 aop alliance 的实现和增强
1. Pointcut : Core Spring pointcut abstraction.
A pointcut is composed of a ClassFilter and a MethodMatcher.
Both these basic terms and a Pointcut itself can be combined to
build up combinations (e.g. through
org.springframework.aop.support.ComposablePointcut).
Pointcut 就是传说中的 "切入点" 了, spring 的主要实现包括 StaticMethodMatcherPointcut, DynamicMethodMatcherPointcut 以及 AspectJExpressionPointcut, DynamicMethodMatcherPointcut 因为性能问题使用率不高, AspectJExpressionPointcut 是 spring2.0 才引入的, 本文不再讲述,StaticMethodMatcherPointcut 中最常用的是基于正则表达式的实现 JdkRegexpMethodPointcut 和 Perl5RegexpMethodPointcut, 前者需要 JDK1.4+ 的环境, 后者需要 Jakarta ORO lib, 一个典型的配置
- <bean id= "settersPointcut" class = "org.springframework.aop.support.Perl5RegexpMethodPointcut" >
- <property name="patterns" >
- <list>
- <value>.*set.*</value>
- </list>
- </property>
- </bean>
上述配置匹配所有的 set 方法, 而
- <bean id= "settersPointcut" class = "org.springframework.aop.support.Perl5RegexpMethodPointcut" >
- <property name="patterns" >
- <list>
- <value>com.mycom.Foo.*set.*</value>
- </list>
- </property>
- </bean>
仅匹配 com.mycom.Foo 中的所有 set 方法
2. MethodInterceptor, BeforeAdvice, ThrowsAdvice, AfterReturningAdvice, IntroductionAdvice
MethodInterceptor : 最常用的 advice, 大家最熟悉的 TransactionInterceptor 就是其实现,
需要注意的地方是实现中一定要调用 invocation.proceed() 使 interceptor chain 继续执行
BeforeAdvice : 不再赘述了, reference 中写的很明白
ThrowsAdvice : 我认为非常有用的 advice, 强大的地方在于它只是一个标志接口, 实现可以选择需要拦截的
Throwable 类型, 当然也可以获取 MethodInvocation 中的一切信息, ThrowsAdvice 可以实现非常完善的
exception handle 机制
AfterReturningAdvice : 使用率不高, 不再赘述
IntroductionAdvice : 另一个非常强大的 advice, 限于文章篇幅, 也不再细数了, 使用它可以实现一些很眩的功能
3. Advisor : Base interface holding AOP advice (action to take at a joinpoint)
and a filter determining the applicability of the advice
(such as a pointcut).
This interface is not for use by Spring users, but to allow for commonality in support for different types of advice.
Spring AOP is based around around advice delivered via method interception,
compliant with the AOP Alliance interception API.
The Advisor interface allows support for different types of advice,
such as before and after advice, which need not be implemented using interception.
Advisor hold 了 Advice, 并且决定 advice 是否执行, 比如最常用的 PointcutAdvisor, 它包含了 getPointcut() 方法,
通俗的讲, Advice 是 "做什么 ", Pointcut 是 "何时做", 而 PointcutAdvisor 是 "何时做, 做什么",
配合下文提到的 DefaultAdvisorAutoProxyCreator, 就可以对容器中的 bean 实现灵活的拦截
使用 Spring AOP
spring 提供了多种不同的方案实现对 bean 的 aop proxy, 包括 ProxyFactoryBean, 便利的 TransactionProxyFactoryBean 以及 AutoProxyCreator 等,
下图是 proxy class diagram 以供参考
这里重点说一下最常用的 ProxyFactoryBean, TransactionProxyFactoryBean, BeanNameAutoProxyCreator, DefaultAdvisorAutoProxyCreator 的联系和区别
1. ProxyFactoryBean : 使用率最高的 proxy 方式, 它通过配置 interceptorNames
属性决定加入哪些 advisor (method interceptor 将会被自动包装成 advisor, 下文将描述这个细节),
注意是 "interceptorNames" 而不是 "interceptors", 原因是
ProxyFactoryBean 可能返回非 singleton 的 proxy 实例, 而 advisior 可能也是非 singleton
的, 因此不能通过 interceptor reference 来注入
2. TransactionProxyFactoryBean : 特定用于 transaction proxy, 注意其 super class 是 AbstractSingletonProxyFactoryBean, 也就是说, TransactionProxyFactoryBean 永远无法返回非 singleton 的 proxy 实例 !!!
如果你需要非 singleton 的 proxy 实例, 请考虑使用 ProxyFactoryBean.
3. BeanNameAutoProxyCreator : 故名思义, 根据 bean name 进行 auto proxy,
bean name 的 match 规则参见 org.springframework.util.PatternMatchUtils
4. DefaultAdvisorAutoProxyCreator : 更强大的 auto proxy creator, 强大之处在于它会 cahce 容器中所有注册的 advisor, 然后搜索容器中所有的 bean ,
如果某个 bean 满足 advisor 中的 Pointcut, 那么将会被自动代理, 与 BeanNameAutoProxyCreator 相比, 省去了配置 beanNames 的工作,
eg :
- <bean class = "org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
- <bean id="defaultPointcutAdvisor" class = "org.springframework.aop.support.DefaultPointcutAdvisor" scope= "prototype" >
- <property name="pointcut" ref= "fooPointcut" />
- <property name="advice" ref= "fooAdvice" />
- </bean>
- <bean id="fooAdvice" class = "com.mycompany.FooAdvice" scope= "prototype" />
- <bean id="fooPointcut" class = "org.springframework.aop.support.JdkRegexpMethodPointcut" scope= "prototype" >
- <property name="patterns" >
- <list>
- <value>com.mycompany.FooService.*</value>
- </list>
- </property>
- </bean>
以上配置将自动代理容器中所有 com.mycompany.FooService 类型的 bean, 并拦截其所有方法
深度话题
1. MethodInterceptor 如何被包装成 Advisor ?
在 AdvisorAdapterRegistry#wrap(Object) 方法中实现, code as below
- public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
- if (adviceObject instanceof Advisor) {
- return (Advisor) adviceObject;
- }
- if (!(adviceObject instanceof Advice)) {
- hrow new UnknownAdviceTypeException(adviceObject);
- }
- Advice advice = (Advice) adviceObject;
- if (advice instanceof MethodInterceptor) {
- // So well-known it doesn't even need an adapter.
- return new DefaultPointcutAdvisor(advice);
- }
- for ( int i = 0 ; i < this .adapters.size(); i++) {
- / Check that it is supported.
- AdvisorAdapter adapter = (AdvisorAdapter) this .adapters.get(i);
- if (adapter.supportsAdvice(advice)) {
- return new DefaultPointcutAdvisor(advice);
- }
- }
- throw new UnknownAdviceTypeException(advice);
- }
从代码可以看到, 如果 adviceObject(也就是 interceptorNames 对应的 bean) 不是 advisor
而是 MethodInterceptor 或 Advice, 那么 spring 将其包装成 DefaultPointcutAdvisor,
而 DefaultPointcutAdvisor 中定义的 Pointcut 是 TruePointcut :
- class TruePointcut implements Pointcut, Serializable {
- public static final TruePointcut INSTANCE = new TruePointcut();
- /**
- * Enforce Singleton pattern.
- */
- private TruePointcut() {
- }
- public ClassFilter getClassFilter() {
- return ClassFilter.TRUE;
- }
- public MethodMatcher getMethodMatcher() {
- return MethodMatcher.TRUE;
- }
- /**
- * Required to support serialization. Replaces with canonical
- * instance on deserialization, protecting Singleton pattern.
- * Alternative to overriding <code>equals()</code>.
- */
- private Object readResolve() {
- return INSTANCE;
- }
- public String toString() {
- return "Pointcut.TRUE" ;
- }
- }
也就是说, MethodInterceptor 和 Advice 被包装成的 Advisor 将会匹配容器中的所有 bean,
所以, 永远不要在 DefaultAdvisorAutoProxyCreator 的
interceptorNames 中引用一个 Advice, 那将会使容器中所有的 bean 被自动代理!!! 此时应该考虑使用
BeanNameAutoProxyCreator 或者将 Advice 用特定的 Pointcut 包装成 advisor 后注入
DefaultAdvisorAutoProxyCreator.