3 -【 JVM - 动态字节码技术 】

余生颓废 提交于 2020-03-01 22:54:27

1 字节码技术应用场景

  • AOP 技术
  • Lombok 去除重复代码插件
  • 动态修改 class 文件等

2 字节技术优势

Java 字节码增强指的是在 Java 字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。Java 字节码增强主要是为了减少冗余代码,提高性能等。

实现字节码增强的主要步骤为:

  1. 修改字节码
    在内存中获取到原来的字节码,然后通过一些工具(如 ASM,Javaasist)来修改它的 byte[] 数组,得到一个新的 byte 数组。

  2. 使修改后的字节码生效
    有两种方法:
    1) 自定义 ClassLoader 来加载修改后的字节码;
    2)替换掉原来的字节码:在 JVM 加载用户的 Class 时,拦截,返回修改后的字节码;或者在运行时,使用 Instrumentation.redefineClasses 方法来替换掉原来的字节码

3 常见的字节码操作类库

3.1 BCEL

Byte Code Engineering Library(BCEL),这是 Apache Software Foundation 的 Jakarta 项目的一部分。

BCELJava classworking 广泛使用的一种框架,它可以让您深入 jvm 汇编语言进行类库操作的细节。

BCELjavassist 有不同的处理字节码方法,BCEL 在实际的 jvm 指令层次上进行操作(BCEL 拥有丰富的 jvm 指令集支持) 而 javassist 所强调的是源代码级别的工作。

3.2 ASM

是一个轻量级 Java 字节码操作框架,直接涉及到 JVM 底层的操作和指令
高性能,高质量

3.3 CGLB

生成类库,基于 ASM 实现

3.4 Javassist

是一个开源的分析,编辑和创建 Java 字节码的类库。性能较 ASM 差,跟 cglib 差不多,但是使用简单。很多开源框架都在使用它。

3.4.1 Javassist 优势

  • 比反射开销小,性能高。
  • javassist 性能高于反射,低于 ASM

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

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

javassist 的最外层的 API 和 JAVA 的反射包中的 API 颇为 类似 。
它主要由 CtClassCtMethod,以及 CtField 几个类组成。用以执行和 JDK 反射 APIjava.lang.Classjava.lang.reflect.Methodjava.lang.reflect.Method.Field 相同的 操作 。

方法操作

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

3.4.2 javassist 的局限性

JDK5.0 新语法不支持 ( 包括泛型、枚举 ) ,不支持注解修改,但可以通过底层的 javassist 类来解决,具体参考: javassist.bytecode.annotation

  • 不支持数组的初始化,如 String[]{“1”,“2”} ,除非只有数组的容量为 1
  • 不支持内部类和匿名类
  • 不支持 continue 和 break表达式。

对于继承关系,有些不支持。例如

  • class A {}
  • class B extends A {}
  • class C extends B {}

4 javassist 例子

4.1 使用 Javassist 创建类

导入依赖:

<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.12.1.GA</version>
</dependency>

创建实体类:

package com.snow;

public class User {

    private String name;
    private Integer age;

}

测试:

package com.snow;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

/**
 * 使用java字节码技术创建字节码
 *
 */
public class Test {

    public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {

        ClassPool pool = ClassPool.getDefault();
        // 1.创建user类
        CtClass userClass = pool.makeClass("com.snow.User");
        // 2.创建name 和age属性
        CtField nameField = CtField.make("	private String name;", userClass);
        CtField ageField = CtField.make("	private Integer age;", userClass);
        // 3.添加属性
        userClass.addField(nameField);
        userClass.addField(ageField);
        // 4.创建方法
        CtMethod nameMethod = CtMethod.make("public String getName() {return name;}", userClass);
        // 5.添加方法
        userClass.addMethod(nameMethod);
        // 6.添加构造函数
        CtConstructor ctConstructor = new CtConstructor(
                new CtClass[] { pool.get("java.lang.String"), pool.get("java.lang.Integer") }, userClass);

        ctConstructor.setBody("	{ this.name = name; this.age = age; }");
        userClass.addConstructor(ctConstructor);

        // 生成class文件
        userClass.writeFile("/Users/yangshuo/Desktop/learning");
    }

}

执行代码,发现创建了 class 文件:
在这里插入图片描述

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.snow;

public class User {
    private String name;
    private Integer age;

    public String getName() {
        return this.name;
    }

    public User(String var1, Integer var2) {
        this.name = this.name;
        this.age = this.age;
    }
}

4.2 使用 Javassist 动态修改字节码

package com.snow;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException,
            InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        // 1.使用反射技术执行某方法
        Class<?> forName = Class.forName("com.snow.Test");
        Object newInstance = forName.newInstance();
        Method method = forName.getDeclaredMethod("sum", int.class, int.class);
        method.invoke(newInstance, 1, 5);
    }

    static public void sum(int a, int b) {
        System.out.println("sum...");
    }
}

控制台打印:

sum...
package com.snow;

import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

/**
 * 动态修改字节码文件
 * 
 */
public class Test {

    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            // 读取com.itmayiedu.User
            CtClass userClass = pool.get("com.snow.User");
            CtMethod method = new CtMethod(CtClass.voidType, "sum", new CtClass[] { CtClass.intType, CtClass.intType },
                    userClass);
            method.setBody("{System.out.println(\"sun:\" + ($1 + $2));}");
            // 添加方法
            userClass.addMethod(method);
            userClass.writeFile("/Users/yangshuo/Desktop/learning");
            // 动态执行方法
            Class clazz = userClass.toClass();
            Object newInstance = clazz.newInstance();

            Method sumMethod = clazz.getDeclaredMethod("sum", int.class, int.class);
            System.out.println("开启事物");
            sumMethod.invoke(newInstance, 2, 5);
            // 使用 javassist 实现动态代理。
            System.out.println("提交事物");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

控制台打印:

开启事物
sun:7
提交事物
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!