什么是动态代理?
目录
一、为什么需要代理?
二、代理长什么样?
三、Java通过什么来保证代理的样子?
四、动态代理实现案例
五、动态代理在SpringBoot中的应用
导入依赖
数据库表设计
OperateLogEntity实体类
OperateLog枚举
RecordLog注解
上下文相关类
OperateLogAspect切面类
OperateLogMapper
一、为什么需要代理?
代理可以无侵入式地给对象增强其他的功能
例如以下方法
public static void playGame() {System.out.println("玩游戏");
}
现在要对这个方法进行增强,在玩游戏之前要先吃饭,玩完游戏后要睡觉。
下面这种修改方法就是侵入式修改,对原始的方法进行修改。缺点是会使代码变得繁琐,扩展性变差。原本playGame()方法就是用来玩游戏的,吃饭和睡觉不属于玩游戏的范畴。
public static void playGame() {System.out.println("吃饭");System.out.println("玩游戏");System.out.println("睡觉");
}
为什么需要代理?
代理就是调用playGame()方法,并且在调用之前先吃饭,玩完游戏后再睡觉。
类似于下面这种修改方式
public class Test {public static void main(String[] args) {action();}public static void action() {System.out.println("吃饭");playGame();System.out.println("睡觉");}public static void playGame() {System.out.println("玩游戏");}
}
我们并没有直接调用playGame()方法,而是通过action()方法间接调用playGame()方法。
所以代理的目的就是在调用方法时,能在调用前或者调用后做一些事,从而达到在不修改原始方法的基础上,对原始方法进行增强。
当然我们要实现代理并不是用以上的方法,上面的案例只是解释代理的作用是什么。

二、代理长什么样?
代理里面就是对象要被代理的方法
简单理解,代理就是一个对象,这个对象里面有要被代理的方法。
被代理对象里面有playGame()方法,代理对象里面也有playGame()方法,并且是增强后的playGame()方法。
也就是说我们直接从代理对象里调用方法就行了,代理就是一个对象。
那么如何获取代理对象呢?
代理对象应该长得和被代理对象差不多才行,所以我们可以根据被代理对象来创建代理对象。
三、Java通过什么来保证代理的样子?
上面说到要根据被代理对象来创建代理对象,既然两者是相似的,可以想到如果代理对象和被代理对象继承了同一个父类,两者不就相似了吗?
因为代理对象和被代理对象虽然都有playGame()方法,但是方法的实现不同,只是方法的名称是一样的。
那么代理对象和被代理对象应该实现同一个接口,接口中的方法也就是要被代理的方法。
四、动态代理实现案例
我们先定义一个OnePerson类,其中playGame()方法就是要代理的方法,所以我们再定义一个Person接口。Person接口里面写playGame()抽象方法,代理对象也要实现Person接口并且重写playGame()方法。
public class OnePerson implements Person {private String name;private String gender;private Integer age;@Overridepublic void playGame() {System.out.println(this.name + "正在玩游戏");}public OnePerson() {}public OnePerson(String name, String gender, Integer age) {this.name = name;this.gender = gender;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "OnePerson{" +"name='" + name + '\'' +", gender='" + gender + '\'' +", age=" + age +'}';}
}
public interface Person {void playGame();
}
ProxyUtils
定义生成代理对象的工具类
这里用到反射,Method调用的方法就是原始的方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ProxyUtils {public static Person createProxy(OnePerson onePerson) {return (Person) Proxy.newProxyInstance(ProxyUtils.class.getClassLoader(), // 类加载器new Class[]{Person.class}, // 接口,指定要代理的方法new InvocationHandler() { // 调用并增强原始方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = onePerson.getName();if (method.getName().equals("playGame")) {System.out.println(name + "在吃饭");}Object object = method.invoke(onePerson, args);if (method.getName().equals("playGame")) {System.out.println(name + "在睡觉");}return object;}});}
}
案例测试
public class Test {public static void main(String[] args) {OnePerson onePerson = new OnePerson("艾伦", "男", 15);Person person = ProxyUtils.createProxy(onePerson);person.playGame();}
}
测试结果

可以看到我们并没有在OnePerson类中修改playGame()方法,但是成功地对playGame()方法进行了增强。
五、动态代理在SpringBoot中的应用
动态代理在SpringBoot中就是面向切面编程(AOP),可以用来记录操作日志、公共字段自动填充等实现。
下面介绍如何实现记录操作日志
导入依赖
<!--AOP起步依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--fastjson-->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version>
</dependency>
数据库表设计
create table if not exists tb_operate_log
(id bigint auto_increment comment '主键id'primary key,class_name varchar(128) not null comment '类名',method_name varchar(128) not null comment '方法名',method_param varchar(1024) not null comment '方法参数',method_return varchar(2048) not null comment '方法返回值',cost_time bigint not null comment '方法运行耗时;单位:ms',create_username varchar(16) not null comment '方法调用者用户名',create_time datetime not null comment '创建时间',update_time datetime not null comment '更新时间',constraint idunique (id)
)comment '操作日志表';
OperateLogEntity实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;/*** @Description: 操作日志表实体类* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return:*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperateLogEntity {private Long id;private String className;private String methodName;private String methodParam;private String methodReturn;private Long costTime;private String createUsername;private LocalDateTime createTime;private LocalDateTime updateTime;
}
OperateLog枚举
/*** @Description: 日志操作类型* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return:*/
public enum OperateLog {//记录操作RECORD}
RecordLog注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Description: 注解,用于标识需要进行记录操作日志的方法* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return:*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordLog {//日志操作类型,RECORD记录操作OperateLog value();}
上下文相关类
ThreadLocal线程局部变量,将信息放入上下文,后面要用可以直接取出。
public class BaseContext {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void setContext(String context) {threadLocal.set(context);}public static String getContext() {return threadLocal.get();}public static void removeContext() {threadLocal.remove();}
}
OperateLogAspect切面类
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.Arrays;/*** @Description: 切面类,实现记录操作日志* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return:*/
@Aspect
@Component
@RequiredArgsConstructor
public class OperateLogAspect {private final OperateLogMapper operateLogMapper;/*** @Description: 切入点* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return: void*/@Pointcut("execution(* com.demo.controller.*.*(..)) && @annotation(com.demo.annotation.RecordLog)")public void recordLogPointcut() {}/*** @Description: 环绕通知,进行记录操作日志* @Author: 翰戈.summer* @Date: 2023/11/21* @Param: ProceedingJoinPoint* @Return: Object*/@Around("recordLogPointcut()")public Object recordLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//获取类名String className = proceedingJoinPoint.getTarget().getClass().getName();//获取方法名String methodName = proceedingJoinPoint.getSignature().getName();//获取方法参数Object[] args = proceedingJoinPoint.getArgs();String methodParam = Arrays.toString(args);Long begin = System.currentTimeMillis();// 方法运行开始时间Object result = proceedingJoinPoint.proceed();// 运行方法Long end = System.currentTimeMillis();// 方法运行结束时间//方法耗时Long costTime = end - begin;//获取方法返回值String methodReturn = JSONObject.toJSONString(result);String username = BaseContext.getContext();// 当前用户名LocalDateTime now = LocalDateTime.now();// 当前时间OperateLogEntity operateLog = new OperateLogEntity(null, className, methodName,methodParam, methodReturn, costTime, username, now, now);operateLogMapper.insertOperateLog(operateLog);return result;}
}
OperateLogMapper
import org.apache.ibatis.annotations.Mapper;/*** @Description: 操作日志相关的数据库操作* @Author: 翰戈.summer* @Date: 2023/11/21* @Param:* @Return:*/
@Mapper
public interface OperateLogMapper {void insertOperateLog(OperateLogEntity operateLog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.demo.mapper.OperateLogMapper"><!--记录操作日志--><insert id="insertOperateLog">insert into tb_operate_log (id, class_name, method_name, method_param,method_return, cost_time, create_username, create_time, update_time)values (null, #{className}, #{methodName}, #{methodParam},#{methodReturn}, #{costTime}, #{createUsername}, #{createTime}, #{updateTime});</insert>
</mapper>
通过给controller层的接口方法加上@RecordLog(OperateLog.RECORD)注解即可实现记录操作日志,方便以后的问题排查。
《AOP如何实现公共字段自动填充》
https://blog.csdn.net/qq_74312711/article/details/134702905?spm=1001.2014.3001.5502
相关文章:
什么是动态代理?
目录 一、为什么需要代理? 二、代理长什么样? 三、Java通过什么来保证代理的样子? 四、动态代理实现案例 五、动态代理在SpringBoot中的应用 导入依赖 数据库表设计 OperateLogEntity实体类 OperateLog枚举 RecordLog注解 上下文相…...
【OAuth2】:赋予用户控制权的安全通行证--原理篇
🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于OAuth2的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一.什么是OAuth? 二.为什么要用OAuth?…...
【K8s】2# 使用kuboard管理K8s集群(kuboard安装)
文章目录 安装 Kuboard v3部署计划 安装登录测试 安装 Kuboard v3 部署计划 在正式安装 kuboard v3 之前,需做好一个简单的部署计划的设计,在本例中,各组件之间的连接方式,如下图所示: 假设用户通过 http://外网IP:80…...
爬虫是什么?起什么作用?
【爬虫】 如果把互联网比作一张大的蜘蛛网,数据便是放于蜘蛛网的各个节点,而爬虫就是一只小蜘蛛,沿着网络抓取自己得猎物(数据)。这种解释可能更容易理解,官网的,就是下面这个。 爬虫是一种自动…...
代码随想录27期|Python|Day24|回溯法|理论基础|77.组合
图片来自代码随想录 回溯法题目目录 理论基础 定义 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。 回溯是递归的副产品,只要有递归就会有回溯。回溯函数也就是递归函数,指的都是一个函数。 基本问题 组合问题(无序&…...
mysql(49) : 大数据按分区导出数据
代码 import com.alibaba.gts.flm.base.util.Mysql8Instance;import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.u…...
阿里云ECS配置IPv6后,如果无法访问该服务器上的网站,可检查如下配置
1、域名解析到这个IPv6地址,同一个子域名可以同时解析到IPv4和IPv6两个地址,这样就可以给网站配置ip4和ipv6双栈; 2、在安全组规则开通端口可访问,设定端口后注意授权对象要特殊设置“源:::/0” 3、到服务器nginx配置处,增加端口…...
基于SSM的双减后初小教育课外学习生活活动平台的设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...
HTTP前端请求
目录 HTTP 请求1.请求组成2.请求方式与数据格式get 请求示例post 请求示例json 请求示例multipart 请求示例数据格式小结 3.表单3.1.作用与语法3.2.常见的表单项 4.session 原理5.jwt 原理 HTTP 请求 1.请求组成 请求由三部分组成 请求行请求头请求体 可以用 telnet 程序测…...
前端性能优化二十四:花裤衩模板第三方库打包
(1). 工作原理: ①. externals配置在所创建bundle时:a. 会依赖于用户环境(consumers environment)中的依赖,防止将某些import的包(package)打包到bundle中b. 在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)②. webpack会检测这些组件是否在externals中注…...
多维时序 | MATLAB实现BiTCN-Multihead-Attention多头注意力机制多变量时间序列预测
多维时序 | MATLAB实现BiTCN-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现BiTCN-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现BiTCN-Multihea…...
Qt的简单游戏实现提供完整代码
文章目录 1 项目简介2 项目基本配置2.1 创建项目2.2 添加资源 3 主场景3.1 设置游戏主场景配置3.2 设置背景图片3.3 创建开始按钮3.4 开始按钮跳跃特效实现3.5 创建选择关卡场景3.6 点击开始按钮进入选择关卡场景 4 选择关卡场景4.1场景基本设置4.2 背景设置4.3 创建返回按钮4.…...
SpringMVC之文件的下载
系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 SpringMVC之文件的下载 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、文件下载实现…...
计算机组成原理第6章-(算术运算)【下】
移位运算 对于有符号数的移位称为算术移位,对于无符号数的移位称为逻辑移位。 算术移位规则【极其重要】 对于正数的算术移位,且不管是何种机器数【原码、反码、补码】,移位后出现的空位全部填0。 而对于负数的算术移位,机器数不同,移位后的规则也不同。 对于负数的原…...
【开题报告】基于微信小程序的校园资讯平台的设计与实现
1.选题背景与意义 随着移动互联网的快速发展,微信成为了人们日常生活中不可或缺的工具之一。在校园生活中,学生们对于校园资讯的获取和交流需求也越来越高。然而,传统的校园资讯发布方式存在信息不及时、传播范围有限等问题,无法…...
VUE前端导出文件之file-saver插件
VUE前端导出文件之file-saver插件 安装 npm install file-saver --save # 如使用TS开发,可安装file-saver的TypeScript类型定义 npm install types/file-saver --save-dev如果需要保存大于 blob 大小限制的非常大的文件,或者没有 足够的 RAM࿰…...
【Earth Engine】协同Sentinel-1/2使用随机森林回归实现高分辨率相对财富(贫困)制图
目录 1 简介与摘要2 思路3 效果预览4 代码思路5 完整代码6 后记 1 简介与摘要 最近在做一些课题,需要使用Sentinel-1/2进行机器学习制图。 然后想着总结一下相关数据和方法,就花半小时写了个代码。 然后再花半小时写下这篇博客记录一下。 因为基于多次拍…...
C++ 检测 是不是 com组件 的办法 已解决
在日常开发中,遇到动态库和 com组件库的调用 无法区分。检测是否com组件的办法 在头部文件,引入文件 如果能编译成功说明是 com组件,至于动态库如何引入,还在观察中 最简单办法 regsvr32 TerraExplorerX.dll 是com 组件 regs…...
linux buffer的回写的触发链路
mark_buffer_dirty中除了会标记dirty到buffer_head->state、page.flag、folio->mapping->i_pages外,还会调用inode所在文件系统的dirty方法(inode->i_sb->s_op->dirty_inode)。然后为inode创建一个它所在memory group的wri…...
Lambda表达式超详解
目录 背景 Lambda表达式的用法 函数式接口 Lambda表达式的基本使用 语法精简 变量捕获 匿名内部类 匿名内部类中的变量捕获 Lambda的变量捕获 Lambda表达式在类集中的使用 Collection接口 List接口 Map接口 总结 背景 Lambda表达式是Java SE 8中的一个重要的新特性.…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
