Spring AOP基础动态代理基于JDK动态代理实现
目录
1. 预备知识-动态代理
1.1 什么是动态代理
1.2 动态代理的优势
1.3 基于JDK动态代理实现
2. AOP
2.1 基本概念
2.2 AOP带来的好处
3. Spring AOP
3.1 前置通知
3.2 后置通知
3.3 环绕通知
3.4 异常通知
3.5 适配器
1. 预备知识-动态代理
1.1 什么是动态代理
动态代理利用Java的反射技术(Java Reflection)生成字节码,在运行时创建一个实现某些给定接口的新类(也称"动态代理类")及其实例。
1.2 动态代理的优势
动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)
spring中的AOP是动态代理使用的经典场景。
1.3 基于JDK动态代理实现
在基于JDK的动态代理的实现中有两个重要的类:InvocationHandler, Proxy
- InvocationHandler
是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。 - Proxy
JDK中动态生成代理类的工具类
一个动态代理的示例:
定义一个接口(基于JDK的动态代理只能使用接口)
public interface ISubject {void hello(String param);
}
为接口定义实现类
public class SubjectImpl implements ISubject {@Overridepublic void hello(String param) {System.out.println("hello " + param);}
}
实现一个代理类:
public class JDKProxy implements InvocationHandler {private Object target;public JDKProxy(Object target) {this.target = target;}//创建代理public Object newProxy() {return (ISubject)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("---------- 在业务方法调用之前可以进行前置增强 ------------");//利用反射机制调用方法,invoke为返回值,如果没有返回nullObject invoke = method.invoke(target, args);System.out.println("---------- 在业务方法调用之前可以进行后置增强 ------------");return invoke;}}
编写代理类实际的调用,利用Proxy类创建代理之后的Subject类
public class JDKProxyDemo {public static void main(String[] args) {ISubject subject = new SubjectImpl();JDKProxy subjectProxy = new JDKProxy(subject);ISubject proxyInstance = (ISubject)subjectProxy.newProxy();proxyInstance.hello("world");}}
运行结果:
---------- 在业务方法调用之前可以进行前置增强 ------------
hello world
---------- 在业务方法调用之前可以进行后置增强 ------------
2. AOP
2.1 基本概念
- 连接点 (Joinpoint)
程序执行过程中明确的点,如方法的调用,或者异常的抛出. - 目标(Target)
被通知(被代理)的对象,如上例中的SubjectImpl - 通知(Advice)
在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理),可以理解为AOP真正要实现的功能 - 代理(Proxy)
将通知应用到目标对象后创建的对象(代理=目标+通知),请注意:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的,如上例中的JDKProxy - 切入点(Pointcut)
多个连接点的集合,定义了通知应该应用到那些连接点。也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序 - 适配器(Advisor)
适配器=通知(Advice)+切入点(Pointcut)
AOP运行原理:目标对象只负责业务逻辑,通知只负责AOP增强逻辑(如日志,数据验证等),而代理对象则将业务逻辑而AOP增强代码组织起来(组织者)
2.2 AOP带来的好处
AOP是公用的框架代码放置的理想地方,将公共代码与业务代码分离,使我们在处理业务时可以专心的处理业务。
伪代码:
public void doSameBusiness (long lParam,String sParam){// 记录日志log.info("调用 doSameBusiness方法,参数是:"+lParam);// 输入合法性验证if (lParam<=0){throws new IllegalArgumentException("xx应该大于0");}if (sParam==null || sParam.trim().equals("")){throws new IllegalArgumentException("xx不能为空");}// 异常处理try{ 真正的业务处理}catch(...){}catch(...){}// 事务控制tx.commit();}
通过使用AOP我们可以将日志记录,数据合法性验证,异常处理等功能放入AOP中,那么在编写业务时就可以专心实现真正的业务逻辑代码。
3. Spring AOP
在spring中org.springframework.aop.framework.ProxyFactoryBean用来创建代理对象,在一般情况下它需要注入一下三个属性:
- proxyInterfaces 代理应该实现的接口列表(List)
- interceptorNames 需要应用到目标对象上的通知Bean的名字
- target 目标对象 (Object)
准备工作:创建一个IBookService接口及其实现类,用于演示spring AOP开发示例:
public interface IBookService {// 购书public boolean buy(String userName, String bookName, Double price);// 发表书评public void comment(String userName, String comments);}
public class BookServiceImpl implements IBookService {private Logger logger = LoggerFactory.getLogger(this.getClass());public BookServiceImpl() {super();}public boolean buy(String userName, String bookName, Double price) {//logger.info("userName={},bookName={},price={}", userName, bookName, price);// 通过控制台的输出方式模拟购书logger.info(userName + " buy " + bookName + ", spend " + price);return true;}public void comment(String userName, String comments) {logger.info(userName + " say:" + comments);}}
将service配置到spring配置文件中,以便于被spring管理,按自己的实际情况配置
<!-- 目标 --><bean id="bookServiceTarget" class="com.zking.sp02.impl.BookServiceImpl"/>
3.1 前置通知
前置通知需要实现org.springframework.aop.MethodBeforeAdvice,前置通知将在目标对象调用前调用。示例实现购书系统AOP方式实现日志,简单打印调用的方法及参数
1)首先实现一个前置通知类,实现接口MethodBeforeAdvice,并实现接口中的before方法
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {String s = "[前置通知]: " + this.getClass() + "." + method.getName() + "将被调用,参数为:" + Arrays.toString(args);logger.info(s);}}
2)将实现的前置通知配置到Spring.xml中,一遍与被spring管理。需要根据自己的实际情况配置。
<bean id="myMethodBeforeAdvice" class="com.zking.springdemo.aop.MyMethodBeforeAdvice"/>
3)现在需要解决如何将通知和目标联系起来,需要一个组织者 - 代理
<bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean"><!-- 配置代理目标 --><property name="target" ref="bookServiceTarget"/><!-- 配置拦截器列表,拦截器就是通知 --><property name="interceptorNames"><list><value>myMethodBeforeAdvice</value></list></property><!-- 代理要实现的接口,代理类与被代理类需要实现相同接口 --><property name="proxyInterfaces"><list><value>com.zking.springdemo.aop.IBookService</value></list></property></bean>
写一个测试类,测试前置通知
public class Demo {public static void main(String[] args) {ApplicationContext <u>cxt</u> = new ClassPathXmlApplicationContext("/spring.xml");IBookService bookService = (IBookService)cxt.getBean("bookService");System.out.println(bookService.getClass().getName()); bookService.buy("zs", "hlm", 10D);}}
3.2 后置通知
在连接点正常完成后执行的通知。定义的后置通知类需要实org.springframework.aop.AfterReturningAdvice
示例:在线购书系统中,要求不修改BookServiceImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利10元,简单打印类似于“[后置通知] 返利10元”即可。开发步骤与前置通知类似
1) 编写一个后置通知实现类
public class MyAfterReturnAdvice implements AfterReturningAdvice {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { logger.info("[后置通知]: 返利10元"); }
}
2) 将后置通知实现类配置到spring配置文件中,以便于spring管理
<bean id="myAfterReturnAdvice" class="com.zking.springdemo.aop.MyAfterReturnAdvice"/>
3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将后置通知也配置到拦截器列表当中即可。
<!-- 配置拦截器列表,拦截器就是通知 -->
<property name="interceptorNames"><list><!-- 前置通知 --><value>myMethodBeforeAdvice</value><!-- 后置通知 --><value>myAfterReturnAdvice</value></list>
</property>
运行上例已经实现的测试类,查看后置通知的运行效果。
3.3 环绕通知
包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能很强大。自定义的环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口。
示例:在环绕通知中输出日志和返回值
1)实现一个环绕通知,该类要实现MethodInterceptor接口
public class MyMethodInterceptor implements MethodInterceptor {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//获取目标对象Object target = invocation.getThis();//获取参数Object[] args = invocation.getArguments();//获取方法Method method = invocation.getMethod();logger.info("[环绕通知] 前:将调用{}.{}方法,参数为{}",target.getClass(),method.getName(), Arrays.toString(args));//调用目标对象上的方法 Object val = invocation.proceed();logger.info("[环绕通知] 后,已调用{}.{}, 返回值:{}", target.getClass(),method.getName(), val);return val;}}
2)在spring的配置文件中配置环绕通知,以便于spring管理
<bean id="myMethodInterceptor" class="com.zking.springdemo.aop.MyMethodInterceptor"/>
3)解决如何将通知和目标联系起来,在实现前置通知时已经配置了代理对象,现在只要将环绕通知也配置到拦截器列表当中即可。
<!-- 配置拦截器列表,拦截器就是通知 -->
<property name="interceptorNames"><list><!--前置通知--><value>myMethodBeforeAdvice</value><!--后置通知--><value>myAfterReturnAdvice</value><!--环绕通知--><value>myMethodInterceptor</value></list>
</property>
运行上例已经实现的测试类,查看后置通知的运行效果。
3.4 异常通知
异常通知需要实现ThrowsAdvice接口,这个通知会在方法抛出异常退出时执行,与以上演示的前置、后置、环绕通知不同,主要有一下特点:
- 这个接口里面没有定义方法,要求我们的类必须实现afterThrows这个方法
- 以异常类型作为参数,无返回值
示例
1)定义一个自定义异常,继承RuntimeException运行时异常
public class PriceException extends RuntimeException {public PriceException() {super();}public PriceException(String message, Throwable cause) {super(message, cause);}public PriceException(String message) {super(message);}public PriceException(Throwable cause) {super(cause);}
}
2)创建异常通知类,该类实现ThrowsAdvice接口,并实现afterThrowing方法
public class MyThrowsAdvice implements ThrowsAdvice {private Logger logger = LoggerFactory.getLogger(this.getClass());//以异常类型作为参数,无返回值public void afterThrowing(PriceException e) {logger.info("程序发生了PriceException异常");}}
剩下的步骤是将异常通知配置到spring配置文件中,及在代理配置中加入异常通知的配置,可参考上面的环绕通知等示例。
3.5 适配器
适配器, 通过正则表达式来定义方法切入点,也就是说定义哪些方法将被拦截器处理。适配器=通知(Advice)+切入点(Pointcut)。
在配置适配器时需要使用org.springframework.aop.support.RegexpMethodPointcutAdvisor
配置适配器示例:
<!-- 配置适配器 -->
<bean id="myAdisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"><!-- 定义正则表达式,定义需要拦截的方法名 ,本例定义了所有以buy结尾的方法 --><property name="patterns"><list><value>.*buy</value></list></property><!-- 定义由那个通知(或者叫拦截器)来处理匹配的方法 --><property name="advice"><ref bean="myAfterReturnAdvice"/></property>
</bean>
在代理中使用刚刚配置的适配器
<!-- 将直接使用后置拦截器 ,改为使用适配器 -->
<!-- <value>myMethodAfterReturnAdvice</value> --><!-- 通过适配器使用后置拦截器 -->
<value>myAdisor</value>
修改代理中的拦截器列表(spring配置文件,代理部分),将配置器直接配置在拦截器列表中即可。
相关文章:
Spring AOP基础动态代理基于JDK动态代理实现
目录 1. 预备知识-动态代理 1.1 什么是动态代理 1.2 动态代理的优势 1.3 基于JDK动态代理实现 2. AOP 2.1 基本概念 2.2 AOP带来的好处 3. Spring AOP 3.1 前置通知 3.2 后置通知 3.3 环绕通知 3.4 异常通知 3.5 适配器 1. 预备知识-动态代理 1.1 什么是动态代理…...

第一章 计算机系统概述 五、中断和异常、系统调用
目录 一、中断的作用 二、中断的类型 1、内中断(异常) 2、外中断 三、中断机制的基本原理 四、系统调用 1、定义: 2、与库函数的区别 3、按功能分类 4、作用 一、中断的作用 1、“中断”是让操作系统内核夺回CPU使用权的唯一途径 …...

【C语言】文件操作(上)
一.什么是文件 文件是磁盘上的文件,文件中存放的数据不随程序的退出而销毁. 二.文件的打开与关闭 1.文件指针 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等&…...

【Linux】让笔记本发挥余热,Ubuntu20.04设置WiFi热点
Ubuntu20.04设置WiFi热点 由于卧室距离客厅较远,wifi信号太弱,体验极差。鉴于卧室的笔记本电脑是通过网线连接的客厅路由器,因此考虑将这台老破笔记本作为“路由器”,以便发挥它的余热。实验证明,上网速度提升数十倍&a…...

【云平台】遥感地信云平台收录
文章目录 国内1 航天宏图PIE-Engine2 商汤科技3 AI Earth4 EarthDataMiner国外结语国内 1 航天宏图PIE-Engine https://engine.piesat.cn/live-show-list 在这里插入图片描述 2 商汤科技 https://senseearth-cloud.com/map 3 AI Earth https://engine-aiearth.aliyun.com…...
23种设计模式之---单例模式
闲来无事学一下设计模式,希望这23种可以一直更下去,什么时候能更完呢,也许一个月,也许一年,也许断更 设计模式六大原则 本文是23篇的第一篇,在学习设计模式之前,你需要了解下六大原则。 1、开…...

蓝桥杯官网练习题(纸牌三角形)
题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 A,2,3,4,5,6,7,8,9 共 99 张纸牌排成一个正三角形(A 按 1 计算)。要求每个边的和相等。 下图就是一种排法。 这样的排法可能会有很多。 如果…...

一辆新能源汽车的诞生之旅:比亚迪常州工厂探营
作为在新能源汽车领域首屈一指的国产品牌,比亚迪近年来可以说是捷报频传,高奏凯歌。 以比亚迪常州工厂为例,据介绍该工厂当初规划设计时定下的生产目标,是年产量能够达到20万辆。然而在2023年上半年,该工厂光是主要销往…...

【算法专题突破】双指针 - 最大连续1的个数 III(11)
目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后: 1. 题目解析 题目链接:1004. 最大连续1的个数 III - 力扣(Leetcode) 这道题不难理解,其实就是求出最长的连续是1的子数组, 但是,他支…...
java实现备忘录模式
备忘录模式是一种行为设计模式,它允许您捕获一个对象的内部状态,并在稍后的时间点将其恢复。这对于需要撤销操作或恢复到先前状态的应用程序非常有用。以下是在 Java 中实现备忘录模式的一般步骤: 创建一个原发器类(Originator&am…...

aardio语言的通用数据表维护
import win.ui; /*DSG{{*/ var winform win.form(text"通用数据表维护";right617;bottom427;bgcolor15780518) winform.add( buttonAdd{cls"button";text"增加空行";left469;top40;right564;bottom80;flat1;z2}; buttonDel{cls"button&quo…...
手写RPC框架--7.封装响应
RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧) RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧) 封装响应 封装响应a.封装响应b.请求id生成器(雪花算法)c.抽象序列化d.建立序列化工厂e.hessian的序列化方式(拓展) 封装响应 a.封装响应 在core模块…...

Linux入门教程||Linux系统目录结构
登录系统后,在当前命令窗口下输入命令: ls / 你会看到如下图所示: 树状目录结构: 以下是对这些目录的解释: /bin: bin是Binary的缩写, 这个目录存放着最经常使用的命令。 /boot: 这里存放的是启动Linux时…...
LeetCode 88. 合并两个有序数组
文章目录 一、题目二、C# 题解 一、题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意&a…...

C语言实现扫雷小游戏
1.首先扫雷游戏要存储布置好的雷信息,需要一个二维数组 不是雷放* 雷:# 不是雷:0 雷:1 2. 给2个二维数组 9*9 一个存放雷的信息,一个存放布置好雷的信息 3.为了防止在统计坐标周围的…...

【linux基础(五)】Linux中的开发工具(上)---yum和vim
💓博主CSDN主页:杭电码农-NEO💓 ⏩专栏分类:Linux从入门到开通⏪ 🚚代码仓库:NEO的学习日记🚚 🌹关注我🫵带你学更多操作系统知识 🔝🔝 Linux中的开发工具 1. 前言2.…...

C++学习之list的实现
在了解学习list实现之前我们首先了解一下关于迭代器的分类: 按功能分类: 正向迭代器 反向迭代器 const正向迭代器 const反向迭代器 按性质分类: 单向迭代器 只能 例如单链表 双向迭代器 可,也可-- 例如双…...
一种高效且节约内存的聚合数据结构的实现
一种高效且节约内存的聚合数据结构的实现 在特定的场景中,特殊定制数据结构能够得到更加好的性能且更节约内存。 聚合函数GroupArray的问题 GroupArray聚合函数是将分组内容组成一个个数组,例如下面的例子: SELECT groupArray(concat(ABC…...

机器学习(10)---特征选择
文章目录 一、概述二、Filter过滤法2.1 过滤法说明2.2 方差过滤2.3 方差过滤对模型影响 三、相关性过滤3.1 卡方过滤3.2 F检验3.3 互信息法3.4 过滤法总结 四、Embedded嵌入法4.1 嵌入法说明4.2 以随机森林为例的嵌入法 五、Wrapper包装法5.1 包装法说明5.2 以随机森林为例的包…...

Python之数据库(MYSQL)连接
一)数据库SQL语言基础 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...