本文主要是介绍Spring 揭秘之Spring AOP一世(1)概念实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- Spring AOP一世
- Spring AOP中的Joinpoint
- Spring AOP中的Pointcut
- 常见的Pointcut
- 扩展Pointcut
- 扩展StaticMethodMatcherPointcut
- 扩展DynamicMethodMatcherPointcut
- Spring AOP中的Advice
- per-class类型的Advice
- Before Advice
- ThrowsAdvice
- AfterReturningAdvice
- Around Advice
- per-instance类型的Advice
- DelegatingIntroductionInterceptor
- DelegatePerTargetObjectIntroductionInterceptor
- Spring AOP中的Aspect
- PointcutAdvisor家族
- IntroductionAdvisor分支
- Ordered的作用
- 小结
Spring AOP一世
在上篇文章Spring AOP概述及其实现机制中,我们知道了Spring AOP建立在动态代理和CGLIB的支持之上;在这篇文章里,我们将介绍Spring AOP的底层实现机制;或许Spring AOP的概念实体会进化,但是其低层级制却是稳定的;
Spring AOP中的Joinpoint
AOP中的Joinpoint有很多类型,比如字段的获取与设置、构造方法、类初始化、方法调用、方法执行等;而Spring AOP秉承KISS原则,仅支持方法级别的Joinpoint;这已经可以满足80%的开发需求啦;Spring这么选择有以下几个原因:
- KISS原则:Keep It Simple,Stupid;当然可以选择100%实现AOP中的概念,但是这样就会导致框架过于臃肿;事倍功半并不是我们想要的结果;
- 字段级别的Joinpoint完全可以通过方法级别的Joinpoint来代替,同时对OOP封装的破坏性也较小;
- Spring 提供了对其他AOP实现的支持,比如AspectJ。如果Spring AOP无法满足业务需求,不妨使用AspectJ;
Spring AOP中的Pointcut
Pointcut就是Spring AOP中所有Pointcut的最顶层抽象啦,它定义两个方法用于捕捉系统中的Joinpoint;并且提供了TruePointcut类型的实例,该实例会对系统中所有的对象上的所有被支持的Joinpoint进行匹配;
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;}
和Pointcut关系紧密的有ClassFilter和MethodMatcher:这两个接口分别负责在类层面和方法层面对系统对象进行筛选,从而确定Joinpoint;
ClassFilter进行类级别的筛选;MethodMatcher则进行方法级别的筛选;将其分开有利于筛选逻辑的重用;
ClassFilter和MethodMatcher和Pointcut相同的一点是,都提供了一个True实例,表示都ok;
public interface ClassFilter {/*** Should the pointcut apply to the given interface or target class?* @param clazz the candidate target class* @return whether the advice should apply to the given target class*/boolean matches(Class<?> clazz);/*** Canonical instance of a ClassFilter that matches all classes.*/ClassFilter TRUE = TrueClassFilter.INSTANCE;}
public interface MethodMatcher {/*** Perform static checking whether the given method matches.* <p>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* @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}?* <p>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.* <p>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* @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;}
ClassFilter逻辑较为简单,MethodMatcher相对来说就复杂一些;其提供了两个检测函数——两个参数和三个参数;第三个不同的参数为方法调用的参数数组;只有在isRuntime()方法返回true时,才会调用三个参数的方法进行运行时的判断;
前面我们提到的MethodMatcher的True实例中,isRuntime()返回false,而二个参数的matches方法则返回true;
按照是否进行三参数matches方法调用,Spring中的MethodMatcher分为两类:StaticMethodMatcher和DynamicMethodMatcher:
常见的Pointcut
-
NameMatchMethodPointcut:使用静态MethodMatcher,通过方法名进行筛选,可使用通配符*进行简单的模糊匹配;最简单实现;
-
JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut:使用静态MethodMatcher,通过正则表达式进行筛选;
-
AnnotationMatchingPointcut:根据待检测对象中是否有指定类型的注解来匹配Joinpoint;该Pointcut根据传入的注解类型不同,有着不同的匹配规则:
- 只传入类级别的注解:拥有该注解的类的所有方法均匹配该Pointcut;忽略方法签名;
- 只传入方法级别的注解:拥有该注解的方法均匹配该Pointcut,忽略对象的类型;
- 同时传入:精确匹配,首先待检测对象所属的类拥有对应的类注解,同时该类中只有拥有对应方法注解的方法才匹配该Pointcut
-
ComposablePointcut:之前文章里提到过Pointcut可以进行运算;而ComposablePointcut就是Spring AOP对其的实现;它可以进行交和并运算;该类不仅可以进行Pointcut之间的运算,还可以进行ClassFilter和MethodMatcher的运算;
-
ControlFlowPointcut:Spring AOP中最复杂的Pointcut类型,它不是对方法进行匹配,而是对方法调用的流程进行匹配;
它只在类C中对方法M进行调用时,对M进行匹配;其他上下文中对M的调用将不会使M匹配该Pointcut;当然,我们在指定C的同时,也可以传入方法名N,于是只有C中的方法N调用M时,才会命中M,即匹配Pointcut;
扩展Pointcut
Pointcut根据MethodMatcher的类别(静态还是动态),也可以分为两类,这在上面的图中是可以看到的;
扩展StaticMethodMatcherPointcut
该类别的Pointcut默认使用ClassFilter.TRUT作为其获取ClassFilter的返回值;如果需要对目标对象的类别进行限制,也可以通过Setter方法指定ClassFilter;通常只需要实现两参数的matches方法即可;
扩展DynamicMethodMatcherPointcut
该类别的Pointcut的getClassFilter方法默认返回ClassFilter.TRUE;如果要对目标对象的类别进行约束,可以重写该方法即可;
同时isRuntime和两个参数的matches方法均返回true;通常只需要实现三参数的matches方法即可;
Spring AOP中的Advice
Spring AOP加入了开源组织AOP Alliance,目的在于标准化AOP的使用,促进各种AOP实现之间的可交互性;所以Spring中的Advice实现全部遵循AOP Alliance规定的接口;
Advice封装了横切逻辑;Spring AOP中,Advice按照其能否在目标对象类的所有实例中共享,分为两大类:per-class和per-instance;
per-class类型的Advice
per-class类型的Advice一般不会为目标对象保存任何状态或者添加新的属性,因为它要在所有目标对象之间共享;
上图中的所有Advice均属于per-class类型;上图中未出现的Introduction则不属于该类型;
Before Advice
Before Advice所表示的横切逻辑将在Joinpoint表示的方法之前执行,通常不会打断Joinpoint的执行,当然,如果有必要,也可以通过抛出异常来中断程序执行流程;
要实现Before Advice,在Spring中只需要实现MethodBeforeAdvice接口即可;
public interface MethodBeforeAdvice extends BeforeAdvice {void before(Method method, Object[] args, Object target) throws Throwable;
}
public interface BeforeAdvice extends Advice {
}
public interface Advice {
}
BeforeAdvice和Advice都是标记接口;该类型的Advice常用来做初始化或者其他准备工作;
ThrowsAdvice
对应AOP中AfterThrowingAdvice,虽然该接口没有定义任何方法,但是在实现该接口时,方法需要满足以下规则:
void afterThrowing([Method, args, target], ThrowableSubclass);
如:
public void afterThrowing(Exception ex){}
public void afterThrowing(RemoteException){}
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){}
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex){}
框架通过反射调用这些方法;该类型的Advice通常用于系统中特定异常的监控,并提供统一的异常处理方式;
AfterReturningAdvice
方法在正常返回的情况下,AfterReturningAdvice才会执行;Spring的AfterReturningAdvice可以访问到返回值,但是无法更改返回值;这点需要特别注意;
Around Advice
Spring AOP没有提供After Finally Advice,并且AfterReturningAdvice对于返回值无法做过多的干涉,所以类似的功能就要交给Around Advice来实现了;Spring AOP没有直接定义Around Advice,而是直接采用AOP Alliance标准接口即:MethodInterceptor。
public interface MethodInterceptor extends Interceptor {Object invoke(MethodInvocation invocation) throws Throwable;
}
通过MethodInvocation类,我们可以控制对Joinpoint的拦截行为——调用proceed()方法则继续执行该Joinpoint上的其他MethodInterceptor,否则中断执行;
per-instance类型的Advice
per-instance类型的Advice将为每个目标对象保存各自的状态和相关逻辑;Spring AOP中唯一的per-instance类型的Advice就是Introduction;
Spring中,为目标对象添加新的行为和属性,就必须声明相应的接口及其实现,然后通过特定的AroundAdvice——IntroductionInterceptor将新的逻辑附加到目标对象上,完成对目标对象的增强;
Introduction总体上分为两支,一支是以DynamicIntroductionAdvice为首的动态分支,一支是以IntroductionInfo为首的静态可配置分支;区别在于判断Introduction是否可应用到目标接口的时机——编译时或者运行时;
来看看两个实现类吧~
DelegatingIntroductionInterceptor
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupportimplements IntroductionInterceptor {@Nullableprivate Object delegate;public DelegatingIntroductionInterceptor(Object delegate) {init(delegate);}protected DelegatingIntroductionInterceptor() {init(this);}/*** Both constructors use this init method, as it is impossible to pass* a "this" reference from one constructor to another.* @param delegate the delegate object*/private void init(Object delegate) {Assert.notNull(delegate, "Delegate must not be null");this.delegate = delegate;implementInterfacesOnObject(delegate);// We don't want to expose the control interfacesuppressInterface(IntroductionInterceptor.class);suppressInterface(DynamicIntroductionAdvice.class);}/*** Subclasses may need to override this if they want to perform custom* behaviour in around advice. However, subclasses should invoke this* method, which handles introduced interfaces and forwarding to the target.*/@Override@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {if (isMethodOnIntroducedInterface(mi)) {// Using the following method rather than direct reflection, we// get correct handling of InvocationTargetException// if the introduced method throws an exception.Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());// Massage return value if possible: if the delegate returned itself,// we really want to return the proxy.if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {Object proxy = ((ProxyMethodInvocation) mi).getProxy();if (mi.getMethod().getReturnType().isInstance(proxy)) {retVal = proxy;}}return retVal;}return doProceed(mi);}/*** Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}.* Subclasses can override this method to intercept method invocations on the* target object which is useful when an introduction needs to monitor the object* that it is introduced into. This method is <strong>never</strong> called for* {@link MethodInvocation MethodInvocations} on the introduced interfaces.*/protected Object doProceed(MethodInvocation mi) throws Throwable {// If we get here, just pass the invocation on.return mi.proceed();}}
从其Invoke方法中,就可以看出DelegatingIntroductionInterceptor实际上是一个伪军:因为对于所有目标对象,它都使用一个delegate实例;如果真正要实现per-instance的诺言,就要使用它的兄弟:
DelegatePerTargetObjectIntroductionInterceptor
该类内部持有一个目标对象和相应Introduction逻辑实现类之间的映射关系。当目标对象上的接口方法被调用时,该类就会拦截这些方法,然后找到对应的横切逻辑,之后就没有什么区别啦;
最后,Spring AOP中的Introduction采用动态代理的方式实现,所以在性能上同AspectJ通过编译器将Introduction织入目标对象要逊色不少;
Spring AOP中的Aspect
有了Pointcut和Advice,就可以组装Aspect啦;然鹅,Spring中并没有完全明确的Aspect的概念;但是它还是有相同功能的概念的:Advisor;
Advisor与Aspect不同,因为Spring AOP中,Advisor通常只持有一个Pointcut和一个Advice;而理论上,一个Aspect可以有多个Pointcut和多个Advice;
首先来看Advisor的体系结构:
注意!这里,名词组合出现的情况变得更为普遍!请时刻注意:Spring AOP中,IntroductionInterceptor是Introduction(Advice的一种)的实现,;Interceptor是Around Advice的实现;Advisor是Aspect的实现;
PointcutAdvisor家族
首先上图:
PointcutAdvisor才是真正的一个Pointcut和一个Advice的Advisor;以下简单介绍各个Advisor:
- DefaultPointcutAdvisor:PointcutAdvisor最为常用的实现;不接受Introduction类型的Advice,其余任何类型的Pointcut和Advice均可接受;
- NameMatchMethodPointcutAdvisor:限定使用NameMatchMethodPointcut,并且外部不可更改;不接受Introduction类型的Advice,其余均可;
- RegexpMethodPointcutAdvisor:同上,限定使用正则表达式的Pointcut。默认使用JdkRegexpMethodPoincut;可以通过setPerl5(boolean)进行转换;
- DefaultBeanFactoryPointcutAdvisor:将自身绑定到BeanFactory之上,通过beanName的方式寻找关联的Advice;只有Pointcut匹配成功后才去实例化对应的Advice,减少容器启动初期Advisor和Advice之间的耦合;
IntroductionAdvisor分支
该类别与PointcutAdvisor最大的区别在于,IntroductionAdvisor只能用于类级别的拦截,只能使用Introduction类型的Advisor;其类层次比较简单:
只有DefaultIntroductionAdvisor这一默认实现;
我们需要关注的也就不多啦;
Ordered的作用
系统中存在多个横切关注点,也会存在多个Advisor;当一个Joinpoint命中多个Advisor时,就需要留意或者控制这些Advisor的执行顺序;特别是这些Advisor需要按照一定顺序执行时,否则系统就会出现不可控的情况;
Spring在处理同一Joinpoint处的多个Advisor时,会按照指定的顺序来执行它们;顺序号越小,优先级越高;优先级越高,将越早执行;
问题来了,顺序号如何指定呢?默认情况下,配置文件中先声明的Advisor顺序号比较小;
当然,最为彻底的解决方法是为每个Advisor指定顺序号(一般从0或者1开始,因为小于0的编号一般是Spring内部使用)——即实现Ordered接口;
小结
这一节中,我们了解了Spring AOP对AOP的各种概念的实现;它们是整个框架的基础;体现了Spring框架的稳定性和灵活性;
接下来,我们将看看Spring AOP的织入过程;
这篇关于Spring 揭秘之Spring AOP一世(1)概念实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!