秒懂Java动态编程(Javassist研究)

2023-05-16

版权申明】非商业目的可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/81269295
出自:shusheng007

  • 概述
  • 什么是动态编程
    • 反射
    • 动态编译
    • 调用JavaScript引擎
    • 动态生成字节码
  • 动态编程解决什么问题
  • Java中如何使用
    • Javassit使用方法
    • 动态生成一个类
    • 动态添加构造函数及方法
    • 动态修改方法体
  • 什么原理
  • 总结

概述

什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?(需要我们一起探索,由于自己也是比较菜,一般深入不到这个程度)。

什么是动态编程

动态编程是相对于静态编程而言的,平时我们讨论比较多的就是静态编程语言,例如Java,与动态编程语言,例如JavaScript。那二者有什么明显的区别呢?简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的。所谓动态编程就是绕过编译过程在运行时进行操作的技术,在Java中有如下几种方式:

反射

这个搞Java的应该比较熟悉,原理也就是通过在运行时获得类型信息然后做相应的操作。

动态编译

动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

调用JavaScript引擎

Java 6加入了对Script(JSR223)的支持。这是一个脚本框架,提供了让脚本语言来访问Java内部的方法。你可以在运行的时候找到脚本引擎,然后调用这个引擎去执行脚本。这个脚本API允许你为脚本语言提供Java支持。

动态生成字节码

这种技术通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素。

动态编程解决什么问题

在静态语言中引入动态特性,主要是为了解决一些使用场景的痛点。其实完全使用静态编程也办的到,只是付出的代价比较高,没有动态编程来的优雅。例如依赖注入框架Spring使用了反射,而Dagger2 却使用了代码生成的方式(APT)。

例如
1: 在那些依赖关系需要动态确认的场景:
2: 需要在运行时动态插入代码的场景,比如动态代理的实现。
3: 通过配置文件来实现相关功能的场景

Java中如何使用

此处我们主要说一下通过动态生成字节码的方式,其他方式可以自行查找资料。

操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。

ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

Javassit 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM.当然如果开发的是一个基础类库,或者基础平台,还是直接使用ASM吧,相信从事这方面工作的开发者能力应该比较高。

这里写图片描述
上一张国外博客的图,展示处理Java字节码的工具的关系。

接下来介绍如何使用Javassit来操作字节码

Javassit使用方法

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist中最为重要的是ClassPoolCtClassCtMethod 以及 CtField这几个类。

ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。

CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。

CtMethods:表示类中的方法。

CtFields :表示类中的字段。

动态生成一个类

下面的代码会生成一个实现了Cloneable接口的类GenerateClass

 public void DynGenerateClass() {
     ClassPool pool = ClassPool.getDefault();
     CtClass ct = pool.makeClass("top.ss007.GenerateClass");//创建类
     ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口
     try {
         CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段
         f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
         ct.addField(f);//将字段设置到类上
         //添加构造函数
         CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
         ct.addConstructor(constructor);
         //添加方法
         CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
         ct.addMethod(helloM);

         ct.writeFile();//将生成的.class文件保存到磁盘

         //下面的代码为验证代码
         Field[] fields = ct.toClass().getFields();
         System.out.println("属性名称:" + fields[0].getName() + "  属性类型:" + fields[0].getType());
     } catch (CannotCompileException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     } catch (NotFoundException e) {
         e.printStackTrace();
     }
 }

上面的代码就会动态生成一个.class文件,我们使用反编译工具,例如Bytecode Viewer,查看生成的字节码文件GenerateClass.class,如下图所示。

这里写图片描述

动态添加构造函数及方法

有很多种方法添加构造函数,我们使用CtNewConstructor.make,他是一个的静态方法,其中有一个重载版本比较方便,如下所示。第一个参数是source text 类型的方法体,第二个为类对象。

 CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
 ct.addConstructor(constructor);     

这段代码执行后会生成如下java代码,代码片段是使用反编译工具JD-GUI产生的,可以看到构造函数的参数名被修改成了paramInt

  public GeneratedClass(int paramInt)
  {
    this.id = paramInt;
  }

同样有很多种方法添加函数,我们使用CtNewMethod.make这个比较简单的形式

CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);

这段代码执行后会生成如下java代码:

  public void hello(String paramString)
  {
    System.out.println(paramString);
  }

动态修改方法体

动态的修改一个方法的内容才是我们关注的重点,例如在AOP编程方面,我们就会用到这种技术,动态的在一个方法中插入代码。
例如我们有下面这样一个类

public class Point {
    private int x;
    private int y;

    public Point(){}
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void move(int dx, int dy) {
        this.x += dx;
        this.y += dy;
    }
}

我们要动态的在内存中在move()方法体的前后插入一些代码

    public void modifyMethod()
    {
        ClassPool pool=ClassPool.getDefault();
        try {
            CtClass ct=pool.getCtClass("top.ss007.Point");
            CtMethod m=ct.getDeclaredMethod("move");
            m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");
            m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");

            ct.writeFile();
            //通过反射调用方法,查看结果
            Class pc=ct.toClass();
            Method move= pc.getMethod("move",new Class[]{int.class,int.class});
            Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});
            move.invoke(con.newInstance(1,2),1,2);
        }
        ...
    }

使用反编译工具查看修改后的move方法结果:

  public void move(int dx, int dy) {
    System.out.print("dx:" + dx);System.out.println("dy:" + dy);
    this.x += dx;
    this.y += dy;
    Object localObject = null;//方法返回值
    System.out.println(this.x);System.out.println(this.y);
  }

可以看到,在生成的字节码文件中确实增加了相应的代码。
函数输出结果为:

dx:1dy:2
2
4

Javassit 还有许多功能,例如在方法中调用方法,异常捕捉,类型强制转换,注解相关操作等,而且其还提供了字节码层面的API(Bytecode level API)。

什么原理

反射:由于Java执行过程中是将类型载入虚拟机中的,在运行时我们就可以动态获取到所有类型的信息。只能获取却不能修类型信息。
动态编译与动态生成字节码:这两种方法比较相似,原理也都是利用了Java的设计原理,存在一个虚拟机执行字节码,这就使我们在此处有了改变字节码的操作空间。

总结

有关动态编程的知识在平时的应用层使用不是特别多,多是用在构建框架。例如Spring框架使用反射来构建,而用于AOP编程的动态代理则多是采用生成字节码的方式,例如JBossSpring中的AOP部分。了解这部分知识可以在日后遇到相关问题时比别人多一条思考的思路也是好的,做一个思路开阔的Developer

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

秒懂Java动态编程(Javassist研究) 的相关文章

  • Linux LVM在线扩容xfs文件系统(创建大于2T的磁盘分区)

    1 使用parted方式格式化磁盘 1 1 查看磁盘情况 root 64 superman fdisk l Disk dev sdd 2199 0 GB 2199023255552 bytes 4294967296 sectors Unit
  • Linux LVM在线扩容ext3文件系统

    1 扩容前信息查看 1 2 查看文件系统情况 root 64 superman df h 文件系统 容量 已用 可用 已用 挂载点 dev mapper VG00 lv root 30G 23G 5 3G 82 dev mapper VG0
  • Linux云计算-04_Linux用户及权限管理

    Linux是一个多用户的操作系统 xff0c 引入用户 xff0c 可以更加方便管理Linux服务器 xff0c 系统默认需要以一个用户的身份登录 xff0c 而且在系统上启动进程也需要以一个用户身份器运行 xff0c 用户可以限制某些进程
  • 01 openEuler操作系统介绍

    文章目录 01 openEuler操作系统介绍1 1 发布件1 2 最小硬件要求1 3 硬件兼容性1 4 关键特性1 4 1 openEuler 22 03 LTS基于 Linux Kernel 5 10 内核构建 在进程调度 内存管理等方
  • 02 openEuler操作系统的安装

    文章目录 02 openEuler操作系统的安装2 1 openEuler操作系统的安装流程2 2 openEuler操作系统的安装详细步骤2 2 1 下载地址2 2 2 创建虚拟机2 2 2 1 方法一 xff1a 典型配置2 2 2 2
  • 06 openEuler XFCE 桌面环境的安装和使用

    06 openEuler XFCE 桌面环境的安装和使用 文章目录 06 openEuler XFCE 桌面环境的安装和使用6 1 XFCE简介6 2 XFCE安装方法6 2 1 更新软件源6 2 2 安装字库6 2 3 安装Xorg6 2
  • 21 openEuler 管理服务-改变运行级别

    文章目录 21 管理服务 改变运行级别21 1 Target和运行级别21 2 查看系统默认启动目标21 3 查看当前系统所有的启动目标21 4 改变默认目标21 5 改变当前目标21 6 切换到救援模式21 7 切换到紧急模式 21 管理
  • 字符串通配(动态规划java)

    1 牛客网题目 xff1a 题目描述 对于字符串A xff0c 其中绝对不含有字符 和 再给定字符串B xff0c 其中可以含有 或 xff0c 字符不能是B的首字符 xff0c 并且任意两个 字符不相邻 exp中的 代表任何一个字符 xf
  • 26 openEuler管理网络-使用ip命令配置网络

    文章目录 26 openEuler管理网络 使用ip命令配置网络26 1 配置IP地址26 1 1 配置静态地址26 1 2 配置多个地址 26 2 配置静态路由 26 openEuler管理网络 使用ip命令配置网络 说明 xff1a 使
  • 31 openEuler使用LVM管理硬盘-管理物理卷

    文章目录 31 openEuler使用LVM管理硬盘 管理物理卷31 1 创建物理卷31 2 查看物理卷31 3 修改物理卷属性31 4 删除物理卷 31 openEuler使用LVM管理硬盘 管理物理卷 31 1 创建物理卷 可在root
  • 41 openEuler搭建FTP服务器-传输文件

    文章目录 41 openEuler搭建FTP服务器 传输文件41 1 概述41 2 连接服务器41 3 下载文件41 4 上传文件41 5 删除文件41 6 断开服务器 41 openEuler搭建FTP服务器 传输文件 41 1 概述 这
  • 45 openEuler搭建Nginx服务器-Nginx概述和安装

    文章目录 45 openEuler搭建Nginx服务器 Nginx概述和安装45 1 概述45 2 安装 45 openEuler搭建Nginx服务器 Nginx概述和安装 45 1 概述 Nginx 是一款轻量级的 Web 服务器 反向代
  • 14 KVM虚拟机配置-配置虚拟设备(其它常用设备)

    文章目录 14 KVM虚拟机配置 配置虚拟设备 xff08 其它常用设备 xff09 14 1 概述14 2 元素介绍14 3 配置示例 14 KVM虚拟机配置 配置虚拟设备 xff08 其它常用设备 xff09 14 1 概述 除存储设备
  • 15 KVM虚拟机配置-体系架构相关配置

    文章目录 15 KVM虚拟机配置 体系架构相关配置15 1 概述15 2 元素介绍15 3 AArch64架构配置示例15 4 x86 64架构配置示例 15 KVM虚拟机配置 体系架构相关配置 15 1 概述 XML中还有一部分体系架构相
  • 16 KVM虚拟机配置-其他常见配置项

    文章目录 16 KVM虚拟机配置 其他常见配置项16 1 概述16 2 元素介绍16 3 配置示例 16 KVM虚拟机配置 其他常见配置项 16 1 概述 除系统资源和虚拟设备外 xff0c XML配置文件还需要配置一些其他元素 xff0c
  • windows查看默认编码类型

    xfeff xfeff 开始 cmd 运行chcp 你会得到一个数 例 xff1a 如936 xff0c 那就是GBK简体中文 ANSI代码页为1252 xff0c 日文代码页为932
  • 三、@PathVariable

    3 1 64 PathVariable 映射 URL 绑定的占位符 带占位符的 URL 是 Spring3 0 新增的功能 xff0c 该功能在SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义通过 64 PathVari
  • 走进CSDN

    走进CSDN 关注CSDN不久 xff0c 最近浏览的次数增多 xff0c CSDN的资讯刚开始基本上看的懂的不多 xff0c 专业名词扎堆的论坛 xff0c CSDN的氛围个人感觉挺好的 xff0c 一群俗称 程序员 的人聚集讨论问题 x
  • B端产品经理基本工作流程

    产品岗位必备素质 产品是一个门槛较低的岗位 xff0c 是一个看起来很容易 xff0c 做起来各个地方都是bug的岗位 产品需要更多的是软实力 xff0c 把握产品的方向 xff0c 目标用户是谁 xff0c 场景是什么 xff0c 达到怎
  • Mac实用的远程ssh连接工具( Royal TSX安装及使用)

    Mac实用的远程ssh连接工具 Royal TSX安装及使用 1 下载地址 https www royalapps com ts mac download 2 如何连接远程服务器 2 1 首先下载插件Terminal 2 2 然后创建新的D

随机推荐

  • 尝试VC控制外部程序

    这两天尝试VC控制外部程序呢 xff0c 慢慢完善 在参考了网络学习以后 xff0c 简单做了以下工作 xff1a 期间用了spy 43 43 器件 void CVCControlDlg OnStartreader 启动朗读女 TODO A
  • Windows Sever 2012 R2设置组策略对“不显示最后的登录名”选项已启用

    Windows Sever 2012 R2设置组策略对 不显示最后的登录名 选项已启用 作者 xff1a 我道梦 关注我的CSDN博客 xff0c 更多笔记知识还在更新 xff01 设置组策略启用 不显示最后的登录名 后 xff0c 系统将
  • Ubuntu22.04.1 & WIN11 双系统+双硬盘 grub启动项中无WIN11开机引导

    本机UEFI 43 GPT安装的双系统 xff0c 两块固态硬盘 xff0c 两个系统各自使用自己的硬盘分区 xff0c xff08 选择的全盘安装在新硬盘 xff0c 没有自定义分区 xff0c 所以安装的时候也没有提示与当前window
  • tightvnc,tightvnc软件介绍,详细介绍

    tightvnc一款用于windows操作系统的应用软件 xff0c 是一款远程控制软件 出门在外忘了带档案怎么办 xff1f FTP server 上头忘了开帐号怎么办 xff1f 这些麻烦的问题其实都可以靠 VNC 解决 tightvn
  • OpenCore-EFI-配置模版(持续更新)

    前言 随着OpenCore日臻完善 xff0c 将在以后会更多的用于黑苹果的安装 同时 xff0c 在各位大佬的大力支持与推广 xff0c 各种入门 xff0c 进阶教程的推出 xff0c OpenCore已经从神界降临到人间 逐渐为普通黑
  • OpenCore(OC)引导开机声音与图形界面设置

    关键字 xff1a OC xff1b OpenCore xff1b 引导 xff1b 开机声音 xff1b 图形界面 下面的设置基于OpenCore0 5 8 04 10编译版与1 22 0 0版OpenCore Configurator
  • The BMJ研究:现有的新冠病毒诊断AI模型,几乎毫无用处

    图片出处 xff1a unsplash 本文作者 xff1a 朱演瑞 新型冠状病毒对全球健康造成了严重的威胁 xff0c 为了减轻医疗保健系统的负担 xff0c 也给患者提供最佳的护理 xff0c 高效的诊断和疾病预后信息问题亟待解决 理论
  • 06-Docker-Centos 7.2 (Vmware最小化安装)之一篇搞定hyperledger/fabric的e2e_cli测试运行所遇到的ERROR总结

    bug产生原因分析如下 xff1a 1 系统过于单纯或复杂 xff08 即最小化安装与全部安装以及自行安装了很多软件 xff09 xff0c 很多命令和工具无法使用和执行或冲突 2 自己操作失误 xff0c 敲错代码 xff08 关键词和语
  • 秒懂Java之方法引用(method reference)详解

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 相关文章 xff1a 秒懂Java之深入理解Lambda表达式 文章目录 概述使用条件使用场景如何使用方法引用的类型调用类的静态方法调用传入的实例参数的方法调
  • 产品设计中关于思考力那些事

    这周的面试 xff0c 对我自己来说 xff0c 更像是一种迭代反思 从做什么怎么做 xff0c 到为什么做 xff0c 的一种强制思考 一方面是入行时间短 xff0c 另一方面是公司产品业务主导 xff0c 相对不需要产品去思考 xff0
  • 永不磨灭的设计模式(有这一篇真够了,拒绝标题党)

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述定义分类创建型 xff08 creational xff09 结构型 xff08 structural xff09 行为型 xff08 beha
  • shusheng007编程手记

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述工具篇IntelliJ IDEA在Idea中下载源码时 xff0c 报无法下载源码 Postman使用Postman发送Post请求服务端报得到
  • SpringBoot如何整合RabbitMQ

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述rabbitmq简介SpringBoot整合安装rabbitmq初级用法高级用法配置交换器与队列发送消息消费消息测试 总结 概述 好久没有写博客
  • 秒懂SpringBoot之@Async如何自定义线程池

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述异步初探线程池ThreadPoolExecutorThreadPoolTaskExecutor 验证线程池配置拒绝策略为AbortPolicy拒
  • 秒懂SpringBoot之参数验证全解析(@Validated与@Valid)

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述实例SpringBoot 验证概述引入依赖使用相关注解标记使用 64 Valid标记统一处理异常 高级用法复杂对象参数验证基本类型参数验证Ser
  • 如何添加本地JAR文件到Maven项目中

    版权申明 非商业目的可自由转载 博文地址 xff1a 出自 xff1a shusheng007 相关文章 xff1a 秒懂Java序列化与反序列化 秒懂 Java注解类型 xff08 64 Annotation xff09 秒懂Java多线
  • 秒懂Java泛型

    版权申明 非商业目的可自由转载 博文地址 xff1a https blog csdn net ShuSheng0007 article details 80720406 出自 xff1a shusheng007 文章目录 概述什么是泛型为什
  • 实际项目中如何使用Git做分支管理

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 相关文章 Git日常开发常用命令汇总 文章目录 前言概述Git的基本使用方法使用Git管理项目的方式主分支支持分支总结图 总结 前言 记得刚工作的时候根本不知
  • 秒懂Java代理与动态代理模式

    版权申明 非商业目的可自由转载 博文地址 xff1a https blog csdn net shusheng0007 article details 80864854 出自 xff1a shusheng007 设计模式汇总篇 xff0c
  • 秒懂Java动态编程(Javassist研究)

    版权申明 非商业目的可自由转载 博文地址 xff1a https blog csdn net ShuSheng0007 article details 81269295 出自 xff1a shusheng007 概述什么是动态编程 反射动态