什么是动态代理?

目录

一、为什么需要代理?

二、代理长什么样?

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/266351.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

车云TCP链路偶现链接失联问题排查

一、问题分析 1.1 车云tcp长连接分析排查 在15:37:32.039上线&#xff0c; 在 16:07:26.527下线&#xff0c;车云长连接通道稳定&#xff0c;且该期间心跳数据正常。 1.2 云向驾仓推送数据分析 在15:37:42 进行车辆接管后&#xff0c;该车辆下线&#xff0c;且无法在上线&am…

Java语法---使用sort进行排序

目录 一、升序 二、降序 &#xff08;1&#xff09;类实现接口 &#xff08;2&#xff09;匿名内部类 三、自定义排序规则 四、集合中的sort排序 &#xff08;1&#xff09;升序 &#xff08;2&#xff09;降序 &#xff08;3&#xff09;自定义排序 一、升序 升序排…

vue3+Ts

安装 命令含义可参考typescript文章中的自动编译部分 npm create vitelatest vuets-project -- --template vue-tsvs code插件 Vue Language Features (Volar)对.vue文件进行实时的类型错误反馈TypeScript Vue Plugin (Volar) 用于支持在TS中import*.vue文件&#xff08;mai…

08-JVM调优实战及常量池详解

文章目录 阿里巴巴Arthas详解Arthas使用场景Arthas使用 GC日志详解打印GC日志方法如何分析GC日志CMSG1 JVM参数汇总查看命令Class常量池与运行时常量池字面量符号引用 字符串常量池字符串常量池的设计思想三种字符串操作(Jdk1.7 及以上版本)字符串常量池位置字符串常量池设计原…

C语言——高精度除法

一、引子 1、引言 高精度除法相较于加减乘法更加复杂&#xff0c;它需要处理的因素更多&#xff0c;在这里我们先探讨高精度数除以低精度数&#xff0c;即大数除小数。这已满足日常所需&#xff0c;如需大数除以大数&#xff0c;可以使用专门的库&#xff0c;例如&#xff1a…

3.Redis持久化

文章目录 RDB&#xff08;Redis DataBase&#xff09;&#xff1a;RDB是什么&#xff1f;RDB能干嘛&#xff1f;RDB自动触发RDB手动触发RDB优点RDB缺点什么情况会触发RDB快照 AOF&#xff08;Append Only File&#xff09;&#xff1a;AOF是什么&#xff1f;AOF能干嘛&#xff…

【Hadoop】 YARN 运行过程/YARN设计目标

YARN 运行过程剖析YARN设计目标 YARN 运行过程剖析 一个Job在YARN中的处理过程&#xff1a; 客户端向RM提交一个job&#xff0c;进入RM中的调度器队列以供调度RM中的AppManager与NM协商协商好一个容器&#xff0c;以启动一个App Master实例App Master启动之后向RM注册并根据Jo…

刷题第五十一天 84. 柱状图中最大矩形

好难&#xff0c;看解析&#xff1a; # 双指针 class Solution:def largestRectangleArea(self, heights: List[int]) -> int:size len(heights)# 两个DP数列储存的均是下标indexmin_left_index [0] * sizemin_right_index [0] * sizeresult 0# 记录每个柱子的左侧第一…

量化服务器 - 后台挂载运行

服务器 - 后台运行 pip3命令被kill 在正常的pip命令后面加上 -no-cache-dir tmux 使用教程 https://codeleading.com/article/40954761108/ 如果你希望在 tmux 中后台执行一个 Python 脚本&#xff0c;你可以按照以下步骤操作&#xff1a; 启动 tmux: tmux这将会创建一个新…

Ubuntu 常用命令之 ps 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 ps命令是Linux下的一个非常重要的命令&#xff0c;它用于查看系统中的进程状态。ps是Process Status的缩写&#xff0c;可以显示系统中当前运行的进程的状态。 以下是一些常用的参数 a&#xff1a;显示所有进程&#xff08;包括…

【机器学习】决策树

参考课程视频&#xff1a;https://www.icourse163.org/course/NEU-1462101162?tid1471214452 1 概述 样子&#xff1a; 2 分裂 2.1 分裂原则 信息增益 信息增益比 基尼指数 3 终止 & 剪枝 3.1 终止条件 无需分裂 当前节点内样本同属一类 无法分裂 当前节点内…

【数据结构】四、串

目录 一、定义 二、表示与实现 定长顺序存储 堆分配存储 链式存储 三、BF算法 四、KMP算法 1.求next数组 方法一 方法二&#xff08;考试方法&#xff09; 2.KMP算法实现 方法一 方法二 3.nextval 4.时间复杂度 本节最重要的就是KMP算法&#xff0c;其他要求不高…

融资项目——swagger2的注解

1. ApiModel与ApiModelProperty(在实体类中使用) 如上图&#xff0c;ApiModel加在实体类上方&#xff0c;用于整体描述实体类。ApiModelProperty(value"xxx",example"xxx")放于每个属性上方&#xff0c;用于对属性进行描述。swagger2网页上的效果如下图&am…

c++内存池项目

文章目录 一、内存池介绍二、ThreadCache实现三、CentralCache实现四、PageCache实现五、回收内存六、大于256KB的内存申请与释放七、将new和delete换为定长内存池八、多线程环境下对比malloc进行基准测试九、使用基数树进行性能优化 一、内存池介绍 二、ThreadCache实现 下面…

Wordpress插件WP-Statistics无法识别来访IP国家和城市处理方法

Wordpress插件WP-Statistics&#xff0c;可以识别网站访问者的IP物理地址&#xff0c;统计出城市、国家&#xff0c;但最近发现都显示unknown/未知&#xff1a; 更新GeoIP数据库到最新还是不行&#xff1a; 偶然找到了之前能用的数据库&#xff0c;恢复回去&#xff0c;竟然大…

基于SpringBoot的桃花峪滑雪场租赁系统 JAVA简易版

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设计3.1 教练表3.2 教练聘请表3.3 押金规则表3.4 器材表3.5 滑雪场表3.7 售票表3.8 器材损坏表 四、系统展示五、核心代码5.1 查询教练5.2 教练聘请5.3 查询滑雪场5.4 滑雪场预定5.5 新…

【AI提示词人物篇】创新艺术未来,让科技改变想象空间

AI 绘画学习难度和练习技巧 学习绘画的技巧 学习能难度&#xff1a; 外貌特征&#xff1a;AI需要学习识别和理解各种外貌特征&#xff0c;如发型、肤色、眼睛颜色等。这可能需要大量的训练数据和复杂的模型架构。 镜头提示&#xff1a;AI需要学习理解不同镜头提示的含义&…

中心性算法归纳

中心性算法不仅是在我所学习的计算机网络当中起很重要的作用&#xff0c;在交通网络、社交网络、信息网络、神经网络当中也有很多的应用例子。今天我在这里总结一下场景的几种中心性算法。 参考文献 Python NetworkX库 偏心中心性&#xff08;Eccentricity Centrality&#x…

Kubernetes 的用法和解析(K8S 日志方案) -- 8

一、统一日志管理的整体方案 通过应用和系统日志可以了解Kubernetes集群内所发生的事情&#xff0c;对于调试问题和监视集群活动来说日志非常有用。对于大部分的应用来说&#xff0c;都会具有某种日志机制。因此&#xff0c;大多数容器引擎同样被设计成支持某种日志机制。 对…

安装vcpkg管理opencv的安装+MFC缺失的解决

第一步&#xff0c;出现#include没有办法找到opencv头文件的问题&#xff0c;无法解决 在VC的提示下&#xff0c;安装了vcpkg&#xff0c;然后用vcpkg命令来帮助安装opencv&#xff0c;过程十分顺利。 1. cmd 到命令行窗口&#xff1b; 2. 建立src文件夹&#xff0c;并进入…