Spring 揭秘之Spring AOP一世(1)概念实现

2024-01-10 00:08

本文主要是介绍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这么选择有以下几个原因:

  1. KISS原则:Keep It Simple,Stupid;当然可以选择100%实现AOP中的概念,但是这样就会导致框架过于臃肿;事倍功半并不是我们想要的结果;
  2. 字段级别的Joinpoint完全可以通过方法级别的Joinpoint来代替,同时对OOP封装的破坏性也较小;
  3. 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

  1. NameMatchMethodPointcut:使用静态MethodMatcher,通过方法名进行筛选,可使用通配符*进行简单的模糊匹配;最简单实现;

  2. JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut:使用静态MethodMatcher,通过正则表达式进行筛选;

  3. AnnotationMatchingPointcut:根据待检测对象中是否有指定类型的注解来匹配Joinpoint;该Pointcut根据传入的注解类型不同,有着不同的匹配规则:

    1. 只传入类级别的注解:拥有该注解的类的所有方法均匹配该Pointcut;忽略方法签名;
    2. 只传入方法级别的注解:拥有该注解的方法均匹配该Pointcut,忽略对象的类型;
    3. 同时传入:精确匹配,首先待检测对象所属的类拥有对应的类注解,同时该类中只有拥有对应方法注解的方法才匹配该Pointcut
  4. ComposablePointcut:之前文章里提到过Pointcut可以进行运算;而ComposablePointcut就是Spring AOP对其的实现;它可以进行交和并运算;该类不仅可以进行Pointcut之间的运算,还可以进行ClassFilter和MethodMatcher的运算;

  5. 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:

  1. DefaultPointcutAdvisor:PointcutAdvisor最为常用的实现;不接受Introduction类型的Advice,其余任何类型的Pointcut和Advice均可接受;
  2. NameMatchMethodPointcutAdvisor:限定使用NameMatchMethodPointcut,并且外部不可更改;不接受Introduction类型的Advice,其余均可;
  3. RegexpMethodPointcutAdvisor:同上,限定使用正则表达式的Pointcut。默认使用JdkRegexpMethodPoincut;可以通过setPerl5(boolean)进行转换;
  4. 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)概念实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/588887

相关文章

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多