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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu