An awesome Java bytecode class library!

Posted by blackcell on Sat, 29 Jan 2022 09:41:23 +0100

Author: rickiyang
source: www.cnblogs.com/rickiyang/p/11336268.html

Java bytecode is stored in binary form Class file, each The class file contains a Java class or interface.

Javaassist is a class library used to process Java bytecode. It can add new methods to a compiled class or modify existing methods, and does not need to have a deep understanding of bytecode. At the same time, you can also generate a new class object by completely manual way.

1. Create a class file using Javassist

First, you need to import the jar package:

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.25.0-GA</version>
</dependency>

Write classes to create objects:

package com.rickiyang.learn.javassist;

import javassist.*;

/**
 * @author rickiyang
 * @date 2019-08-06
 * @Desc
 */
public class CreatePerson {

    /**
     * Create a Person object
     *
     * @throws Exception
     */
    public static void createPseson() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        // 1. Create an empty class
        CtClass cc = pool.makeClass("com.rickiyang.learn.javassist.Person");

        // 2. Add a new field private String name;
        // The field name is name
        CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
        // The access level is private
        param.setModifiers(Modifier.PRIVATE);
        // The initial value is "xiaoming"
        cc.addField(param, CtField.Initializer.constant("xiaoming"));

        // 3. Generate getter and setter methods
        cc.addMethod(CtNewMethod.setter("setName", param));
        cc.addMethod(CtNewMethod.getter("getName", param));

        // 4. Add a parameterless constructor
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
        cons.setBody("{name = \"xiaohong\";}");
        cc.addConstructor(cons);

        // 5. Add a constructor with parameters
        cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
        // $0=this / $1,$2,$3...  Representative method parameters
        cons.setBody("{$0.name = $1;}");
        cc.addConstructor(cons);

        // 6. Create a method named printName, no parameters, no return value, and output the name value
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(name);}");
        cc.addMethod(ctMethod);

        //The class object created here will be compiled into Class file
        cc.writeFile("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");
    }

    public static void main(String[] args) {
        try {
            createPseson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

After the above main function is executed, person will be generated in the specified directory Class file:

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

package com.rickiyang.learn.javassist;

public class Person {
    private String name = "xiaoming";

    public void setName(String var1) {
        this.name = var1;
    }

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

    public Person() {
        this.name = "xiaohong";
    }

    public Person(String var1) {
        this.name = var1;
    }

    public void printName() {
        System.out.println(this.name);
    }
}

As we expected.

In Javassist, the class javaassit CtClass represents a class file. A GtClass (compile time class) object can process a class file, and ClassPool is the container of the CtClass object. It reads the class file on demand to construct the CtClass object, and saves the CtClass object for later use.

It should be noted that ClassPool will maintain all ctclasss created by it in memory. When the number of ctclasss is too large, it will occupy a lot of memory. The solution given in the API is to consciously call the detach() method of ctclasss to free memory.

ClassPool needs to pay attention to the following methods:

  1. getDefault: return the default ClassPool. It is in singleton mode. Generally, this method is used to create our ClassPool;
  2. appendClassPath, insertClassPath: add a ClassPath to the end of the class search path or insert it to the beginning. This method is usually used to write additional class search paths to solve the embarrassment that classes cannot be found in multiple class loader environments;
  3. toClass: load the modified CtClass into the context class loader of the current thread. The toClass method of CtClass is implemented by calling this method. It should be noted that once this method is called, you cannot continue to modify the loaded class;
  4. Get, getctclass: get the CtClass object of the class according to the class path name for subsequent editing.

Methods that CtClass needs to pay attention to:

  1. Freeze: freeze a class so that it cannot be modified;
  2. isFrozen: judge whether a class has been frozen;
  3. prune: delete unnecessary attributes of the class to reduce memory consumption. After calling this method, many methods cannot be used normally. Use with caution;
  4. Defrost: unfreeze a class so that it can be modified. If you know in advance that a class will be defrost, it is forbidden to call the prune method;
  5. detach: delete the class from the ClassPool;
  6. writeFile: generated according to CtClass class file;
  7. toClass: load the CtClass through the class loader.

Above, we created a new method using the CtMethod class. CtMthod represents a method in the class, which can be obtained through the API provided by CtClass or created by CtNewMethod. The method can be modified through the CtMethod object.

Some important methods in CtMethod:

  1. insertBefore: insert code at the beginning of the method;
  2. insterAfter: insert code before all return statements of the method to ensure that the statement can be executed unless exception is encountered;
  3. insertAt: insert code at the specified location;
  4. setBody: set the content of the method to the code to be written. When the method is modified by abstract, the modifier is removed;
  5. make: create a new method.

Notice that in the above code: setBody(), we use some symbols:

// $0=this / $1,$2,$3...  Representative method parameters
cons.setBody("{$0.name = $1;}");

There are many symbols that can be used, but different symbols have different meanings in different scenarios, so I won't repeat it here. You can see the javassist documentation. http://www.javassist.org/tutorial/tutorial2.html

Java core technology tutorial and sample source code: https://github.com/javastacks/javastack

2. Call the generated class object

1. Call by reflection

The above case is to create a class object and then output it after compilation Class file. What should we do if we want to call the properties or methods in the generated class object? javassist also provides corresponding APIs. The code for generating class objects is the same as the first paragraph. Replace the code written to the file with the following:

// Instead of writing files here, you can instantiate them directly
Object person = cc.toClass().newInstance();
// Set value
Method setName = person.getClass().getMethod("setName", String.class);
setName.invoke(person, "cunhua");
// Output value
Method execute = person.getClass().getMethod("printName");
execute.invoke(person);

Then execute the main method and you can see that the printName method is called.

2. By reading class file
ClassPool pool = ClassPool.getDefault();
// Set classpath
pool.appendClassPath("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");
CtClass ctClass = pool.get("com.rickiyang.learn.javassist.Person");
Object person = ctClass.toClass().newInstance();
//  ......  The following is used in the same way as through reflection
3. Interface mode

The above two methods are actually called through reflection. The problem is that there is no such object in our project, so the reflection method is troublesome and expensive. Then if your class object can be abstracted into a collection of methods, you can consider generating an interface class for this class. In this way, when newInstance(), we can forcibly convert it into an interface and omit the set of reflection.

Take the above Person class for example. Create a new Person I interface class:

package com.rickiyang.learn.javassist;

/**
 * @author rickiyang
 * @date 2019-08-07
 * @Desc
 */
public interface PersonI {

    void setName(String name);

    String getName();

    void printName();

}

The code of the implementation part is as follows:

ClassPool pool = ClassPool.getDefault();
pool.appendClassPath("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");

// Get interface
CtClass codeClassI = pool.get("com.rickiyang.learn.javassist.PersonI");
// Get the class generated above
CtClass ctClass = pool.get("com.rickiyang.learn.javassist.Person");
// The class generated by the code implements the PersonI interface
ctClass.setInterfaces(new CtClass[]{codeClassI});

// The following interface is used to directly call forced conversion
PersonI person = (PersonI)ctClass.toClass().newInstance();
System.out.println(person.getName());
person.setName("xiaolv");
person.printName();

It's easy to use.

2. Modify the existing class object #

As mentioned earlier, a new class object is added. This usage scenario has not been encountered yet. The common usage scenario should be to modify existing classes. For example, the common log aspect and permission aspect. We use javassist to implement this function.

There are the following class objects:

package com.rickiyang.learn.javassist;

/**
 * @author rickiyang
 * @date 2019-08-07
 * @Desc
 */
public class PersonService {

    public void getPerson(){
        System.out.println("get Person");
    }

    public void personFly(){
        System.out.println("oh my god,I can fly");
    }
}

Then modify him:

package com.rickiyang.learn.javassist;

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

import java.lang.reflect.Method;

/**
 * @author rickiyang
 * @date 2019-08-07
 * @Desc
 */
public class UpdatePerson {

    public static void update() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.rickiyang.learn.javassist.PersonService");

        CtMethod personFly = cc.getDeclaredMethod("personFly");
        personFly.insertBefore("System.out.println(\"Prepare parachute before takeoff\");");
        personFly.insertAfter("System.out.println(\"Successfully landed....\");");


        //Add a new method
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "joinFriend", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(\"i want to be your friend\");}");
        cc.addMethod(ctMethod);

        Object person = cc.toClass().newInstance();
        // Call the personFly method
        Method personFlyMethod = person.getClass().getMethod("personFly");
        personFlyMethod.invoke(person);
        //Call the joinFriend method
        Method execute = person.getClass().getMethod("joinFriend");
        execute.invoke(person);
    }

    public static void main(String[] args) {
        try {
            update();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The print log is added before and after the personFly method. Then a new method joinFriend is added. Execute the main function to find that it has been added.

In addition, it should be noted that for the statements in insertBefore() and setBody() above, if you are a single line statement, you can directly use double quotation marks, but in the case of multi line statements, you need to enclose the multi line statements with {}. javassist accepts only a single statement or a block of statements enclosed in curly braces.

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Finally got the IntelliJ IDEA activation code through the open source project. It's really fragrant!

3.Ali Mock tools are officially open source and kill all Mock tools on the market!

4.Spring Cloud 2020.0.0 is officially released, a new and subversive version!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!