目录
在了解Spring事务源码时,根据调用跟踪,最后发现是TransactionAttributeSourcePointcut判断是否应用了@Transactional注解,来进行AOP。TransactionAttributeSourcePointcut是Pointcut接口的一个实现。
Pointcut
Pointcut的唯一作用就是筛选要拦截的目标方法,Pointcut只是一种筛选规则(或者叫过滤器)。
Pointcut由ClassFilter(类过滤器)和MethodMatcher(方法匹配器)两个组件组成。ClassFilter检查当前筛选规则与目标类是否匹配,MethodMatcher检查当前筛选规则与目标方法是否匹配。两个组件的共同作用,可以筛选出一个符合既定规则的方法的集合。通过Advisor(通知器)和Advice(通知)和Pointcut(切点)组合起来,就可以把指定的通知应用到指定的方法集合上。
Pointcut定义源码如下:
public interface Pointcut {
/**
* 返回当前切点持有的类过滤器,不能返回null值。
* @return
*/
ClassFilter getClassFilter();
/**
* 返回当前切点持有的方法匹配器,不能返回null值
* @return
*/
MethodMatcher getMethodMatcher();
/**
* 一个最简单切点的实现。
* 这个切点由ClassFilter.TRUE和MethodMatcher.TRUE两个组件组成
* 它匹配所有的目标类和目标方法。
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
Spring Framework实现了以下PointCut。
其中比较重要的有:
- AnnotationMatchingPointcut:
注解匹配切点。根据类上或方法上是否存在指定的注解判断切点的匹配性,如果没有显示指定注解,则匹配所有。
- DynamicMethodMatcherPointcut:
动态方法匹配器切点。它本质上是一个方法匹配器,但同时具有了切点的功能。
- ComposablePointcut:
可组合的切点。这种切点可以与或逻辑,任意组合其他的Pointcut、ClassFilter和MethodMatcher。其本质是通过ClassFilters和MethodMatchers两个工具类进行Pointcut内部组件的组合。
- JdkRegexpMethodPointcut:
JDK正则表达式切点,即使用正则表达式描述方法的拦截规则和排除规则。
- StaticMethodMatcherPointcut
静态方法匹配器切点。包括TransactionAttributeSourcePointcut(事务相关)
- AspectJExpressionPointcut:
AspectJ切点表达式切点,ExpressionPointcut的一个具体实现。顾名思义,使用AspectJ的切点表达式描述筛选规则。表达式基本语法如下(非完整语法):
execution(<方法修饰符>? <方法返回值类型> <包名>.<类名>.<方法名>(<参数类型>) [throws <异常类型>]?)
ClassFilter
ClassFilter,用于约束一个Advisor,与指定的targetClass是否匹配。只有匹配的前提下,Advisor才能使用其内部持有的Advice对targetClass进行增强。
@FunctionalInterface
public interface ClassFilter {
/** * 判断指定接口或类是否匹配*/
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
ClassFilter有4中简单方式的实现:
- TypePatternClassFilter:基于AspectJ的类型匹配实现
- AnnotationClassFilter:通过检查目标类是否存在指定的注解,决定是否匹配
- RootClassFilter:通过判断目标类是否是指定类型(或其子类型),决定是否匹配
除此之外,还有两种特殊方式的实现:
- DefaultIntroductionAdvisor:默认的引介通知器,它是一种通知器,但同时兼具了类过滤器的功能,且matches总返回true。它的作用是给所有bean追加指定接口。
- AspectJExpressionPointcut:AspectJ表达式切点(通过解析XML配置文件中的<aop:pointcut>元素生成的就是此类型的bean)。它是一种切点,但与一般的切点不同。一般的切点需要持有单独的ClassFilter和MethodMatcher。但是AspectJ表达式切点本身就兼具了这两个组件的功能。因为切点表达式,就是用来描述要代理的目标类和目标方法的。
MethodMatcher
方法匹配器。判断目标类的方法是否匹配。
两个参数的matches()被称为静态匹配,在匹配条件不是太严格时使用,可以满足大部分场景的使用,称之为静态的主要是区分为三个参数的matches()方法需要在运行时动态的对参数的类型进行匹配;两个方法的分界线就是boolean isRuntime()方法,进行匹配时先用两个参数的matches()方法进行匹配,若匹配成功,则检查boolean isRuntime()的返回值,若为true,则调用三个参数的matches()方法进行匹配(若两个参数的都匹配不中,三个参数的必定匹配不中)
public interface MethodMatcher {
/**静态检查目标类的方法是否匹配。*/
boolean matches(Method method, Class<?> targetClass);
/**
* 此MethodMatcher 是否动态的。*/
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
MethodMatcher 的几种主要实现:
- DynamicMethodMatcher:动态方法匹配器。isRuntime()一直返回true。
- StaticMethodMatcher:静态方法匹配器。isRuntime()一直返回false。
- IntersectionMethodMatcher:And 匹配器,2个匹配器都匹配才匹配。
- UnionMethodMatcher:Or匹配器,1个匹配器都匹配就匹配。
- IntroductionAwareMethodMatcher:引介匹配器。
Advisor
把Advice(通知)应用到目标类。
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
/**是与特定的实例关联还是所有实例共享。一般返回true,关联指定实例。
*/
boolean isPerInstance();
}
Advisor分两大类:IntroductionAdvisor(引介通知器)和PointcutAdvisor(切点通知器)。两类Advisor都是为了增强targetClass,但是作用不一样。IntroductionAdvisor主要为了给targetClass追加接口(或者说追加更多的方法),这种增强属于类级别的增强;而PointcutAdvisor主要为了拦截方法,这种增强属于方法级别的增强。
正是由于两类Advisor的增强级别不同,而导致了对ClassFilter的使用方式不同。IntroductionAdvisor进行类级别增强,因此只需要直接持有ClassFilter即可;而PointcutAdvisor进行方法级别增强,因此需要同时使用ClassFilter和MethodMatcher(方法匹配器)。PointcutAdvisor内部持有一个Pointcut,而Pointcut就是由ClassFilter和MethodMatcher组成的。
IntroductionAdvisor
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
PointcutAdvisor
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
AbstractPointcutAdvisor
AbstractPointcutAdvisor继承Ordered接口,增加了Advice(实现了Ordered)排序功能。
public int getOrder() {
if (this.order != null) {
return this.order;
}
Advice advice = getAdvice();
if (advice instanceof Ordered) {
return ((Ordered) advice).getOrder();
}
return Ordered.LOWEST_PRECEDENCE;
}
Advice
AOP的目的在于对目标类或目标方法的逻辑增强(如:日志逻辑、统计逻辑、访问控制逻辑等),那么Advice就代表要增强的具体逻辑。Advice接口由AOP联盟(aopalliance.org)定义,它只是一个标记接口,用来强调概念,没有定义任何功能(或者说没有定义增强方式或增强内容)。
//org.aopalliance.aop;
public interface Advice {
}
AspectJ Advice
AspectJ Advice是使用AspectJ注解的Advice。对应的Advice接口都是标记接口。
AbstractAspectJAdvice
AspectJ Advice,调用Advice方法:invokeAdviceMethod。
protected Object invokeAdviceMethod(
@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
//最终调用。object参数为代理对象。
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
Interceptor
AOP联盟在Advice的基础上扩展定义了子接口——Interceptor(拦截器)。拦截器定义了通知的增强方式,也就是通过对Joinpoint(连接点)的拦截。
public interface Interceptor extends Advice {
}
Interceptor接口也在强调概念而非功能,也是一个标记接口。 由Interceptor扩展出的ConstructorInterceptor和MethodInterceptor两个子接口,才具体定义了拦截方式。它们一个用于拦截构造方法,一个用于拦截普通方法。
public interface ConstructorInterceptor extends Interceptor {
Object construct(ConstructorInvocation invocation) throws Throwable;
}
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
spring框架并没有支持AOP联盟对构造方法的拦截,原因很简单,spring框架本身,通过BeanPostProcessor的定义,对bean的生命周期扩展已经很充分了。
MethodInterceptor只定义了增强方式,我们可以通过实现此接口,自定义具体的增强内容。当然,spring框架也提供了3种预定义的增强内容——BeforeAdvice(前置通知)、AfterAdvice(后置通知)和DynamicIntroductionAdvice(动态引介通知)。BeforeAdvice和AfterAdvice更确切地说是定义了增强内容的执行时机(方法调用之前还是之后);而DynamicIntroductionAdvice比较特殊,它可以编辑目标类要实现的接口列表。最后,spring预定义的通知还是要通过对应的适配器,适配成MethodInterceptor接口类型的对象(如:MethodBeforeAdviceInterceptor负责适配MethodBeforeAdvice)。
重点介绍几个常用拦截器:
- MethodBeforeAdviceInterceptor:
MethodBeforeAdvice(前置通知,其父接口是BeforeAdvice)接口的适配器,用于支持spring预定义的前置通知,在目标方法调用前调用MethodBeforeAdvice.before()。 - AspectJAfterAdvice :
AspectJ框架的后置通知实现,在目标方法执行结束后,return之前,调用配置指定的方法(注意:此方法调用被写在finally块中,无论如何都会得到执行)。 - AfterReturningAdviceInterceptor :
AfterReturningAdvice接口的适配器,用于支持spring预定义的后置通知,在目标方法执行结束后,return之前,调用AfterReturningAdvice.afterReturning()执行(注意:如果目标方法抛出异常,则不会执行这个方法)。 - AspectJAfterThrowingAdvice :
AspectJ框架的异常通知,当目标方法执行时产生异常的时候,指定配置指定的方法。 - AspectJAroundAdvice :
AspectJ框架的环绕通知,直接执行配置指定的方法。 - ThrowsAdviceInterceptor :
spring框架预定义的异常通知的适配器,此适配器接受任意类型的对象,但是要求对象所在类必须声明public的名称为afterThrowing,且参数个数为1个或4个,且最后一个参数为Throwable类型的方法。该适配器会保存该Throwable对象的实际类型到该方法之间的映射,当目标方法执行产生异常时,根据产生的异常类型找到对应的通知方法进行调用。 - DelegatingIntroductionInterceptor :
通过构造方法传入指定的引介对象,每当调用的目标方法是引介接口定义的方法时,都会调用该引介对象的对应方法。 - DelegatePerTargetObjectIntroductionInterceptor :
通过构造函数传入指定的引介接口和接口对应的实现类,该拦截器会为每个目标对象创建新的引介对象(通过调用实现类的默认无参构造)。当调用的方法是引介接口定义的方法时,则调用该新建的引介对象对应的方法。
AdvisorAdapter
Advisor的适配器。默认有3个实现:AfterReturningAdviceAdapter,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter。
public interface AdvisorAdapter {
//是否支持
boolean supportsAdvice(Advice advice);
//返回拦截器
MethodInterceptor getInterceptor(Advisor advisor);
}
TargetSource
TargetSource(目标源)是被代理的target(目标对象)实例的来源。TargetSource被用于获取当前MethodInvocation(方法调用)所需要的target(目标对象),这个target通过反射的方式被调用(如:method.invode(target,args))。换句话说,proxy(代理对象)代理的不是target,而是TargetSource,这点非常重要!!!
那么问题来了:为什么SpringAOP代理不直接代理target,而需要通过代理TargetSource(target的来源,其内部持有target),间接代理target呢?
通常情况下,一个proxy(代理对象)只能代理一个target,每次方法调用的目标也是唯一固定的target。但是,如果让proxy代理TargetSource,可以使得每次方法调用的target实例都不同(当然也可以相同,这取决于TargetSource实现)。这种机制使得方法调用变得灵活,可以扩展出很多高级功能,如:target pool(目标对象池)、hot swap(运行时目标对象热替换),等等。
接下来要说的另外一点,可能会颠覆你的既有认知:TargetSource组件本身与SpringIoC容器无关,换句话说,target的生命周期不一定是受spring容器管理的,我们以往的XML中的AOP配置,只是对受容器管理的bean而言的,我们当然可以手动创建一个target,同时使用Spring的AOP框架(而不使用IoC容器)。
public interface TargetClassAware {
Class<?> getTargetClass();
}
public interface TargetSource extends TargetClassAware {
/**返回targets类型。
*/
@Override
@Nullable
Class<?> getTargetClass();
/**target不变则返回true。getTarget返回同一个对象。*/
boolean isStatic();
/**Return a target instance*/
@Nullable
Object getTarget() throws Exception;
/**释放target对象(getTarget获取到的)*/
void releaseTarget(Object target) throws Exception;
}
不同的TargetSource实现类主要解决的是每次调用getTarget怎么返回的对象实例。
TargetSource包含4个简单实现和3大类实现。
四个简单实现包括:
- EmptyTargetSource:
静态目标源,当不存在target目标对象,或者甚至连targetClass目标类都不存在(或未知)时,使用此类实例。 - HotSwappableTargetSource:
动态目标源,支持热替换的目标源,支持spring应用运行时替换目标对象。 - JndiObjectTargetSource:
spring对JNDI管理bean的支持,static属性可配置。 - SingletonTargetSource:
静态目标源,单例目标源。Spring的AOP框架默认为受IoC容器管理的bean创建此目标源。换句话说,SingletonTargetSource、proxy与目标bean三者的声明周期均相同。如果bean被配置为prototype,则spring会在每次getBean时创建新的SingletonTargetSource实例。
三大类实现包括:
- AbstractBeanFactoryBasedTargetSource:
此类目标源基于IoC容器实现,也就是说target目标对象可以通过beanName从容器中获取。此类又扩展出:
(1)SimpleBeanTargetSource:简单实现,直接调用getBean从容器获取目标对象;
(2)LazyInitTargetSource:延迟初始化目标源,子类可重写postProcessTargetObject方法后置处理目标对象;
(3)AbstractPrototypeBasedTargetSource:原型bean目标源,此抽象类可确保beanName对应的bean的scope属性为prototype。其子类做了简单原型、池化原型、线程隔离原型这3种实现。 - AbstractRefreshableTargetSource:
可刷新的目标源。此类实现可根据配置的刷新延迟时间,在每次获取目标对象时自动刷新目标对象。 - AbstractLazyCreationTargetSource:
此类实现在调用getTarget()获取时才创建目标对象。