一文掌握Java动态代理的奥秘与应用场景

一、基本概念

        为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象来替代。代理类负责请求的预处理、过滤、将请求分派委托类处理、以及委托类执行完请求后的后续处理。

        为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类的直接访问,也可以很好的保护和隐藏委托类对象,同时也为实现不同的策略预留了空间,从而在设计上获得了更大的灵活性。

        代理主要分为静态代理和动态代理:

  • 静态代理:创建代理类或特定工具自动生成源代码在对其编译,在程序运行前代理类的class文件就已经存在。
  • 动态代理:动态代理是一种编程技术,允许在运行时动态创建并处理代理对象,代理对象可以作为真实对象(也称为目标对象)的替代品,对真实对象的访问请求进行拦截、过滤、增强或修改。

        下面主要介绍一下动态代理技术

二、动态代理

        2.1 动态代理应用场景

        即使你不了解动态代理,但是相信是平时你已经在不经意间使用过动态代理,下面看下动态代理的使用场景。

  1. Spring AOP:AOP 使用了动态代理技术来实现对目标对象的代理和增强。
  2. SpringCloud OpenFeign:OpenFeign 中指定义了 FeignClient 的接口,但却能实现远程调用,OpenFeign 中只用了动态代理技术来实现远程调用。
  3. Mybatis 的接口绑定:定义的Mapper接口和xml文件,也是通过动态代理技术来实现的。
  4. RPC 框架:其实与 OpenFeign类似,比如 Dubbo、gRPC 等都是通过动态代理技术来实现远程调用的。

        上面这些功能相信你在平时都应用过,动态代理的应用场景不只列举的这些,应用十分广泛。

        2.2 动态代理的实现

        动态代理的代理实现方式有多种,在 Java 中主要有以下四种实现方式:

  • Java Proxy:是基于接口实现的,动态代理基于 java.lang.reflect.Proxy 类 java.lang.reflect.InvocationHandler 实现。代理类在运行时动态生成,动态的构建全新的字节码 Bean。
  • CGLIB:是一个三方库,动态构建全新的字节码Bean,不依赖接口。CGLIB通过生成目标类的子类来实现代理功能,因此即使目标类没有实现任何接口,也可以为其创建代理。
  • AspectJ:AspectJ 提供了一种更为强大且全面的切面编程解决方案,它既可以静态编译时织入(compile-time weaving),也可以在运行时织入(load-time weaving 或 runtime weaving)。修改目标类的字节,在程序编译时织入,不会生成全新的class,因此性能更好一些。
  • Instrumentation:是Java平台提供的一个内置 API,它主要用在 JVM 加载类的过程中进行字节码级别的操作,实现类的动态修改和增强,这与传统的动态代理机制(如Java的JDK动态代理和CGLIB动态代理)有所不同。不用再运行时创建新的class。

        动态代理技术的底层实现是修改字节码

三、使用案例

        下面演示一下 CGLIB 动态代理是如何工作的,加入我们有一个 HelloService 的类,要对台进行动态代理,在 sayHello 的执行前后分别做一些我们想做的事情。

public class HelloService {

    public HelloService() {
        System.out.println("HelloService构造");
    }

    /**
     * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }

}
public class MyMethodInterceptor implements MethodInterceptor {

    /**
     *
     * @param sub cglib生成的代理对象
     * @param method 被代理对象的方法
     * @param objects 方法入参
     * @param methodProxy 代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}


public class Client {

    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(HelloService.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy = (HelloService) enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}

        运行代码就能看到动态代理的结果了。我们看到 CGLIB 等是依赖 ASM,那 ASM 又是什么呢?

        3.1 ASM 介绍

        ASM (Abstract Syntax Tree, bytecode manipulation and analysis framework) 是一个用于Java 字节码操作和分析的开源框架。ASM 允许开发者在运行时直接操作 Java 类的字节码,提供了低层次的 API,使得开发者可以精细地控制字节码的生成和修改,实现如字节码增强、类和方法的动态修改、AOP(面向切面编程)等功能。        

        ASM 在JDK 中,具体位置如图

        通过 ASM 我们可以获取指定类的字节码

public class AsmUtils {

    public static void main(String[] args) {
        try {
            ClassReader reader = new ClassReader("指定全路径");
            TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
            reader.accept(visitor, ClassReader.SKIP_FRAMES);
        } catch (Exception e) {

        }
    }
}

        ASM的特点如下:

  1. 高度灵活:ASM 提供了对字节码的直接访问,可以精确控制每个字节码指令,具有很高的灵活性和可定制性。
  2. 高性能:由于 ASM 直接操作字节码,性能优异,尤其适合需要高性能字节码操作的场合。
  3. 广泛使用:ASM 被许多著名的开源项目和框架所使用,如 Hibernate、CGLIB、Spring 等,用以实现 AOP、动态代理、字节码增强等功能。
  4. 支持广泛的 Java 版本:ASM 支持从 Java 1.0 到最新的 Java 版本,兼容性非常好。
  5. API设计:ASM 的API设计简洁明了,开发者可以直接操作 ClassVisitor、MethodVisitor 和FieldVisitor 等接口,来遍历、修改或生成字节码。

        通过ASM,开发者可以实现例如方法调用拦截、性能监控、字节码注入等高级功能,是Java字节码操作领域的重要工具。

        总结:动态代理技术因其灵活性和实用性,在现代软件开发中的应用非常广泛,几乎涵盖了从底层服务到上层业务逻辑的各个环节。

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

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

相关文章

中国软件评测中心发布《健康软件安全白皮书》,美创参编分享方案实践

日前,中国软件评测中心网络安全和数据安全研究测评事业部发布《健康软件安全白皮书》研究成果,美创科技深度参与此次白皮书编写工作,针对健康软件目前面临的安全风险,分享数据安全建设思路。 白皮书详细剖析健康软件面临的网络和数…

盲盒抽卡机小程序:探索神秘盲盒,尽享抽卡乐趣

在盲盒文化的热潮中,盲盒抽卡机作为一种深受大众喜爱的娱乐形式,正逐渐成为市场上的新宠。为了满足广大盲盒爱好者和抽卡玩家的需求,我们积极投身于盲盒抽卡机小程序的开发,旨在通过创新的技术手段,为用户带来全新的盲…

PyTorch出现:RuntimeError: An attempt has been made to start a new process...报错

1.查看显卡: 第一步:同时按下键盘的winr键,输入cmd 第二步:随后输入 nvidia-smi 2.查看下载安装的包: conda list conda list 3.问题报错 PyTorch出现:RuntimeError: An attempt has been made to …

解决vue3中刷新浏览器页面的axios请求状态变为canceled

最近在开发中要加一个悲观锁的功能,具体需求是:用户1和用户2不能同时打开一个模型进行编辑,用户1优先进入模型后,要对该模型进行上锁,关闭该模型或刷新页面时要进行解锁,此时在刷新页面时出现了问题。 刷新…

线上linux服务器升级nginx

一个nginx版本空包 一个pcre文件 一个zlib文件 ./configure配置文件 make编译 make install复制所有文件到nginx 如果nginx -v无版本号 检查环境变量cat /etc/profile 编辑 环境变量vi /etc/profile 按i进入编辑模式 按esc进入查看模式 因为path中并未使用%JAVA_HOME%字样…

力扣---全排列---回溯

思路: 递归做法,一般会有visit数组来判断第 i 位是否被考虑了。我们先考虑第0位,再考虑第1位,再考虑第2位...dfs函数中还是老套路,先判定特殊条件,再从当下的角度(决定第 j 位是哪个元素&#x…

Docker 应用部署

MySQL部署 需求 在 Docker 容器中部署 MySQL ,并通过外部 mysql 客户端操作 MySQL Server 。 步骤 1. 搜索mysql镜像 docker search mysql 2. 拉取mysql镜像 docker pull mysql:5.6 3. 创建容器,设置端口映射、目录映射 事先在/root目录下创建m…

VScode手动安装vsix格式插件,提示安装插件与code版本不兼容问题

问题描述: vscode手动按装插件提示"插件不兼容code版本 原因方案:修改安装包内的package.json文件中的版本号与vscode版本号对应即可 解决步骤 以(adpyke.codesnap-1.3.4.vsix)安装包为例 手动安装vscode弹出 无法安装扩展“adpyke.codesnap-1.3.4”,它与 …

每周一算法:迭代加深A*

题目链接 AcWing 180. 排书 题目描述 给定 n n n 本书,编号为 1 ∼ n 1\sim n 1∼n。 在初始状态下,书是任意排列的。 在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。 我们的目标状态是把书按照 1 ∼…

提高企业员工生产力的办法

在现代商业环境中,提高企业员工生产力是企业持续发展的关键因素之一。员工生产力的提升不仅有助于企业提高运营效率,还能增强企业的市场竞争力。那么,如何有效地提高企业员工生产力呢?本文将就此问题进行探讨。 一、引入先进技术软…

[ C++ ] STL---stack与queue

目录 stack简介 stack的常用接口 queue简介 queue的常用接口 stack的模拟实现 queue的模拟实现 stack简介 1. stack是具有后进先出操作的一种容器适配器,其只能从容器的一端进行元素的插入与删除操作; 2. stack是作为容器适配器被实现的&#xff0…

jmeter接口自动化测试框架

接口测试可以分为两部分: 一是线上接口(生产环境)自动化测试,需要自动定时执行,每5分钟自动执行一次,相当于每5分钟就检查一遍线上的接口是否正常,有异常能够及时发现,不至于影响用…

基于preCICE的Fluent适配器开发分享

1 开发目的 后向台阶流是流动分离现象的经典代表,为了更有效地控制后向台阶流中的重要特征参数,如背部分离压强和湍流强度,进行了耦合分析。通过该耦合分析,能够深入研究后向台阶流的特性,并探索如何控制这些参数对流…

RK3568 安装Miniconda3

下载链接:https://download.csdn.net/download/smile_5me/89012477?spm=1001.2014.3001.5503 需要RK3568运行Ubuntu,之前的文章有关于如何安装Ubuntu以及遇到的问题 1、 拷贝 Miniconda3-latest-Linux-aarch64.sh 到开发板 2、运行安装 Miniconda3-latest-Linux-aarch64.…

央视6周年巨献《中国神话》AI微短剧首发,人工智能频道震撼上线

在庆祝成立六周年之际,中央广播电视总台(简称央视)为观众带来了一场科技与文化的交融盛宴。近日,央视隆重发布了我国首部AI全流程微短剧《中国神话》,并正式上线了人工智能专业频道。这一系列举措彰显了央视在人工智能…

网络: 应用层

网络资源 uri(uniform resource identifier) 统一资源标识符。url(uniform resource location) 统一资源定位符,统指绝对路径。urn(uniform resource name) 统一资源名。 http 报文结构 第一部分简略信息,包含请求方法、url 和协议版本;或…

【c++】string类---标准库(STL)中的string类

主页:醋溜马桶圈-CSDN博客 专栏:c_醋溜马桶圈的博客-CSDN博客 gitee:mnxcc (mnxcc) - Gitee.com 目录 1.STL(标准库) 1.1 什么是STL 1.2 STL的版本 1.3 STL的六大组件 1.4 STL的重要性 1.5 如何学习STL 6.STL的缺陷 2. 为什么要学习st…

STM32定时器详解(1)

文章目录 前言一、STM32中定时器的分类二、基础定时器2.1基础定时器框图讲解2.2基础定时器计数功能讲解 三、通用定时器3.1通用定时器基本描述3.2通用定时器硬件框图 四、高级定时器4.1高级定时器基本描述4.2高级定时器硬件框图 总结 前言 本篇文章将带大家来学习STM32中的定时…

SunFMEA冠翔(台山)工业FMEA培训会圆满结束

近日,SunFMEA软件成功在冠翔(台山)工业有限公司举办了为期三天的FMEA软件系统培训,通过重要知识讲解、现场答疑、演练互动、软件实操等环节,把培训氛围推向高潮。 ​ 此次培训分为DFMEA与PFMEA两部分,按照七…

浏览器工作原理与实践--TCP协议:如何保证页面文件能被完整送达浏览器

在衡量Web页面性能的时候有一个重要的指标叫“FP(First Paint)”,是指从页面加载到首次开始绘制的时长。这个指标直接影响了用户的跳出率,更快的页面响应意味着更多的PV、更高的参与度,以及更高的转化率。那什么影响FP…