spring之动态代理

news2025/1/15 16:49:02

文章目录

  • 前言
  • 一、JDK动态代理
    • 1、业务接口OrderService
    • 2、目标对象OrderServiceImpl
    • 3、客户端程序Client
    • 4、InvocationHandler 的实现类TimeInvocationHandler
    • 5、运行结果
  • 二、CGLIB动态代理
    • 1、先引入依赖
    • 2、目标类 UserService
    • 3、客户端程序Client
    • 4、MethodInterceptor的实现类TimeMethodInterceptor


前言

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存中生成动态生成类的技术常见的包括:

  • JDK动态代理技术:只能代理接口
  • CGLIB动态代理技术:CGLIB是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。

一、JDK动态代理

1、业务接口OrderService

public interface OrderService {
    void generate();
    void modify();
    void detail();
}

2、目标对象OrderServiceImpl

public class OrderServiceImpl implements OrderService{
    @Override
    public void generate() {
        //模拟生成订单的耗时
        try{
            Thread.sleep(1234);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void modify() {
        try{
            Thread.sleep(443);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }

    @Override
    public void detail() {
        try{
            Thread.sleep(546);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println("查看订单详情");
    }
}

3、客户端程序Client

newProxyInstance翻译为:新建代理对象,也就是说,通过调用这个方法可以创建代理对象
本质上,这个Proxy.newProxyInstance() 方法的执行,做了两件事:
* 第一件事:在内存中动态的生成了一个代理类的字节码class。
* 第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了对象
三个参数的含义:
第一个参数:ClassLoader loader
在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中,加载类就需要类加载器,所以这里指定需要类加载器,并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个(target.getClass().getClassLoader())
第二个参数:Class<?>[] interface
* 代理类和目标类要实现同一个接口或同一些接口
* 在内存中生成代理类的时候,这个代理类是需要你告诉他实现哪些接口的
* target.getClass().getInterface()
第三个参数:InvocationHandler h 调用处理器,是一个接口
* 在调用处理器接口中编写的就是增强代码。
* 因为具体要增强什么代码,JDK动态代理是猜不到的。
* 既然是接口,就要写接口的实现类

public class Client {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        //创建代理对象(借助JDK中的类)
        OrderService proxy = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimeInvocationHandler(target));
        //调用代理对象的代理方法
        //注意:调用代理对象的代理方法的时候,如果要做增强的话,目标对象的目标方法得保证执行
        proxy.generate();
    }
}

4、InvocationHandler 的实现类TimeInvocationHandler

  • 专门负责计时的一个调用处理器对象
  • 在这个调用处理器当中编写计时相关的增强代码

1、为什么强行要求必须实现InvocationHandler接口?
因为一个类实现接口就必须实现接口中实现的方法

2、invoke()方法什么时候调用呢?
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用

3、invoke方法的三个参数:
invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数

第一个参数:Object proxy 代理对象的引用
第二个参数:Method method 目标对象的目标方法(要执行的目标方法就是它)
第三个参数:Object[] args 目标方法上的实参

public class TimeInvocationHandler implements InvocationHandler {
    private Object target;
    public TimeInvocationHandler(Object target) {
        //赋值给成员变量
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这个接口的目的就是为了让你有地方写增强代码
        long begin = System.currentTimeMillis();
        //调用目标对象上的目标方法
        //方法四要素:那个对象 哪个方法 传什么参数 返回什么值
        Object retValue = method.invoke(target,args);
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end-begin)+"毫秒");
        //注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回
        return retValue;
    }
}

这里考虑传哪个对象时,由于客户端程序中创建了一个目标对象,那么我们把这个变量传到代理类中,即在代理类中创造一个构造方法,完成赋值。

5、运行结果

在这里插入图片描述
代码得到了复用,不会造成类爆炸

二、CGLIB动态代理

CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的类不能使用final修饰。

1、先引入依赖

    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>

2、目标类 UserService

public class UserService {
    //目标方法
    public boolean login(String username,String password){
        System.out.println("系统正在验证身份");
        if("admin".equals(username) && "123".equals(password)){
            return true;
        }
        else return false;
    }
    //目标方法
    public void logout(){
        System.out.println("系统正在退出...");
    }
}

3、客户端程序Client

在CGLIB当中不是InvocationHandler接口,是方法拦截器:MethodInterceptor

public class Client {
    public static void main(String[] args) {
        //创建字节码增强对象
        //这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类
        Enhancer enhancer = new Enhancer();
        //告诉CGLIB父类是谁。告诉CGLIB目标类是谁
        enhancer.setSuperclass(UserService.class);
        //设置回调(等同于JDK动态代理中的调用处理器 InvocationHandler)
        //在CGLIB当中不是InvocationHandler接口,是方法拦截器:MethodInterceptor
        enhancer.setCallback(new TimeMethodInterceptor());
        //创建代理对象
        //这一步会做两件事:
        //第一件事:在内存中生成UserService类的子类,其实就是代理类的字节码
        //第二件事:创建代理对象
        //父类是UserService 子类这个代理类一定实现UserService
        UserService userServiceProxy = (UserService) enhancer.create();
        //调用代理对象的代理方法
        boolean success = userServiceProxy.login("admin", "123");
        System.out.println(success ? "登陆成功":"登录失败");
        userServiceProxy.logout();
    }
}

4、MethodInterceptor的实现类TimeMethodInterceptor

public class TimeMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //增强语句
        long begin = System.currentTimeMillis();
        //调用方法(目标对象的目标方法)
        Object retValue = methodProxy.invoke(target, objects);

        //增强语句
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end-begin)+"毫秒");
        return retValue;
    }
}

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

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

相关文章

温振传感器的信号输出方式及应用领域

在振动测量系统中&#xff0c;测量振动的仪器排在前端。温振传感器也称为温度振动传感器&#xff08;变送器&#xff09;&#xff0c;它可以将被测对象的振动量&#xff08;位移、速度&#xff09;准确接受后&#xff0c;并将此机械量转换为电信号显示出来。 在工业生产、食品…

内存对齐(memory align)

0. 内存结构 我们平时所称的内存也叫随机访问存储器&#xff08;random-access memory&#xff09;也叫RAM。而RAM分为两类&#xff1a; 一类是静态RAM&#xff08;SRAM&#xff09;&#xff0c;这类SRAM用于前边介绍的CPU高速缓存L1Cache&#xff0c;L2Cache&#xff0c;L3C…

不求星光灿烂,但愿岁月静好

作者&#xff1a;非妃是公主 专栏&#xff1a;《程序人生》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录不求星光灿烂&#xff0c;但愿岁月静好说一说这一年的自己的收获吧2022年的追求自我学会拒绝尝试表达…

Unreal单播委托

单播委托只能注册一个函数:无参无返回值给委托绑定函数:判断如果委托有绑定函数就发起广播:解绑:绑定方式除了BindUObject,还有BindUFunction,通过这种方式绑定需要给函数添加UFUNCTION标记:还有BindLambda匿名函数:BindRaw可以绑定原生C类中的函数:无参有返回值定义委托类型:声…

Linux进程状态与系统负载检测

1.基础知识-进程的5个状态进程可以分为五个状态&#xff0c;分别是&#xff1a;1&#xff09;创建状态一个应用程序从系统上启动&#xff0c;首先就是进入创建状态&#xff0c;需要获取系统资源创建进程管理块&#xff08;PCB&#xff09;完成资源分配。2) 就绪状态在创建状态完…

Dextran-Azide,Dextran-N3结构式;叠氮修饰的葡聚糖 科研用试剂说明

Dextran-N3,叠氮基团葡聚糖 英文名称&#xff1a;Dextran-Azide,Dextran-N3 中文名&#xff1a;叠氮修饰的葡聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观: 固体或类白色絮状&#xff0c;取决于分子量 溶剂&#xff1a;溶于大部分有机溶剂&…

kafka单节点部署,手把手从零到一

kafka单节点部署 书接上回&#xff1a;zookeeper单节点部署&#xff0c;手把手从零到一 建议配套观看 2、kafka的单节点部署 2.1、下载 这里如果和zookeeper相似的就不再赘述&#xff0c;直接上命令 wget https://archive.apache.org/dist/kafka/2.8.2/kafka_2.12-2.8.2.tgz…

深入了解ArrayBlockingQueue 阻塞队列

1. 前言 开始正式了解阻塞队列之前&#xff0c;我们需要了解什么是队列。 队列有什么作用。其实队列的作用就是解耦&#xff0c;更加确切的说应该是生产者以及消费者 之间的解耦 今天就让我们来看下ArrayBlockingQueue 的实现。虽然通过名称就可以看到&#xff0c;无非是通过数…

Theory for the information-based decomposition of stock price

文章目录MotivationThe potential of Brogaard DecompositionIntuitions for Brogaard decompositionTechnique details in Brogaard decompositionDefine the VAR systemIdentify the VAR systemVariance decompositionSummaryMain ReferencesMotivation Brogaard et al. (20…

1000字带您了解网络设备的接口分类和接口编号规则

通过本文&#xff0c;您可以了解到设备的接口分类和接口编号规则。 文章目录一、接口分类1.1 物理接口1.1.1 管理接口1.1.1 业务接口LAN侧接口WAN侧接口1.2 逻辑接口二、接口编号规则2.1 物理接口编号规则三、总结一、接口分类 接口是设备与网络中的其它设备交换数据并相互作用…

3.3 行列式的几何意义

文章目录二维面积三维体积多维体积行列式是线性代数一个非常重要的内容&#xff0c;也是非常难的领域.行列式在欧几里得空间里还有特殊的几何意义。二维面积 &esmp; 两个向量围成的平行四边形的面积就是这两个向量组成的矩阵的行列式的绝对值。以两个向量(3.−2)T(3.-2)^…

结构体 · 内存对齐

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 在初识C语言中简单介绍了结构体&#xff0c;结构体可以理解为不同类型数据的集合体&#xff0c;但是你想过结构体的大小是如何计算的吗&#xff1f;看完这篇博客&#xff0c;你就能给自己答…

Linux 计算机网络 route 路由表、多网段与 bond 的故事

Linux 计算机网络 route 路由表、多网段与 bond 的故事 序 在之前的章节中&#xff0c;介绍了计算机网络的发展以及各种解析&#xff0c;在之中我们提到了每个主机设备都会维护一张自己的路由表&#xff0c;通过路由表来确定在不同网络之间&#xff0c;怎么将数据规划传输到各…

1988-2020年31省基尼系数数据

1、时间&#xff1a;1988-2020年 2、范围&#xff1a;31省 3、指标&#xff1a;包括省基尼系数年度数据&#xff0c;省城市和农村基尼系数年度 4、来源及计算方法说明附在文件内 5、指标说明&#xff1a; 基尼系数&#xff08;英文&#xff1a;Gini index、Gini Coefficie…

LeetCode 94. 二叉树的中序遍历

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 94. 二叉树的中序遍历&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetC…

Mybatis获取参数

Mybatis获取参数 配置模板 mybatis获取参数值的两种方式 1、&{}&#xff1a; 字符串拼接 2、#{}&#xff1a; 占位符赋值 MyBatis获取参数值的各种情况&#xff1a; MyBatis获取参数值的各种情况&#xff1a; 1、mapper接口方法的参数为单个的字面量类型 可以通过&#xf…

双系统下 linux挂载window磁盘

如果想让linux访问window分区磁盘&#xff0c;呈只读状态&#xff0c;解决办法是bios取消window快速开机。永久挂载windows磁盘 https://blog.csdn.net/yuehenmiss/article/details/124737456 # 创建挂载目录 sudo mkdir /window # 挂载分区 sudo mount /dev/sda1 /window # 查…

产品经理必懂知识之计算机基础知识

作为产品经理&#xff0c;非常有必要了解一下计算机的发展历史&#xff0c;今天带大家一起&#xff0c;大概地了解一下计算机的基础知识&#xff0c;希望能够帮助到大家&#xff0c;框架如下&#xff1a; 一、计算机发展史 1.1计算机的诞生 1946年第一台电子计算机问世美国宾…

YOLOv8训练自己的数据集(超详细)

一、准备深度学习环境 本人的笔记本电脑系统是&#xff1a;Windows10 YOLO系列最新版本的YOLOv8已经发布了&#xff0c;详细介绍可以参考我前面写的博客&#xff0c;目前ultralytics已经发布了部分代码以及说明&#xff0c;可以在github上下载YOLOv8代码&#xff0c;代码文件夹…

一种车辆纵向控制切换算法设计思路

传统及主流的纵向控制切换算法&#xff1a; 例如《某避障控制策略研究》硕士论文&#xff1a; 在CarSim中设定节气门开度及制动踏板力为0&#xff0c;测得不同车速工况下车辆自然滑行的减速度。为了避免在控制过程中车辆驱动与制动切换的过于频繁&#xff0c;在其两侧设置了宽…