动态代理详解

动态代理

  • 一、JDK动态代理
  • 二、CGLIB动态代理
  • 三、Javassist动态代理技术


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

一、JDK动态代理

  • DK动态代理技术:只能代理接口。

  • Java 动态代理实现相关类位于 java.lang.reflect 包,主要涉及两个类:

    • InvocationHandler 接口。它是代理实例的调用处理程序实现的接口,该接口中定义了如下方法:

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
      
      • invoke() 方法上有三个参数:
        • 第一个参数 proxy 表示代理类。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
        • 第二个参数 method 表示需要代理的方法。
        • 第三个参数 args 表示代理方法的参数数组。
    • Proxy 类。该类即为动态代理类,该类最常用的方法为:

      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
      
      • 这行代码做了两件事:
        • 第一件事:在内存中生成了代理类的字节码。
        • 第二件事:创建代理对象。
      • newProxyInstance() 方法有三个参数:
        • 第一个参数 loader 表示代理类的类加载器。。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
        • 第二个参数 interfaces 表示代理类实现的接口列表(与真实主题类的接口列表一致)。通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
        • 第三个参数 h 表示所指派的调用处理程序类。这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
  • 还是使用静态代理中的例子:一个接口和一个实现类。(参考上面的代码)

  • 要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 动态代理类
     **/
    
    public class TimerInvocationHandler implements InvocationHandler {
    
        private Object target; //需要代理的目标对象
    
        public TimerInvocationHandler() {
        }
    
        public TimerInvocationHandler(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) + "毫秒");
            // 返回目标对象方法的返回值。
            return retValue;
        }
    }
    
  • 编写 Client 测试程序:

    import service.OrderService;
    import service.OrderServiceImpl;
    
    public class Client {
        public static void main(String[] args) {
        	 // 创建目标对象
            OrderService target = new OrderServiceImpl();
    		// 创建代理对象
            OrderService ret = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
    		 // 调用代理对象的代理方法
            ret.generate();
            ret.detail();
            ret.delete();
            //当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。
        }
    }
    
  • 提供一个工具类:ProxyUtil,封装一个方法:

    package utils;
    
    import dynamic_proxy.TimerInvocationHandler;
    
    import java.lang.reflect.Proxy;
    
    public class ProxyUtil {
        private ProxyUtil(){}
    
        public static Object newProxyInstance(Object target) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
        }
    }
    
  • 这样客户端代码就不需要写那么繁琐了:

    import service.OrderService;
    import service.OrderServiceImpl;
    import utils.ProxyUtil;
    
    public class Client {
        public static void main(String[] args) {
        	 // 创建目标对象
            OrderService target = new OrderServiceImpl();
    		// 创建代理对象
            OrderService ret = (OrderService) ProxyUtil.newProxyInstance(target);
    		 // 调用代理对象的代理方法
            ret.generate();
            ret.detail();
            ret.delete();
            //当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。
        }
    }
    
  • Spring AOP 实现中使用了动态代理模式,使得代码中不存在与具体要用到的接口或类相关的引用。


二、CGLIB动态代理

  • CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
  • 使用CGLIB,需要引入它的依赖:
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  • 我们准备一个没有实现接口的类,如下:
public class UserService {
    public String  login(String username, String password) {

        try {
            Thread.sleep(13654);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (username.equals("root") && password.equals("123456")) {
            return "success~";
        }
        return "username or password error!";
    }
}
  • 和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor编写MethodInterceptor接口实现类:
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}
  • MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
    • 第一个参数:目标对象
    • 第二个参数:目标方法
    • 第三个参数:目标方法调用时的实参
    • 第四个参数:代理方法
  • 使用CGLIB在内存中为UserService类生成代理类,并创建对象:
public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        String ret = userServiceProxy.login("root", "123456");
        System.out.println(ret);
    }
}
  • 对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
    在这里插入图片描述
  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

三、Javassist动态代理技术


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

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

相关文章

备份 ChatGPT 的聊天纪录

备份 ChatGPT 的聊天纪录 ChatGPT 在前阵子发生了不少次对话纪录消失的情况&#xff0c;让许多用户觉得困扰不已&#xff0c;也担心自己想留存的聊天记录消失不见。 好消息是&#xff0c;OpenAI 在 2023 年 4 月 11 日推出了 ChatGPT 聊天记录备份功能&#xff0c;无论是免费…

怎么把视频内容快速转为文字?这三款工具助您轻松实现!

在现代社会&#xff0c;视频内容日益丰富多样&#xff0c;但有时我们更希望获得视频中的文字文稿&#xff0c;以便于搜索、编辑或传播。下面我将为您介绍三款优秀的视频转文字工具&#xff0c;它们能够帮助您快速、准确地将视频内容转换为可编辑的文字格式。让我们一起来看看这…

【JavaEE初阶】 JVM类加载简介

文章目录 &#x1f343;前言&#x1f332;类加载过程&#x1f6a9;加载&#x1f6a9;验证&#x1f6a9;准备&#x1f6a9;解析&#x1f6a9;初始化 &#x1f384;双亲委派模型&#x1f6a9;什么是双亲委派模型&#xff1f;&#x1f6a9;双亲委派模型的优点 ⭕总结 &#x1f343…

微服务:Docker篇

1. 初识Docker 1.1. 什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。 在数百上千台服务中重复部署&#xff0c;环境不一定一…

JavaScript极速入门(2)

JQuery W3C标准给我们提供了一系列函数,让我们可以操作: 网页内容 网页结构 网页样式 但是原生的JavaScript提供的API操作DOM元素时,代码比较繁琐,冗长.我们学习使用JQuery来操作页面对象. JQuery是一个快速,简洁且功能丰富的JavaScript框架,于2006年发布.它封装JavaScript常…

3dmax画图卡顿解决方法---模大狮模型网

当你在使用3D Max进行画图时遇到卡顿问题&#xff0c;可以尝试以下方法来解决&#xff1a; 减少模型复杂度&#xff1a;如果你的场景中有过多的高细节模型&#xff0c;可能会导致卡顿。尝试减少模型的复杂度&#xff0c;合并或简化多边形数量过多的模型。这将减轻计算机的负担&…

定制红酒:与客户的互动沟通,提升定制满意度

在云仓酒庄洒派&#xff0c;云仓酒庄洒派深知与客户之间的互动沟通对于提升定制满意度至关重要。因此&#xff0c;云仓酒庄洒派始终致力于与消费者建立积极、进一步的沟通&#xff0c;确保他们能够获得满意的红酒定制体验。 首先&#xff0c;云仓酒庄洒派通过多种渠道与客户建立…

扭蛋机小程序开发,互联网时代下的创新发展

近几年&#xff0c;扭蛋机市场迎来新生&#xff0c;逐渐火爆&#xff0c;受到了年轻一代消费者的钟爱。扭蛋机商品价格低廉&#xff0c;种类丰富&#xff0c;根据IP能够不断进行创新&#xff0c;收藏价值极高。在市场的发展下&#xff0c;扭蛋机行业也成为了一个蓝海市场&#…

算法打卡day12|二叉树篇01|144. 二叉树的前序遍历、94. 二叉树的中序遍历、145. 二叉树的后序遍历

二叉树理论基础篇 二叉树的定义 二叉树的定义和链表是差不多的&#xff0c;相对于链表 &#xff0c;二叉树的节点里多了一个指针&#xff0c;一共有两个指针&#xff0c;指向左右孩子。 JAVA的定义如下&#xff0c;需要理解性记忆&#xff0c;因为面试手撕代码可能会用。 p…

VBA中类的解读及应用第十讲:限制文本框的输入,使其只能输入数值(上)

《VBA中类的解读及应用》教程【10165646】是我推出的第五套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。 类&#xff0c;是非常抽象的&#xff0c;更具研究的价值。随着我们学习、应用VBA的深入&#xff0…

10大AI工具

ChatGPT ChatGPT是由OpenAI开发的人工智能聊天机器人程序&#xff0c;全称为Chat Generative Pre-trained Transformer。它基于GPT-3.5架构&#xff0c;能够生成回答并根据聊天上下文进行互动。ChatGPT具备强大的对话能力&#xff0c;能在同一会话中回答上下文相关的问题&…

SpringCloud之Nacos入门与实战系列

目录 一、Nacos介绍 1.1、配置中心和注册中心的概念 1.2 Nacos 优点 二、Nacos的使用 2.1 以单机模式启动Nacos 2.2 Nacos部署方式介绍 2.3 配置数据源 2.4 开启控制台权限登录 三、配置中心的使用 3.1 创建配置信息 3.2 SpringBoot使用配置中心 四、注册中心的使用 4…

知识管理系统:初创企业的智慧助手

一、什么是知识管理系统 用通俗易懂的语言来解释&#xff0c;知识管理系统就像一个超级大脑&#xff0c;帮助企业和团队更好地记住、分享和使用他们学到的东西。无论是工作中的经验、方案还是项目成果&#xff0c;这个系统都能帮大家保存下来&#xff0c;并方便以后查找和使用。…

程序如何知道mqtt设备是否在线

在做物联网设备的时候经常会碰到设备的在线与掉线 问题&#xff1a;emqx如何来实现这个在线与掉线 实现&#xff1a;添加一个规则&#xff0c;程序监控这个规则 1、SELECT * FROM "$events/client_connected", "$events/client_disconnected" 2、添加一…

全面认识计算机

目录 一、计算机的发展史 1. 电子管计算机时代 2. 晶体管计算机时代 3. 小、中规模集成电路计算机时代 4. 大、超大规模集成电路计算机时代 二、计算机硬件组成 1. 输入设备 2. 输出设备 3. 存储器 4. 运算器 5. 控制器 三、计算机硬件间的连接 四、计算机系统的结…

AlpacaEval Leaderboard大模型排行榜

参考 Model NameWin RateLengthGPT-4 Turbo &#x1f4c4;50.00%2049Contextual AI (KTO-Mistral-PairRM) &#x1f4c4;33.23%2521Yi 34B Chat &#x1f4c4;29.66%2123Claude 3 Opus (02/29) &#x1f4c4;29.04%1388Claude 3 Sonnet (02/29) &#x1f4c4;25.56%1420GPT-4 …

数睿通2.0数据接入升级——支持增量字段同步,表单独映射

引言 上次数睿通 2.0 更新是在 23 年12 月 底&#xff0c;已经过去了接近三个月的时间&#xff0c;中间由于过年加上年前年后实在是工作繁忙&#xff0c;所以一直没有腾出空来更新代码&#xff0c;希望大家可以理解&#xff0c;平台的发展离不开你们的支持&#xff0c;在此表示…

Seata 2.x 系列【4】产品简介

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 3.1.0 本系列Seata 版本 2.0.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 概述2. 发展历史3. 核心术语3.1 TC3.2 TM3.3 RM 4.…

泽众云真机-选择时长套餐?还是选择不限时套餐?

泽众云真机2月底推出了不限时套餐&#xff0c;改变了传统时长计费套餐&#xff0c;对用户来说&#xff0c;选择困难症状又来了&#xff0c;两种计费模式&#xff0c;我应该如何选择呢&#xff1f; 首先&#xff0c;说说平台为什么上线不限时套餐&#xff0c;之前平台运维和客服…

在vue3中根据element Plus封装一个图片上传组件

继上次文件上传之后&#xff0c;可能又遇到多个图片上传&#xff0c;图片如下 组件使用方法如下 话不多说&#xff0c;直接上组件&#xff0c;上面的划入删除是手写的&#xff0c;组件里面只有多图片上传的&#xff0c;索性就自己写了个划入显示点击删除的 下面是代码 <te…