什么是动态代理?
目录
一、为什么需要代理?
二、代理长什么样?
三、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中的一个重要的新特性.…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
