当前位置: 首页 > news >正文

Java动态代理Aop的好处

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配置文件,代理部分),将配置器直接配置在拦截器列表中即可。

相关文章:

Java动态代理Aop的好处

1. 预备知识-动态代理 1.1 什么是动态代理 动态代理利用Java的反射技术(Java Reflection)生成字节码&#xff0c;在运行时创建一个实现某些给定接口的新类&#xff08;也称"动态代理类"&#xff09;及其实例。 1.2 动态代理的优势 动态代理的优势是实现无侵入式的代…...

各种存储性能瓶颈如何分析与优化?

【摘要】本文结合实践剖析存储系统的架构及运行原理&#xff0c;深入分析各种存储性能瓶颈场景&#xff0c;并提出相应的性能优化手段&#xff0c;希望对同行有一定的借鉴和参考价值。 【作者】陈萍春&#xff0c;现就职于保险行业&#xff0c;拥有多年的系统、存储以及数据备…...

Android StateFlow初探

Android StateFlow初探 前言&#xff1a; 最近在学习StateFlow&#xff0c;感觉很好用&#xff0c;也很神奇&#xff0c;于是记录了一下. 1.简介&#xff1a; StateFlow 是一个状态容器式可观察数据流&#xff0c;可以向其收集器发出当前状态更新和新状态更新。还可通过其 …...

Docker Compose初使用

简介 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是 工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&#xff09;以及容器&#xff08;cont…...

测试与FastAPI应用数据之间的差异

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 当使用两个不同的异步会话来测试FastAPI应用程序与数据库的连接时&#xff0c;可能会出现以下错误&#xff1a; 在测试中&#xff0c;在数据库中创建了一个对象&#x…...

WebStorm 2023年下载、安装教程、亲测有效

文章目录 简介安装步骤常用快捷键 简介 WebStorm 是JetBrains公司旗下一款JavaScript 开发工具。已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源&#xff0c;继承了IntelliJ IDEA强大的JS部分的…...

k8s储存卷

卷的类型 In-Tree存储卷插件 ◼ 临时存储卷 ◆emptyDir ◼ 节点本地存储卷 ◆hostPath, local ◼ 网络存储卷 ◆文件系统&#xff1a;NFS、GlusterFS、CephFS和Cinder ◆块设备&#xff1a;iSCSI、FC、RBD和vSphereVolume ◆存储平台&#xff1a;Quobyte、PortworxVolume、Sto…...

【解决Win】“ 无法打开某exe提示无法成功完成操作,因为文件包含病毒或潜在的垃圾软件“

在下载某个应用程序&#xff0c;打开时出现了“无法成功完成操作因为文件包含病毒或潜在垃圾”的提示&#xff0c;遇到这个情况怎么解决&#xff1f; 下面为大家分享故障原因及具体的处理方法。 故障原因 是由于杀毒 防护等原因引起的。 解决方案 打开Windows 安全中心 选择…...

SpringBoot调用ChatGPT-API实现智能对话

目录 一、说明 二、代码 2.1、对话测试 2.2、单次对话 2.3、连续对话 2.4、AI绘画 一、说明 我们在登录chatgpt官网进行对话是不收费的&#xff0c;但需要魔法。在调用官网的API时&#xff0c;在代码层面上使用&#xff0c;通过API KEY进行对话是收费的&#xff0c;不过刚…...

element-table出现错位解决方法

先看示例图&#xff0c;这个在开发中还是很常遇到的&#xff0c;在table切换不同数据时或者切换页面时&#xff0c;容易出现&#xff1a; 解决方法很简单&#xff0c;官方有提供方法&#xff1a; 我们可以在重新渲染数据后&#xff1a; this.$nextTick(() > {this.$refs.…...

DC电源模块具有不同的安装方式和安全规范

BOSHIDA DC电源模块具有不同的安装方式和安全规范 DC电源模块是将低压直流电转换为需要的输出电压的装置。它们广泛应用于各种领域和行业&#xff0c;如通信、医疗、工业、家用电器等。安装DC电源模块应严格按照相关的安全规范进行&#xff0c;以确保其正常运行和安全使用。 D…...

zabbix自定义监控、钉钉、邮箱报警

目录 一、实验准备 二、安装 三、添加监控对象 四、添加自定义监控项 五、监控mariadb 1、添加模版查看要求 2、安装mariadb、创建用户 3、创建用户文件 4、修改监控模版 5、在上述文件中配置路径 6、重启zabbix-agent验证 六、监控NGINX 1、安装NGINX&#xff0c…...

短信、邮箱验证码本地可以,部署到服务器接口却不能使用

应对公司双验证要求&#xff0c;对本系统做邮箱、短信验证码登录&#xff0c;本地开发正常发送&#xff0c;到服务器上部署却使用失败&#xff0c;已全部解决&#xff0c;记录坑。 一、nginx拦截 先打开你的服务器 nginx.conf 看看有没有做接口拦截。&#xff08;本地可能做Sp…...

Java web基础知识

Servlet Servlet是sun公司开发的动态web技术 sun在API中提供了一个接口叫做 Servlet &#xff0c;一个简单的Servlet 程序只需要完成两个步骤 编写一个实现了Servlet接口的类 把这个Java部署到web服务器中 一般来说把实现了Servlet接口的java程序叫做&#xff0c;Servlet 初步…...

【Linux学习】01Linux初识与安装

Linux&#xff08;B站黑马&#xff09;学习笔记 01Linux初识与安装 文章目录 Linux&#xff08;B站黑马&#xff09;学习笔记前言01Linux初识与安装操作系统简述Linux初识虚拟机介绍安装VMware Workstation虚拟化软件VMware中安装CentOS7 Linux操作系统下载CentOS操作系统VMwa…...

android 将数据库中的 BLOB 对象动态加载为 XML,并设置到 Android Activity 的内容视图上

以下是一个示例代码,演示如何将数据库中的 BLOB 对象动态加载为 XML,并设置到 Android Activity 的内容视图上: ```java import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import and…...

Android12之强弱智能指针sp/wp循环引用死锁问题(一百六十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…...

springboot自定义Json序列化返回,实现自动转换字典值

自定义序列化 原理 当你使用Spring Boot的Spring Data或者Spring MVC等组件来处理JSON序列化时&#xff0c;Spring框架会在需要将Java对象转换为JSON字符串时调用JsonSerializer。这通常发生在控制器方法返回JSON响应时&#xff0c;或者在将对象保存到数据库等操作中。 // 注册…...

Lostash同步Mysql数据到ElasticSearch(二)logstash脚本配置和常见坑点

1. logstash脚本编写&#xff08;采用单文件对应单表实例&#xff09; 新建脚本文件夹 cd /usr/local/logstash mkdir sql & cd sql vim 表名称.conf #如: znyw_data_gkb_logstash.conf 建立文件夹&#xff0c;保存资源文件更新Id mkdir -p /data/logstash/data/last_r…...

兔兔答题企业版1.0.0版本全网发布,同时开源前端页面到unicloud插件市场

项目说明 兔兔答题是用户端基于uniapp开发支持多端适配&#xff0c;管理端端采用TypeScriptVue3.jselement-plus&#xff0c;后端采用THinkPHP6PHP8Golang开发的一款在线答题应用程序。 问题反馈 线上预览地址 相关问题可以通过下方的二维码&#xff0c;联系到我。了解更多 …...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...