目录

一、为什么需要代理?

二、代理长什么样?

三、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),可以用来记录操作日志、公共字段自动填充等实现。

下面介绍如何实现记录操作日志

导入依赖

org.springframework.bootspring-boot-starter-aop
com.alibabafastjson1.2.83

数据库表设计

create table if not exists tb_operate_log(idbigint auto_increment comment '主键id'primary key,class_namevarchar(128)not null comment '类名',method_name varchar(128)not null comment '方法名',method_paramvarchar(1024) not null comment '方法参数',method_return varchar(2048) not null comment '方法返回值',cost_time bigintnot null comment '方法运行耗时;单位:ms',create_username varchar(16) not null comment '方法调用者用户名',create_time datetimenot null comment '创建时间',update_time datetimenot 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@NoArgsConstructorpublic 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 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@RequiredArgsConstructorpublic 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: */@Mapperpublic interface OperateLogMapper {void insertOperateLog(OperateLogEntity operateLog);}
 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});

通过给controller层的接口方法加上@RecordLog(OperateLog.RECORD)注解即可实现记录操作日志,方便以后的问题排查。

《AOP如何实现公共字段自动填充》

https://blog.csdn.net/qq_74312711/article/details/134702905?spm=1001.2014.3001.5502