JAVAssist字节码操作

Java 动态性的两种常见实现方式

  • 字节码操作
  • 反射

运行时操作字节码可以让我们实现如下功能:

  • 动态生成新的类
  • 动态改变某个类的结构(添加/删除/修改新的属性/方法)

优势:

  • 比反射开销小,性能高
  • JAVAa sist 性能高于反射,低于ASM

常见的字节码操作类库

BCEL

这是Apache Software Fundation 的jakarta 项目的一部分。BCEL 是javaclassworking 广泛使用的一种跨级啊,它可以让你深入JVM 汇编语言进行类的操作的细节。BCEL 与javassist 所强调的是源码级别的工作。

ASM

是一个轻量及java 字节码操作框架,直接涉及到JVM 底层的操作和指令。

CGLIB

是一个强大的,高性能,高质量的Code 生成类库,基于ASM 实现。

JAVAssist

JAVAssist 库的API

  • java ssist 最外层的API 和Java 的反射包中的API 颇为类似、
  • 它主要有Ct Class,CtMethod,以及CtField 及各类组成。用以执行和JDK 反射API 中java.lang.Class ,java.lang.reflect.Method ,java.lang.reflect.Method.Field 相同的操作。

官方介绍:

Javassist (Java Programming Assistant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level. If the users use the source-level API, they can edit a class file without knowledge of the specifications of the Java bytecode. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.

java ssist 库的简单使用

  • 使用一个全新的类
  • 使用XJAD 反编译工具,将生成的class 二年间反编译成JAVA 文件

例1:

javassist 库的API

  • 方法操作
  • 修改已有方法的方法体(插入代码到已有方法体)
  • 新增方法
  • 删除方法

各种符号替代

$0,$1, $2, …

this and actual parameters

$args

An array of parameters. The type of $args is Object[].

$$

All actual parameters.

For example,m($$) is equivalent to m($1,$2,…)

$cflow(…)

cflow variable

$r

The result type. It is used in a cast expression.

$w

The wrapper type. It is used in a cast expression.

$_

The resulting value

$sig

An array of java.lang.Class objects representing the formal parameter types.

$type

A java.lang.Class object representing the formal result type.

$class

A java.lang.Class object representing the class currently edited.

例2:

public class Demo02 {
    /**
     * 处理类的基本用法
     * @throws Exception
     */
    public static void test01() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.sinosoft.test.Emp");

        byte[] bytes = cc.toBytecode();
        System.out.println(Arrays.toString(bytes));

        System.out.println(cc.getName()); //获取类名
        System.out.println(cc.getSimpleName()); //获取简要类名
        System.out.println(cc.getSuperclass()); //获得父类
        System.out.println(cc.getInterfaces()); //获得接口

    }

    /**
     * 测试产生新的方法
     * @throws Exception
     */
    public static void test02() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bjsxt.test.Emp");

//        CtMethod m = CtNewMethod.make("public int add(int a,int b){return a+b;}", cc);

        CtMethod m = new CtMethod(CtClass.intType,"add",
                new CtClass[]{CtClass.intType,CtClass.intType},cc);
        m.setModifiers(Modifier.PUBLIC);
        m.setBody("{System.out.println(\"Hello!!!\");return $1+$2;}");

        cc.addMethod(m);

        //通过反射调用新生成的方法
        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();  //通过调用Emp无参构造器,创建新的Emp对象
        Method method = clazz.getDeclaredMethod("add", int.class,int.class);
        Object result = method.invoke(obj, 200,300);
        System.out.println(result);
    }

    /**
     * 修改已有的方法的信息,修改方法体的内容
     * @throws Exception
     */
    public static void test03() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.sinosoft.test.Emp");

        CtMethod cm = cc.getDeclaredMethod("sayHello",new CtClass[]{CtClass.intType});
        //方法执行前
        cm.insertBefore("System.out.println($1);System.out.println(\"start!!!\");");
        cm.insertAt(9, "int b=3;System.out.println(\"b=\"+b);");
        //方法执行后
        cm.insertAfter("System.out.println(\"after!!!\");");

        //通过反射调用新生成的方法
        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();  //通过调用Emp无参构造器,创建新的Emp对象
        Method method = clazz.getDeclaredMethod("sayHello", int.class);
        method.invoke(obj, 300);
    }

    /**
     * 属性的操作
     * @throws Exception
     */
    public static void test04() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.sinosoft.test.Emp");

//        CtField f1 = CtField.make("private int empno;", cc);
        CtField f1 = new CtField(CtClass.intType,"salary",cc);
        f1.setModifiers(Modifier.PRIVATE);
        cc.addField(f1);

//        cc.getDeclaredField("ename");   //获取指定的属性

        //增加相应的set和get方法
        cc.addMethod(CtNewMethod.getter("getSalary", f1));;
        cc.addMethod(CtNewMethod.getter("setSalary", f1));;

    }

    /**
     * 构造方法的操作
     * @throws Exception
     */
    public static void test05() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.sinosoft.test.Emp");

        CtConstructor[] cs = cc.getConstructors();
        for (CtConstructor c : cs) {
            System.out.println(c.getLongName());
        }
    }

    /**
     * 注解操作
     * @throws Exception
     */
    public static void test06() throws Exception{
         CtClass cc = ClassPool.getDefault().get("com.sinosoft.test.Emp");
         Object[] all = cc.getAnnotations();
         Author a = (Author)all[0];
         String name = a.name();
         int year = a.year();
         System.out.println("name: " + name + ", year: " + year);

    }

    public static void main(String[] args) throws Exception {
        test06();
    }
}

/**
 * 注解类
 *
*/
public @interface Author {
          String name();
           int year();
}

局限性:

  • JDK5.0 行语法不支持(包括泛型、枚举),不支持注解修改,但可以通过底层的java ssist 类来解决,具体参考javassist.bytecode.annotation
  • 不支持数组的初始化,如String[]{“1 “,”2 “},除非只有数组的容量为1
  • 不支持内部类和匿名类
  • 不支持continue 和btreak 表达式
  • 对于继承关系,有些不支持,例如:

Original: https://www.cnblogs.com/dooor/p/5289326.html
Author: Dvomu
Title: JAVAssist字节码操作

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/609444/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Docker(47)- 一键安装 docker 的 shell 脚本

    背景 有多个自己的云服务器,都要安装 docker,所以就尝试将安装的步骤转换成 shell 脚本,回头可以一键执行~ 创建 shell 脚本 touch install_dock…

    Linux 2023年5月28日
    092
  • 线程池如何保证核心线程一直存活

    转载请注明出处: 查看 ThreadPoolExecutor 类中的 getTask 方法,这个方法可以保持核心线程在没有任务的时候也可以一直处于存活状态 核心在于 workQue…

    Linux 2023年6月14日
    0163
  • Git常用命令

    克隆拉取远程代码 git clone https://xxxxxxxxx 本地添加远程仓库地址 git remote add origin(设定名字,随意。不过一般都叫这个名字) …

    Linux 2023年6月8日
    094
  • Jenkins+svn自动化部署完整教程

    1、概述 Jenkins 是一个可扩展的持续集成引擎。主要用于持续、自动地构建/测试软件项目、监控一些定时执行的任务。Jenkins用Java语言编写,可在Tomcat等流行的se…

    Linux 2023年6月7日
    096
  • Redis学习手册(Sorted-Sets数据类型)

    一、概述: Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有…

    Linux 2023年5月28日
    088
  • linux系统(centos)下kvm虚拟化用命令行给虚拟机添加硬盘

    linux系统(centos)下kvm虚拟化用命令行给虚拟机添加硬盘 背景 公司有用单台服务器使用kvm装虚拟机,利用webvirtmgr进行界面管理。当虚拟机创建时固定硬盘后,不…

    Linux 2023年6月8日
    0112
  • 脚本安装lamp

    脚本安装lamp [root@localhost ~]# mkdir lamp [root@localhost ~]# cd lamp/ [root@localhost lamp]…

    Linux 2023年6月6日
    0131
  • 如何设置redis中hash的field的expire ?

    redis 127.0.0.1:6379> hset expire:me name tom (integer) 0 redis 127.0.0.1:6379> hget…

    Linux 2023年5月28日
    0129
  • 实验1:SDN拓扑实践

    实验1:SDN拓扑实践 基础要求 a) mininet运行结果图 b)2的执行结果截图 2.a)3台交换机,每个交换机连接1台主机,3台交换机连接成一条线。 2.b)3台主机,每个…

    Linux 2023年6月7日
    0108
  • Git的使用

    Git的概念 Git是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的项目 Git的安装 Git官网https://git-scm.com/下载安装直接下一步…

    Linux 2023年6月13日
    092
  • java程序使用ssl证书连接mysql

    bash;gutter:false; 1. 在mysql服务器上生成证书 openssl genrsa 2048 > ca-key.pem openssl req -new …

    Linux 2023年6月7日
    0104
  • Linux下使用压力测试工具stress

    首先解压安装包到/usr/local/src/下 mv stress-1.0.4.tar.gz /usr/local/src​tar -zxf stress-1.0.4.tar.g…

    Linux 2023年6月13日
    093
  • 部署无人值守安装系统

    主机名 操作系统 ip地址 无人值守系统 CentOS Linux release 7.9.2009 (Core) 192.168.71.128 客户端 未安装系统 –…

    Linux 2023年6月13日
    0128
  • jdk8 时间

    package p2022; import java.text.SimpleDateFormat; import java.util.Date; /** * @descriptio…

    Linux 2023年6月8日
    0104
  • Ubuntu18开启默认root登录方法

    默认的Ubuntu 18.04系统在登陆界面上是不支持root用户直接登录的,但是你可以使用下面的方法让Ubuntu 18.04也支持root登录,其他类似的版本参考在Ubuntu…

    Linux 2023年6月7日
    0106
  • Django orm的managed参数

    Django orm的managed参数 如果一张表不是在django的models.py中创建表,而是该表由cmd或者Navicat或者其他方式创建的,或者该表是一个视图,那么也…

    Linux 2023年6月14日
    094
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球