什么是动态代理?

发布时间:2023年12月23日

目录

一、为什么需要代理?

二、代理长什么样?

三、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?

文章来源:https://blog.csdn.net/qq_74312711/article/details/135164663
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。