Spring AOP实现原理
- 动态代理: 利用核心类
Proxy
和接口InvocationHandler
(基于代理模式的思想) - 字节码生成: 利用
CGLIB
动态字节码库
Spring AOP中的关键字
1.Joinpoint
只支持方法执行类型的Joinpoint,力求付出20%的努力来满足80%的开发需求
2.Pointcut
Spring AOP Pointcut 框架结构:
根接口: org.springframework.aop.Pointcut
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
其中,ClassFilter
和MethodMatcher
分别用于匹配将执行织入操作的对象及其相应的方法。
如果直接获取类型为Pointcut
的TRUE
对象时,那么代表Pointcut的匹配将会针对系统所有的目标类以及它们的实例进行。
MethodMatcher
是一个重要的接口,其内部结构如下:
public interface MethodMatcher {
/**
* Perform static checking whether the given method matches. If this
* returns {@code false} or if the {@link #isRuntime()} method
* returns {@code false}, no runtime check (i.e. no.
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made.
* @param method the candidate method
* @param targetClass the target class (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring class)
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class> targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
* Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
*
This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring class)
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class> targetClass, Object[] args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
方法
boolean matches(Method method, Class> targetClass
表示不会对被代理对象的方法参数进行捕获,处理。当方法isRuntime()
返回false
时,表示执行的是该方法。这种类型的处理称之为StaticMethodMathcer
,可以对匹配到的结果进行缓存,所以处理性能很高。
同理,当isRuntime()
返回true
时,代表执行的是方法
boolean matches(Method method, Class> targetClass, Object[] args)
表示要对被代理对象的方法参数进行捕获,处理。这种类型的处理称之为DynamicMethodMatcher
,因为参数不定,无法进行缓存,所以性能不高。
常见的几种Pointcut
实现:
1.NameMatchMethodPointcut
:
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("test1");
pointcut.setMappedNames(new String[]{"test1","test2"]};
该类可以直接根据给定的方法名进行匹配(也支持简单的模糊匹配),不过通过上边的代码也可以了解到,该匹配不涉及目标方法的参数,所以对于重载
方法则无法进行有效的支持。
2.AbstractRegexpMethodPointcut
:
该类支持正则表达式
来匹配方法,其下有一个重要的实现类:JdkRegexpMethodPointcut
。代码片段如下:
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.*",".*matches"});
3.AnnotationMatchingPointcut
:
基于注解的方法匹配。代码片段如下:
定义自定义注解:
//类级别的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface ClassLevelAnnotation {
}
//方法级别的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {
}
将注解应用在某一个类上:
@ClassLevelAnnotation
public class GenericTargetObject {
@MethodLevelAnnotation
public void hello() {
System.out.println("hello");
}
}
利用AnnotationMatchingPointcut
来进行匹配:
类级别的匹配:
AnnotationMatchingPointcut pointcutByClass = new AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
方法级别的匹配:
AnnotationMatchingPointcut pointcutByMethod = new AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);
或者两个结合使用:
AnnotationMatchingPointcut pointcutByClassAndMethod = new AnnotationMatchingPointcut.forMethodAnnotation(ClassLevelAnnotation.class, MethodLevelAnnotation.class);
4.扩展Pointcut:
通过继承抽象类StaticMethodMatcherPointcut
和DynamicMethodMatcherPointcut
来自定义自己的Pointcut。
3.Advice
Spring Advice可以分为两大类:per-class
和 per-instance
。
1.per-class
是指该类型的Advice的实例可以在目标对象类的所有实例之间共享,这种类型的Advice通常只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性。
其包括:
- Before Advice
代码片段如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object object) throws Throwable;
}
BeforeAdvice
接口为一个标记接口,需要继承该接口,实现自己的before
逻辑
- ThrowsAdvice
代码片段如下:
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable t) {
//普通异常处理
}
public void afterThrowing(RuntimeException e) {
//运行时异常处理
}
}
ThrowsAdvice
同样也为一个标记接口,不过在实现它时,方法定义需要遵循如下规则:
void afterThrowing([Method, args, target], ThrowableSubclass);
其中,[]中的可以省略。
- AfterReturningAdvice
其接口定义如下:
public interface AfterReturningAdvice extends AfterAdvice {
/**
* Callback after a given method successfully returned.
* @param returnValue the value returned by the method, if any
* @param method method being invoked
* @param args arguments to the method
* @param target target of the method invocation. May be {@code null}.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
通过该接口方法可以获取到原方法成功执行之后的返回值,不过不可以对其进行修改。
- Around Advice
Spring没有提供该接口的规范,而是直接使用了AOP Alliance的标准接口,如下:
pubic interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
能够cover
前面几种类型的Advice
,很强大!
2.per-instance
类型的Advice不会在目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关逻辑。它织入面向的是对象
。在Spring AOP中,Introduction
就是唯一的一种per-instance
型的Advice
,对其进行实现的是IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}
其IntroductionInterceptor
接口还继承了两个重要的接口MethodInterceptor
和DynamicIntroductionAdvice
。其中接口DynamicIntroductionAdvice
界定为哪些接口类提供相应的拦截功能,通过接口MethodInterceptor
来处理新添加的接口方法调用。
如果要添加切入的逻辑操作,可以直接扩展IntroductionInterceptor
中的invoke
方法,不过也可以利用Spring提供的两个实现类来完成:
- DelegatingIntroductionInterceptor (需要设置其
scope
=prototype
)
来简单看一段伪代码演示:
{SubClass} targetObject = new {Class()};
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegate);
//进行织入
{SubClass} proxyObject = {SubClass} weaver.weave(interceptor).getPxory();
- DelegatePerTargetObjectIntroductionInterceptor
4.Aspect
Aspect在Spring中表示为Advisor
,其体系结构分为两类:PointcutAdvisor
和IntroductionAdvisor
1.PointcutAdvisor
接口:
public interface PointcutAdvisor extends Advisor {
/**
* Get the Pointcut that drives this advisor.
*/
Pointcut getPointcut();
}
对该接口的实现有几个比较重要的类:DefaultPointcutAdvisor
、NameMatchMethodPointcutAdvisor
和RegexpMethodPointcutAdvisor
来看一下DefaultPointcutAdvisor
的构造方法:
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
可以看出,该类是持有一个Pointcut
和Advice
,所以从这一点上也可以将Spring的Advisor理解为封装了Pointcut
和Advice
的一个容器。
然后对于类NameMatchMethodPointcutAdvisor
和RegexpMethodPointcutAdvisor
,联想一下前边了解到的几种类型的Pointcut,不难得出,这两个类对于Pointcut的封装则分别为NameMatchMethodPointcut
和RegexpMethodPointcut
。
2.IntroductionAdvisor
接口:是对Introduction
类型的封装,其内部持有的Pointcut和Advice只能是都是Introduction
类型的。
5.Ordered
可以通过Ordered
接口来为不同的Advisor
配置相应的优先级。
在xml中来为Advisor
指定不同的优先级:
当然也可以在代码中,通过调用抽象类AbstractPointcutAdvisor
的
public void setOrder(int order) {
this.order = order;
}
来为不同的Advisor设置相应的优先级
Spring AOP 织入器
上边的流程全部走完一遍之后,接下来就是如何调用Spring AOP提供的接口来获得针对目标对象的一个代理对象啦。有两种方法,分别是ProxyFactory
和ProxyFactoryBean
- ProxyFactory
简单看一下ProxyFactory
的代码演示:
ProxyFactory weaver = new ProxyFactory();
Advisor advisor = ..;
waever.addAdvisor(advisor);
weaver.setTarget({yourTargetObject});
Object proxyObject = weaver.getProxy();
所以如果要利用ProxyFactory
来生成代理对象,需要设置两处:目标对象:yourTargetObject
和Advisor
。
上边说到Sping AOP的实现包括动态代理
和字节码
生成,那么什么时候采用其中的某种方式呢?
对于动态代理
如果Spring AOP检测到targetObject
实现了相应的接口,那么就会采用动态代理。可以显示指定接口,比如:
weaver.setInterfaces();
也可以不用指定,框架会自动检测。
但是也可以 通过设置相应的属性来强制转换成字节码生成方式,方式为:
ProxyFactory weaver = new ProxyFactory();
weaver.setproxyTargetClass(true);
另外还有两种方式是要采用字节码生成方式来代理的:
1.如果targetObject没有实现任何接口
2.将ProxyFactory的optimize
属性置为true
以前总结的都是针对类级别,即per-class
的代理,而对于per-instance
级别的代理该如何去做呢?
来简单看一段伪代码演示吧:
ProxyFactory weaver = new ProxyFactory();
/*
*面向接口的代理
*weaver.setProxyTargetClass(true);面向类的代理,即CGLIB代理
*/
weaver.setInterfaces(new class[] {Interface1.class, Interface2.class});
DemoIntroductionInterceptor advice = new DemoIntroductionInterceptor();
weaver.addAdvice(advice);
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice, advice);
weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
- ProxyFactoryBean
ProxyFactoryBean
为IOC容器中的一个织入器。同proxyFactory
,它也支持基于接口和基于类的代理。
参考书籍:
1.《Spring揭秘》