目录
一、为什么需要代理?
二、代理长什么样?
三、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;
@Override
public 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;
}
@Override
public 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() { // 调用并增强原始方法
@Override
public 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 id
unique (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 mapper
PUBLIC "-//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