什么是动态代理?

news2025/1/18 17:13:36

目录

一、为什么需要代理?

二、代理长什么样?

三、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.coloradmin.cn/o/1331172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习(八):bert理解之transformer

1.主要结构 transformer 是一种深度学习模型&#xff0c;主要用于处理序列数据&#xff0c;如自然语言处理任务。它在 2017 年由 Vaswani 等人在论文 “Attention is All You Need” 中提出。 Transformer 的主要特点是它完全放弃了传统的循环神经网络&#xff08;RNN&#x…

蓝桥杯2019年11月青少组Python程序设计省赛真题

1、试编写一个程序,输入一个整数,输出它的各个数位之和。 2、试编写一个程序,输入一个带有小数的数字,输出它的各个数位之和。 3、小兰要为1-2020住户制作门牌号,例如制作1107号门牌,需要制作2块1字符,一块0"字符一块7"字符,求制作1-2020需要多少块2. 4、编程画…

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

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

超维空间S2无人机使用说明书——32、使用yolov7进行目标识别

引言&#xff1a;为了提高yolo识别的质量&#xff0c;提高了yolo的版本&#xff0c;改用yolov7进行物体识别&#xff0c;同时系统兼容了低版本的yolo&#xff0c;包括基于C的yolov3和yolov4&#xff0c;也有更高版本的yolov8。 简介&#xff0c;为了提高识别速度&#xff0c;系…

Java期末复习题之选择题理论综合

点击返回标题->23年Java期末复习-CSDN博客 选择题考察内容为—— 构造函数的描述&#xff0c;在文件中写入字符而不是字节选用什么类&#xff0c;java源文件import, class定义以及package的顺序&#xff0c;静态成员变量作用域&#xff0c;非抽象子类的接口实现&#xff0c;…

【C++进阶02】多态

一、多态的概念及定义 1.1 多态的概念 多态简单来说就是多种形态 同一个行为&#xff0c;不同对象去完成时 会产生出不同的状态 多态分为静态多态和动态多态 静态多态指的是编译时 在程序编译期间确定了程序的行为 比如&#xff1a;函数重载 动态多态指的是运行时 在程序运行…

BDD - Python Behave VS Code 插件 Behave VSC

BDD - Python Behave VS Code 插件 Behave VSC 引言Behave VSC 插件Behave VSC 安装Behave VSC 注意事项Behave VSC 插件默认可识别的项目结构Behave VSC 设置识别非 features 文件名的项目 引言 上一篇《BDD - Python Behave 入门》介绍了 Behave 的入门基础知识&#xff0c;…

C语言之指针

目录 函数的参数 对象和地址 取地址运算符 注意 指针 注意 指针运算符 注意 在C语言中&#xff0c;指针是一个十分重要的概念&#xff0c;它的作用是“指示对象”。 例如&#xff1a;你要去一座公寓楼找一位朋友&#xff0c;公寓楼由很多楼层组成&#xff0c;每个楼层…

MySQL中CASE when 实战

CASE 语法 CASEWHEN condition1 THEN result1WHEN condition2 THEN result2WHEN conditionN THEN resultNELSE result END; 将表中的内容转换为右边的形式&#xff1a; 1、创建表&#xff0c;创建数据 CREATE TABLEchapter10_7 (order_id VARCHAR(255) NULL,price VARCHAR(25…

【Linux驱动】字符设备驱动程序框架 | LED驱动

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;Hello驱动程序⚽驱动程序框架⚽编程 &#x1f3c0;LED驱动⚽配置GPIO⚽编程驱动…

NHNL因子如何刻画行业强弱

根据华福证券-市场情绪指标专题&#xff08;五&#xff09;&#xff0c;进行了提炼和改写&#xff0c;特此致谢&#xff01; ( N H N L ) % ( c o u n t ( H H V ) − c o u n t ( L L V ) ) / N (NHNL)\% (count(HHV) - count(LLV))/N (NHNL)%(count(HHV)−count(LLV))/N 个…

Uniapp + Vue3 + Pinia + Vant3 框架搭建

现在越来越多项目都偏向于Vue3开发&#xff0c;想着uniapp搭配Vue3试试效果怎么样&#xff0c;接下来就是详细操作步骤。 初始化Uniapp Vue3项目 App.vue setup语法 <script setup>import {onLaunch,onShow,onHide} from dcloudio/uni-apponLaunch(() > {console.l…

Linux poll 和 select 机制

poll select 介绍 使用非阻塞 I/O 的应用程序常常使用 poll, select, 和 epoll 系统调用. poll, select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个 或多个文件而不阻塞. 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来 读或写.…

叮咚,微信年度聊天报告(圣诞节版)请查收~丨GitHub star 16.8k+

微信年度聊天报告——圣诞节特别版&#xff0c;快发给心仪的ta吧~ 开源地址 GitHub开源地址&#xff1a;https://github.com/LC044/WeChatMsg 我深信有意义的不是微信&#xff0c;而是隐藏在对话框背后的一个个深刻故事。未来&#xff0c;每个人都能拥有AI的陪伴&#xff0c;…

「Vue3面试系列」Vue3.0性能提升主要是通过哪几方面体现的?

文章目录 一、编译阶段diff算法优化静态提升事件监听缓存SSR优化 二、源码体积三、响应式系统参考文献 一、编译阶段 回顾Vue2&#xff0c;我们知道每个组件实例都对应一个 watcher 实例&#xff0c;它会在组件渲染的过程中把用到的数据property记录为依赖&#xff0c;当依赖发…

显卡之争!英伟达和AMD下场互掐!GPU霸主地位是否能保?

大家好&#xff0c;我是二狗。 英伟达和AMD这两家芯片巨头掐起来啦&#xff01; 事情的起因是&#xff0c;两周前AMD董事会主席兼CEO苏姿丰在一场活动中发布了用于生成式AI和数据中心的新一代Intinct MI300X GPU芯片加速卡。 单单发布显卡没啥问题&#xff0c;但是AMD声称MI300…

【Spring实战】03 JDBC常用操作

文章目录 1. JdbcTemplate 类1&#xff09;queryForList2&#xff09;update3&#xff09;query4&#xff09;execute5&#xff09;queryForObject 2.代码及执行1&#xff09;代码2&#xff09;执行 3. 优点4. 详细代码总结 Spring JDBC 是 Spring 框架提供的一种用于简化数据库…

05. Springboot admin集成Actuator(一)

目录 1、前言 2、Actuator监控端点 2.1、健康检查 2.2、信息端点 2.3、环境信息 2.4、度量指标 2.5、日志文件查看 2.6、追踪信息 2.7、Beans信息 2.8、Mappings信息 3、快速使用 2.1、添加依赖 2.2、添加配置文件 2.3、启动程序 4、自定义端点Endpoint 5、自定…

基于epoll的web服务器(C语言版本)

基于epoll的web服务器(C语言版本) 1. 初始化监听套接字 包括创建监听套接字&#xff0c;设置端口复用&#xff0c;绑定&#xff0c;设置监听等步骤 1.1 创建监听套接字&#xff08;socket函数&#xff09; socket()打开一个网络通讯端口&#xff0c;如果成功的话&#xff0…

界面控件DevExpress v23.2全新发布 - 官宣正式支持.NET 8

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress 今年第一个重要版本v23.1正式发布&#xff0c;该版本拥有众多…