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)生成字节码,在运行时创建一个实现某些给定接口的新类(也称"动态代理类")及其实例。 1.2 动态代理的优势 动态代理的优势是实现无侵入式的代…...
各种存储性能瓶颈如何分析与优化?
【摘要】本文结合实践剖析存储系统的架构及运行原理,深入分析各种存储性能瓶颈场景,并提出相应的性能优化手段,希望对同行有一定的借鉴和参考价值。 【作者】陈萍春,现就职于保险行业,拥有多年的系统、存储以及数据备…...
Android StateFlow初探
Android StateFlow初探 前言: 最近在学习StateFlow,感觉很好用,也很神奇,于是记录了一下. 1.简介: StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 …...
Docker Compose初使用
简介 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层,分别是 工程(project),服务(service)以及容器(cont…...
测试与FastAPI应用数据之间的差异
【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 当使用两个不同的异步会话来测试FastAPI应用程序与数据库的连接时,可能会出现以下错误: 在测试中,在数据库中创建了一个对象&#x…...
WebStorm 2023年下载、安装教程、亲测有效
文章目录 简介安装步骤常用快捷键 简介 WebStorm 是JetBrains公司旗下一款JavaScript 开发工具。已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的…...
k8s储存卷
卷的类型 In-Tree存储卷插件 ◼ 临时存储卷 ◆emptyDir ◼ 节点本地存储卷 ◆hostPath, local ◼ 网络存储卷 ◆文件系统:NFS、GlusterFS、CephFS和Cinder ◆块设备:iSCSI、FC、RBD和vSphereVolume ◆存储平台:Quobyte、PortworxVolume、Sto…...
【解决Win】“ 无法打开某exe提示无法成功完成操作,因为文件包含病毒或潜在的垃圾软件“
在下载某个应用程序,打开时出现了“无法成功完成操作因为文件包含病毒或潜在垃圾”的提示,遇到这个情况怎么解决? 下面为大家分享故障原因及具体的处理方法。 故障原因 是由于杀毒 防护等原因引起的。 解决方案 打开Windows 安全中心 选择…...
SpringBoot调用ChatGPT-API实现智能对话
目录 一、说明 二、代码 2.1、对话测试 2.2、单次对话 2.3、连续对话 2.4、AI绘画 一、说明 我们在登录chatgpt官网进行对话是不收费的,但需要魔法。在调用官网的API时,在代码层面上使用,通过API KEY进行对话是收费的,不过刚…...
element-table出现错位解决方法
先看示例图,这个在开发中还是很常遇到的,在table切换不同数据时或者切换页面时,容易出现: 解决方法很简单,官方有提供方法: 我们可以在重新渲染数据后: this.$nextTick(() > {this.$refs.…...
DC电源模块具有不同的安装方式和安全规范
BOSHIDA DC电源模块具有不同的安装方式和安全规范 DC电源模块是将低压直流电转换为需要的输出电压的装置。它们广泛应用于各种领域和行业,如通信、医疗、工业、家用电器等。安装DC电源模块应严格按照相关的安全规范进行,以确保其正常运行和安全使用。 D…...
zabbix自定义监控、钉钉、邮箱报警
目录 一、实验准备 二、安装 三、添加监控对象 四、添加自定义监控项 五、监控mariadb 1、添加模版查看要求 2、安装mariadb、创建用户 3、创建用户文件 4、修改监控模版 5、在上述文件中配置路径 6、重启zabbix-agent验证 六、监控NGINX 1、安装NGINX,…...
短信、邮箱验证码本地可以,部署到服务器接口却不能使用
应对公司双验证要求,对本系统做邮箱、短信验证码登录,本地开发正常发送,到服务器上部署却使用失败,已全部解决,记录坑。 一、nginx拦截 先打开你的服务器 nginx.conf 看看有没有做接口拦截。(本地可能做Sp…...
Java web基础知识
Servlet Servlet是sun公司开发的动态web技术 sun在API中提供了一个接口叫做 Servlet ,一个简单的Servlet 程序只需要完成两个步骤 编写一个实现了Servlet接口的类 把这个Java部署到web服务器中 一般来说把实现了Servlet接口的java程序叫做,Servlet 初步…...
【Linux学习】01Linux初识与安装
Linux(B站黑马)学习笔记 01Linux初识与安装 文章目录 Linux(B站黑马)学习笔记前言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序列化时,Spring框架会在需要将Java对象转换为JSON字符串时调用JsonSerializer。这通常发生在控制器方法返回JSON响应时,或者在将对象保存到数据库等操作中。 // 注册…...
Lostash同步Mysql数据到ElasticSearch(二)logstash脚本配置和常见坑点
1. logstash脚本编写(采用单文件对应单表实例) 新建脚本文件夹 cd /usr/local/logstash mkdir sql & cd sql vim 表名称.conf #如: znyw_data_gkb_logstash.conf 建立文件夹,保存资源文件更新Id mkdir -p /data/logstash/data/last_r…...
兔兔答题企业版1.0.0版本全网发布,同时开源前端页面到unicloud插件市场
项目说明 兔兔答题是用户端基于uniapp开发支持多端适配,管理端端采用TypeScriptVue3.jselement-plus,后端采用THinkPHP6PHP8Golang开发的一款在线答题应用程序。 问题反馈 线上预览地址 相关问题可以通过下方的二维码,联系到我。了解更多 …...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx
“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网(IIoT)场景中,结合 DDS(Data Distribution Service) 和 Rx(Reactive Extensions) 技术,实现 …...
Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...
基于Python的气象数据分析及可视化研究
目录 一.🦁前言二.🦁开源代码与组件使用情况说明三.🦁核心功能1. ✅算法设计2. ✅PyEcharts库3. ✅Flask框架4. ✅爬虫5. ✅部署项目 四.🦁演示效果1. 管理员模块1.1 用户管理 2. 用户模块2.1 登录系统2.2 查看实时数据2.3 查看天…...
【自然语言处理】大模型时代的数据标注(主动学习)
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构D 实验设计E 个人总结 A 论文出处 论文题目:FreeAL: Towards Human-Free Active Learning in the Era of Large Language Models发表情况:2023-EMNLP作者单位:浙江大…...
[electron]预脚本不显示内联script
script-src self 是 Content Security Policy (CSP) 中的一个指令,它的作用是限制加载和执行 JavaScript 脚本的来源。 具体来说: self 表示 当前源。也就是说,只有来自当前网站或者当前页面所在域名的 JavaScript 脚本才被允许执行。"…...
【立体匹配】:双目立体匹配SGBM:(1)运行
注:这是一个专题,我会一步步介绍SGBM的实现,按照我的使用和优化过程逐步改善算法,附带实现方法 系列文章【立体匹配】:双目立体匹配SGBM:(1)运行 【立体匹配】:双目立体匹…...
基于机器学习的智能故障预测系统:构建与优化
前言 在现代工业生产中,设备故障不仅会导致生产中断,还会带来巨大的经济损失。传统的故障检测方法依赖于人工巡检和定期维护,这种方式效率低下且难以提前预测潜在故障。随着工业物联网(IIoT)和机器学习技术的发展&…...
