3 - [JVM - Dynamic bytecode technology]

Keywords: Java jvm JDK Lombok

1 byte code technology application scenario

  • AOP Technology
  • Lombok de duplication plug-in
  • Dynamically modify class files, etc

2-byte technology advantage

Java bytecode enhancement refers to modifying and enhancing the function of Java bytecode after it is generated, which is equivalent to modifying the binary file of application program. Java bytecode enhancement is mainly to reduce redundant code and improve performance.

The main steps to enhance bytecode are as follows:

  1. Modify bytecode
    Get the original bytecode in memory, then modify its byte [] array through some tools (such as ASM, Javaasist) to get a new byte array.

  2. Make the modified bytecode effective
    There are two ways:
    1) Customize ClassLoader to load the modified bytecode;
    2) Replace the original bytecode: when the JVM loads the user's Class, it intercepts and returns the modified bytecode; or at runtime, it replaces the original bytecode by using the Instrumentation.redefineClasses method

3 common bytecode operation class library

3.1 BCEL

Byte Code Engineering Library(BCEL), which is part of the Jakarta project of the Apache Software Foundation.

BCEL is a widely used framework of Java classworking, which allows you to go deep into the details of class library operation in jvm assembly language.

BCEL and javassist have different ways to deal with bytecode. BCEL operates at the actual jvm instruction level (BCEL has rich jvm instruction set support), while javassist emphasizes the work at the source code level.

3.2 ASM

It is a lightweight Java bytecode operation framework, which directly involves the operations and instructions at the bottom of the JVM
High performance, high quality

3.3 CGLB

Generation of class library based on ASM

3.4 Javassist

Is an open source analysis, editing and creation of Java bytecode class library. Performance is worse than ASM, similar to cglib, but easy to use. Many open source frameworks use it.

3.4.1 Javassist advantages

  • It has less overhead and higher performance than reflection.
  • javassist performance is higher than reflection and lower than ASM

The runtime operation bytecode enables us to achieve the following functions:

  • Generate new classes dynamically
  • Dynamically change the structure of a class (add / remove / modify new properties / methods)

The outermost API of javassist is quite similar to that in JAVA's reflection package.
It is mainly composed of several classes: CtClass, CtMethod, and CtField. It is used to perform the same operations as java.lang.Class, java.lang.reflect.Method, java.lang.reflect.Method.Field in the JDK reflection API.

Method operation

  • Modify the method body of an existing method (insert code into the existing method body)
  • New method
  • Deletion method

3.4.2 limitations of javassist

The new syntax of JDK 5.0 does not support (including generics and enumerations), does not support annotation modification, but can be solved through the underlying javassist class. For details, refer to: javassist.bytecode.annotation

  • Array initialization is not supported, such as String [] {"1", "2"}, unless only the array has a capacity of 1
  • Inner and anonymous classes are not supported
  • continue and break expressions are not supported.

Some do not support inheritance relationships. for example

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

4 javassist example

4.1 creating classes using Javassist

Import dependency:

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

Create entity class:

package com.snow;

public class User {

    private String name;
    private Integer age;

}

Test:

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;

/**
 * Using java bytecode technology to create bytecode
 *
 */
public class Test {

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

        ClassPool pool = ClassPool.getDefault();
        // 1. Create user class
        CtClass userClass = pool.makeClass("com.snow.User");
        // 2. Create name and age attributes
        CtField nameField = CtField.make("	private String name;", userClass);
        CtField ageField = CtField.make("	private Integer age;", userClass);
        // 3. Add attribute
        userClass.addField(nameField);
        userClass.addField(ageField);
        // 4. Creation method
        CtMethod nameMethod = CtMethod.make("public String getName() {return name;}", userClass);
        // 5. Adding method
        userClass.addMethod(nameMethod);
        // 6. Add constructor
        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);

        // Generate class file
        userClass.writeFile("/Users/yangshuo/Desktop/learning");
    }

}

Execute the code and find that the class file is created:

//
// 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 using Javassist to dynamically modify bytecode

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. Use reflection technology to perform a method
        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...");
    }
}

Console printing:

sum...
package com.snow;

import java.lang.reflect.Method;

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

/**
 * Dynamic modification of bytecode file
 * 
 */
public class Test {

    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            // Read 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));}");
            // Adding method
            userClass.addMethod(method);
            userClass.writeFile("/Users/yangshuo/Desktop/learning");
            // Dynamic execution method
            Class clazz = userClass.toClass();
            Object newInstance = clazz.newInstance();

            Method sumMethod = clazz.getDeclaredMethod("sum", int.class, int.class);
            System.out.println("Opening things");
            sumMethod.invoke(newInstance, 2, 5);
            // Using javassist to implement dynamic proxy.
            System.out.println("Submission of things");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Console printing:

Opening things
sun:7
 Submission of things
Published 629 original articles, won praise 212, visited 130000+
His message board follow

Posted by seanpaulcail on Sun, 01 Mar 2020 04:31:51 -0800