Spring系列-8 AOP使用与原理

news2024/12/24 20:26:42

背景

按照软件重构的思想,当多个类中存在相同的代码时,需要提取公共部分来消除代码坏味道。Java的继承机制允许用户在纵向上通过提取公共方法或者公共部分(模版方法方式)至父类中以消除代码重复问题;日志、访问控制、性能监测等重复的非业务代码揉杂在业务代码之中无法横向抽取,AOP技术为其提供了一个解决方案。
AOP技术将这些重复的非业务代码抽取出为一个模块,通过技术整合还原代码的逻辑和功能;即:在代码层面上消除了重复度,提高了可维护性,并在功能层面上得到还原。抽取重复代码作为一个模块是用户的问题,然而技术整合(对目标织入增强逻辑,后文介绍)以实现功能还原是AOP的目标和工作重心,Spring AOP是该技术的一种实现。

本文作为Spring系列的第八篇,介绍Spring框架中AOP的使用、注意事项和实现原理,原理部分会结合Spring框架源码进行。

Spring系列的后续文章如Spring系列-9 Async注解使用与原理和Spring系列-10 事务机制其底层原理都是Spring AOP。

1.AOP

常见的AOP实现方案有Spring AOP和AspectJ:相对于Spring AOP而言,AspectJ是一种更成熟、专业的AOP实现方案。AOP的技术整合(织入增强逻辑)可以发生在编译器、类加载期以及运行期:AspectJ在编译器(ajc)和类加载器(使用特定的类加载器)实现;Spring AOP在运行时通过动态代理方式实现。AspectJ提供了完整了AOP方案,而Spring AOP从实用性出发未常见的应用场景提供了技术方案,如不支持静态方法、构造方法等的AOP。

本文考虑到文章篇幅,下文暂不涉及其他AOP技术,后续会在《AspectJ使用与原理》中单独介绍AspectJ

Spring AOP构建于IOC之上,与IOC一起作为Spring框架的基石。Spring AOP底层使用动态代理技术实现,包括:JDK动态代理与CGLIB动态代理;JDK动态代理技术要求被代理对象基于接口,而CGLIB动态代理基于类的继承实现代理,从而要求被代理类不能为final类且被代理的方法不能被final、staic、private等修饰。二者都有局限性,在一定程度上相互弥补。

1.1 基本概念

[1] 执行点:在Spring AOP中指代目标类中具体的方法;
[2] 连接点:包含位置信息的执行点,位置信息包括:方法执行前、后、前后、异常抛出等;
[3] 切点:根据指定条件(类是否符合、方法是否符合等)过滤出的执行点的集合;
[4] 通知/增强:为目标对象增加的新功能,如在业务代码中引入日志、访问控制等功能;
[5] 切面:切面由切点和通知组成;
[6] 织入:将切面织入目标对象,形成代理对象的过程。

1.2 增强类型

Spring中使用Advise标记接口表示增强,Spring根据方位信息(方法执行前后、环绕、异常抛出等)为其定义了不同的子类接口。

public interface Advice {}

1.2.1 增强类型相关接口

[1] 前置增强
BeforeAdvice接口表示前置增强,由于Spring当前仅支持方法增强,所以可用的接口为MethodBeforeAdvice.

//同Advise接口,BeforeAdvice也是个空接口
public interface MethodBeforeAdvice extends BeforeAdvice {
	void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

如上所示,MethodBeforeAdvice接口中仅有一个before方法,入参分别是方法对象、参数数组、目标对象;该方法会在目标对象的方法调用前调用。
[2] 后置增强

public interface AfterReturningAdvice extends AfterAdvice {
	void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}

该方法中仅有一个afterReturning方法,入参比before多处一个返回值;该方法会在目标对象的方法调用后调用。

[3] 环绕增强

@FunctionalInterface
// Interceptor 是Advise的字接口,且是空接口
public interface MethodInterceptor extends Interceptor {
	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}

可通过invocation.proceed()语句调用目标对象方法并获得放回值,可在前后自定义逻辑,相对于前置和后置有更高的灵活性。

[4] 异常抛出增强

public interface ThrowsAdvice extends AfterAdvice {
}

ThrowsAdvice是一个空接口,起标签作用。在运行期间Spring通过反射调用afterThrowing接口,该接口可以被定义为:void afterThrowing(Method method, Object[] args, Object target, Throwable exception);
其中method、args和target是可选的,exception参数是必选的;在目标方法抛出异常后,实施增强。

除此之外,框架还定义了一种引介增强,用于在目标类中添加一些新的方法和属性。

1.2.2 案例介绍

case 1:前置、后置、环绕增强
定义前置通知:

@Slf4j
public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        LOGGER.info("----before----");
    }
}

定义后置通知:

@Slf4j
public class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        LOGGER.info("----after----");
    }
}

定义环绕通知:

@Slf4j
public class MyRoundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        LOGGER.info("====round before====");
        Object result = invocation.proceed();
        LOGGER.info("====round after====");
        return result;
    }
}

测试用例如下:

public class AdviceAopTest {
    @Test
    public void testAdvice() {
        ProxyFactory proxyFactory = new ProxyFactory();
        TaskService taskService = new TaskServiceImpl();
        proxyFactory.setTarget(taskService);
        proxyFactory.setInterfaces(TaskService.class);
        // 添加前置增强
        proxyFactory.addAdvice(new MyBeforeAdvice());
        // 添加后置增强
        proxyFactory.addAdvice(new MyAfterReturningAdvice());
        // 添加环绕增强
        proxyFactory.addAdvice(new MyRoundAdvice());
        // 获取代理对象
        TaskService proxy = (TaskService)proxyFactory.getProxy();
        proxy.sync();
    }
}

运行结果如下所示:
在这里插入图片描述

case 2:异常抛出增强
修改目标类代码逻辑:

@Slf4j
public class TaskServiceImpl implements TaskService{
    @Override
    @SneakyThrows
    public void sync() {
        LOGGER.info("[sync data]");
        throw new Exception("");
    }
}

测试用例如下:

public class ThrowsAdviceTest {
    @Test
    public void testAdvice() {
        ProxyFactory proxyFactory = new ProxyFactory();
        TaskService taskService = new TaskServiceImpl();
        proxyFactory.setTarget(taskService);
        proxyFactory.setInterfaces(TaskService.class);

        proxyFactory.addAdvice(new MyThrowsAdvice());
        TaskService proxy = (TaskService)proxyFactory.getProxy();
        proxy.sync();
    }
}

结果如下:
在这里插入图片描述

1.3 切点类型

框架定义切点是为了从目标类的连接点(执行点)中过滤出符合条件的部分,为此在切点类的内部提供类两个过滤器:ClassFilter和MethodMatcher,分别对类型和方法进行过滤。

public interface Pointcut {
	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	// Pointcut.TRUE 对象表示所有目标类的所有方法均满足条件
	// (实例对应的ClassFilter和MethodMatcher对象的match方法均返回true)
	Pointcut TRUE = TruePointcut.INSTANCE;
}

Pointcut切点接口定义如上所示,Spring并基于此扩展出了多种切点类型;使得可以根据方法名、参数、是否包含注解以及表达式等进行过滤。

1.4 切面类型

Spring使用Advisor表示切面类型,可以分为3类:一般切面Advisor、切点切面PointcutAdvisor、引介切面IntroductionAdvisor;一般切面Advisor仅包含一个Advice, 即表示作用对象是所有目标类的所有方法;PointcutAdvisor包含Advice和Pointcut信息,可以通过切点定位出满足Pointcut过滤条件的执行点集合;IntroductionAdvisor对应于引介切点和增强。
其中:PointcutAdvisor及其子类DefaultPointcutAdvisor是较为常见的切面类型,源码如下:

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

	private Pointcut pointcut = Pointcut.TRUE;
	
	private Advice advice = EMPTY_ADVICE;
	
	public DefaultPointcutAdvisor() {
	}

	public DefaultPointcutAdvisor(Advice advice) {
		this(Pointcut.TRUE, advice);
	}

	public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
		this.pointcut = pointcut;
		setAdvice(advice);
	}
}

DefaultPointcutAdvisor包含一个切点和一个增强类型属性:Pointcut的默认值为Pointcut.TRUE表示所有目标类的所有方法均为连接点;Advice的默认值为EMPTY_ADVICE:Advice EMPTY_ADVICE = new Advice() {};, 即表示不进行增强。

章节-1.2测试用例中为ProxyFactory添加切面部分逻辑为:proxyFactory.addAdvice(new MyBeforeAdvice()); 等价于 proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyBeforeAdvice()));.

2.使用方式

章节-1中涉及的ProxyFactory代理工厂提供了基于切面构造代理对象的能力,Spring框架结合IOC对此进行了一层封装以适应多种场景。封装后为用户提供了一套Spring风格的“API”(使用方式),该部分是本章节的重点部分。

2.1 xml配置方式

引入AOP的schema:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

方式1:使用aop:aspect标签

定义目标类:

@Slf4j
public class ApplicationRunner {
    public void run() {
        LOGGER.info("exec...");
    }
}

引入增强类:

@Slf4j
public class EnhanceLog {
    public void beforeExec() {
        LOGGER.info("before exec");
    }

    public void afterExec() {
        LOGGER.info("after exec");
    }
}

在xml文件中配置AOP:

<aop:config>
    <aop:pointcut id="runnerExecPc" expression="execution(* *.run(..))"/>
    
    <aop:aspect ref="enhanceLog">
        <aop:before method="beforeExec" pointcut-ref="runnerExecPc"/>
        <aop:after method="afterExec" pointcut-ref="runnerExecPc"/>
    </aop:aspect>
</aop:config>

方式2:使用aop:advisor标签

引入增强类LogAdvice,需要实现org.aopalliance.intercept.MethodInterceptor接口:

@Slf4j
public class LogAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        LOGGER.info("before intercept exec");
        methodInvocation.proceed();
        LOGGER.info("after intercept exec");
        return null;
    }
}

在xml文件中配置AOP:

    <aop:config>
        <aop:pointcut id="runnerExecPc" expression="execution(* *.run(..))"/>
        <aop:advisor advice-ref="logAdvice" pointcut-ref="runnerExecPc"/>
    </aop:config>

其中:advisor将所有的逻辑都封装在了MethodInterceptor的invoke方法中,通过方法完成增强;aspect通过配置对外展示需要增强逻辑,而不需要实现MethodInterceptor等Advice系列接口。相对而言,aspect的代码侵入性较低。

2.2 注解方式

Spring通过整合AspectJ为AOP提供了注解形式的使用方式;因此使用注解时,需要添加对aspectjweaver的依赖(由org.aspectj提供)。

2.2.1 切面注解 @Aspect

注解在类上用于标记切面类;其他注解都可以添加在该类中的方法上。

2.2.2 增强注解 @Before @After @Around @AfterThrowing @AfterReturing

被增强注解的方法内容作为增强。@Before表示前置增强,@AfterReturing表示后置增强,@Around表示环绕增强,@AfterThrowing表示异常抛出增强,@After表示方法正常执行完或者异常抛出都会执行的增强逻辑;与Spring中定义的增强类型基本保持一致。
被上述注解标注的方法可以增加一个JoinPoint类型(Around为ProceedingJoinPoint类型)的参数(也可不加)用于获取上下文信息;另外,@AfterThrowing还可添加异常类型的参数,而@AfterReturing可以添加一个Object类型的参数(表示运行结果),以下通过案例的形式进行介绍。

添加配置类:

@Configuration
@ComponentScan(basePackages = "com.seong.demo.annotation")
@EnableAspectJAutoProxy
public class AopDemoConfiguration {
}

添加目标类:

@Component
@Slf4j
public class DataTask {
    public String syncData() {
        LOGGER.info("start sync data");
        return "success";
    }
}

添加测试用例:

public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopDemoConfiguration.class);
        DataTask dataTask = (DataTask)context.getBean("dataTask");
        dataTask.syncData();
    }
}

case 1: 前置、后置
切面配置如下所示:

@Component
@Aspect
@Slf4j
public class MyNormalAspect {
    @Before("execution(public String com.seong.demo.annotation.DataTask.*(..))")
    public void beforeExec(JoinPoint joinPoint) {
        LOGGER.info("[Before DataTask], joinPoint is {}.", joinPoint);
    }

    @AfterReturning("execution(public String com.seong.demo.annotation.DataTask.*(..))")
    public void afterExec(JoinPoint joinPoint) {
        LOGGER.info("[After DataTask], result is {}, joinPoint is {}.", joinPoint);
    }
}

得到如下运行结果:
在这里插入图片描述

case 2: 环绕通知
在切面中配置环绕增强,如下所示:

@Component
@Aspect
@Slf4j
public class MyAroundAspect {
    @SneakyThrows
    @Around("execution(public String com.seong.demo.annotation.DataTask.*(..))")
    public void aroundExec(ProceedingJoinPoint joinPoint) {
        LOGGER.info("[Around DataTask] call before.");
        Object result = joinPoint.proceed();
        LOGGER.info("[Around DataTask] call end, result is {}.", result);
    }
}

得到如下运行结果:
在这里插入图片描述
case 3: 异常抛出增强
在切面中配置异常抛出增强,如下所示:

@Component
@Aspect
@Slf4j
public class MyExceptionAspect {
    @AfterThrowing("execution(public String com.seong.demo.annotation.DataTask.*(..))")
    public void afterThrowingExec(JoinPoint joinPoint) {
        LOGGER.info("[AfterThrow DataTask], joinPoint is {}.", joinPoint);
    }
}

得到如下运行结果:在这里插入图片描述

2.4 expression表达式

参考文章:《AspectJ使用与原理》

3.实现原理

略,待补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GO的IO接口和工具

GO的IO接口和工具 文章目录GO的IO接口和工具一、io包中接口的好处和优势1.1拷贝数据的函数二、 在io包中&#xff0c;io.Reader 的扩展接口和实现类型都有哪些2.1 io.Reader的扩展接口2.2 io.Reader接口的实现类型2.3 示例三、io包的接口&#xff0c;以及它们之间的关系3.1 读操…

Raki的读paper小记:Forget-free Continual Learning with Winning Subnetworks

Abstract&Introduction&Related Work 研究任务 用子网络做CL已有方法和相关工作 彩票假说&#xff08;LTH&#xff09;证明了稀疏子网络&#xff08;称为中奖彩票&#xff09;的存在&#xff0c;这些子网络保持了密集网络的性能&#xff0c;然而使用迭代修剪方法在持续…

Splunk 的一个Bug (Events from tracker.log have not been seen)

1:背景:Splunk version: 8.2.4 splunk 的一个alert: Events from tracker.log have not been seen for the last 47 seconds, which is more than the yellow threshold (45 seconds). This typically occurs when indexing or forwarding are falling behind or are blocked…

【15】linux命令每日分享——head命令查看文件

大家好&#xff0c;这里是sdust-vrlab&#xff0c;Linux是一种免费使用和自由传播的类UNIX操作系统&#xff0c;Linux的基本思想有两点&#xff1a;一切都是文件&#xff1b;每个文件都有确定的用途&#xff1b;linux涉及到IT行业的方方面面&#xff0c;在我们日常的学习中&…

德鲁特金属导电理论(Drude)

德鲁特模型的重要等式 首先我们建立德鲁特模型的重要等式 我们把原子对于电子的阻碍作用&#xff0c;用一个冲量近似表示出来 在式子 首先定义一个等效加速度 由于 我们可以得到电导率的微观表达式 在交流电环境中 电场的表达式 借鉴上一问的公式 我们可以列出这样的表达式…

1.5 全概率公式和贝叶斯公式

1.5.1 全概率公式在处理复杂事件的概率时&#xff0c;我们经常将这个复杂事件分解为若千个互不相容的较简单的事件之和&#xff0c;先求这些简单事件的概率&#xff0c;再利用有限可加性得到所求事件的概率,这种方法就是全概率公式的思想方法全概率公式是概率论中的一个非常重要…

【CSS】CSS 复合选择器 ② ( 子元素选择器 | 交集选择器 )

文章目录一、子元素选择器1、语法说明2、代码分析3、代码示例二、交集选择器1、语法说明2、代码示例一、子元素选择器 1、语法说明 子元素选择器 可以选择 某个基础选择器 选择出的 元素组 的 直接子元素 ( 亲儿子元素 ) 中 使用基础选择器 选择 元素 ; 子元素选择器语法 : 父选…

【JAVA程序设计】(C00112)基于Springboot+Thymeleaf的在线购物商城——有文档

基于SpringbootThymeleaf的在线购物商城——有文档项目简介项目获取开发环境项目技术运行截图运行视频项目简介 基于Springbootthymeleaf框架的在线购物商城系统&#xff0c;本系统共分为二个角色&#xff1a;管理员和用户 管理员角色包含以下功能&#xff1a; 商品管理、商品…

DELL-Vostro-5468电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板DELL-Vostro-5468处理器Intel Core i3-7100U 2.40 GHz, 3M Cache已驱动内存Samsung 8GB DDR4-2133MHz已驱动硬盘TOPMORE CAPRICORNUS NVMe 1TB已驱动显卡Intel HD Graphics 620已驱动声卡Realtek ALC2…

Linux指令——文件与权限

一&#xff0c;文件目录管理命令 ls 命令描述&#xff1a; ls命令用于显示指定工作目录下的内容。 命令格式&#xff1a;ls [参数] [目录名]。 参数说明&#xff1a; 参数 说明 -a 显示所有文件及目录&#xff08;包括隐藏文件&#xff09; -l 将文件的权限、拥有者、…

详解七大排序算法

对于排序算法&#xff0c;是我们在数据结构阶段&#xff0c;必须要牢牢掌握的一门知识体系&#xff0c;但是&#xff0c;对于排序算法&#xff0c;里面涉及到的思路&#xff0c;代码……各种时间复杂度等&#xff0c;都需要我们&#xff0c;记在脑袋瓜里面&#xff01;&#xf…

【LeetCode每日一题】——680.验证回文串 II

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 贪心算法 二【题目难度】 简单 三【题目编号】 680.验证回文串 II 四【题目描述】 给你一个字…

终端仿真器、协议分析器和 IO 监视器:IO Ninja 5.3.1 Crack

欢迎使用 IO Ninja 您的一站式终端仿真器、协议分析器和 IO 监视器 IO Ninja是一款专业的一体化终端仿真器、嗅探器和协议分析器。IO Ninja 是高度模块化的&#xff0c;并且具有适用于您可能跨越的大多数传输和协议的插件——网络&#xff08;、、、、、等&#xff09;、串行&…

实验3 设计模式实验2

实验内容: 1. 某Web 性能测试软件中包含一个虚拟用户生成器(Virtual User Generator)。 为了避免出现生成的虚拟用户数量不一致&#xff0c;该测试软件在工作时只允许启动唯一 一个虚拟用户生成器。采用单例模式设计该虚拟用户生成器&#xff0c;绘制类图并使用饿 汉式单例、双…

汇编语言程序设计(四)之汇编指令

系列文章 汇编语言程序设计&#xff08;一&#xff09; 汇编语言程序设计&#xff08;二&#xff09;之寄存器 汇编语言程序设计&#xff08;三&#xff09;之汇编程序 汇编指令 1. 数据传输指令 指令包括&#xff1a;MOV、XCHG、XLAT、LEA、LDS、LES、PUSH、POP、PUSHF、LA…

关闭应用程序遥测,禁止Windows收集用户信息

目录 1. 先创建还原点&#xff0c;防止意外 2. 界面设置 3. 服务 (1) GPEdit.msc - 本地计算机策略 - 计算机配置 - 管理模板 - Windows 组件 - 应用程序兼容性 - 关闭应用程序遥测 - 已启用 (2) GPEdit.msc - 本地计算机策略 - 计算机配置 - 管理模板 - Windows 组件 - 数…

aws apigateway 使用restapi集成lambda

参考资料 代理集成&#xff0c;https://docs.aws.amazon.com/zh_cn/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html非代理集成&#xff0c;https://docs.aws.amazon.com/zh_cn/apigateway/latest/developerguide/getting-started-…

Android 面试必备:高工必问Binder机制~

面试可能会问到的问题 从IPC的方式问到Binder的优势为什么zygote跟其他服务进程的通讯不使用BinderBinder线程池和Binder机制 等等这些问题都是基于你对Binder的理解还有对其他IPC通讯的理解 IPC方式有多少种 传统的IPC方式有Socket、共享内存、管道、信号量等安卓特有的是Bi…

Spring AOP —— 详解、实现原理、简单demo

目录 一、Spring AOP 是什么&#xff1f; 二、学习AOP 有什么作用&#xff1f; 三、AOP 的组成 3.1、切面&#xff08;Aspect&#xff09; 3.2、切点&#xff08;Pointcut&#xff09; 3.3、通知&#xff08;Advice&#xff09; 3.4、连接点 四、实现 Spring AOP 一个简…

linux系统安装学习

文章目录一、系统安装二、命令格式和帮助三、文件目录操作命令创建目录四、cat查看文件内容、合并文件sudo获得root权限总结一、系统安装 二、命令格式和帮助 三、文件目录操作命令 ls查看目录文件 -a 显示隐藏的文件 -l 以列表的形式显示 -h 以人性化的方式显示文件内容大小 …