Bytecode Programming | Generate JavaBeans with Javassist

Hello everyone, I'm Glacier~~

In the actual work process, we can implement the interception logic by instrumenting the Java bytecode in order to intercept the classes and methods we need to intercept, modify these classes and methods, or directly generate corresponding classes dynamically.

In this way, we can achieve the desired effect without modifying the source program. Today, we will use Javassist to dynamically generate JavaBean objects.

After mastering this knowledge point, we can dynamically generate JavaBean objects to deserialize the data sent by the client or respond to the data from the server when we use DAPM (distributed performance management system) by hand.

The relevant case program code can be obtained by following the official account: Glacier Technology , or directly from Github and Gitee.

Github:https://github.com/sunshinelyz/bytecode

Gitee:https://gitee.com/binghe001/bytecode

Note: The source code of this article corresponds to bytecode-javassist-03the program source code of .

development environment

  • JDK 1.8
  • IDEA 2018.03
  • Maven 3.6.0

Maven dependencies

Add the following environment dependencies to the project's pom.xml file.

<properties>
    <javassist.version>3.20.0-GA</javassist.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>${
    
    javassist.version}</version>
    </dependency>
</dependencies>

Case effect

The effect of the overall case is relatively simple, that is, by running the program we wrote, the class bytecode of the User class can be dynamically generated. As follows.

package io.binghe.bytecode.javassist.bean;

public class User {
    
    
    private String name = "binghe";

    public User() {
    
    
        this.name = "binghe";
    }

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

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

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

    public void printName() {
    
    
        System.out.println(this.name);
    }
}
  • In this User class, there is a member variable name, the default value is binghe.
  • There is a no-argument constructor and a parametric constructor, respectively.
  • The get/set method of the member variable name.
  • The method printName() that prints the member variable name.

After understanding the effect of the case, we began to implement how to dynamically generate this User class.

Case realization

For the specific case implementation, we can refer to the effect of the case to complete step by step. Here, we can divide the dynamic generation process of the entire User class into 6 steps, which are:

  • Create the User class.
  • Add name field.
  • Add no-argument constructor.
  • Added parameterized constructor.
  • Add get/set methods.
  • Add printName() method.

Well, just do what you say, and then follow these 5 steps to dynamically generate the User class.

Create User class

//使用默认的ClassPool
ClassPool pool = ClassPool.getDefault();

//1.创建一个空类
CtClass ctClass = pool.makeClass("io.binghe.bytecode.javassist.bean.User");

The creation method of the User class is the same as the class we created for HelloWorld before. The first step is to obtain a ClassPool object and create the User class by calling the makeClass method of the ClassPool object.

add name field

//2.新增一个字段 private String name; 字段的名称为name
CtField param = new CtField(pool.get("java.lang.String"), "name", ctClass);
//设置访问修饰符为private
param.setModifiers(Modifier.PRIVATE);
//设置字段的初始值为binghe
ctClass.addField(param, CtField.Initializer.constant("binghe"));

When adding a member variable name to the User class, the CtField class in Javassist is used. Here, the first parameter of the constructor of CtField we use is the type of the member variable, the second parameter is the name of the variable, and the third field indicates which class to add this variable to.

After creating the CtField object param, we call the setModifiers() method of param to set the access modifier, which is set to private here.

Next, assign the default value binghe to the member variable name. The effect generated by the above code is as follows.

private String name = "binghe";

Add no-argument constructor

//3.添加无参的构造函数
CtConstructor constructor = new CtConstructor(new CtClass[]{
    
    }, ctClass);
constructor.setBody("{" +
                    " $0.name = \"binghe\"; " +
                    "}");
ctClass.addConstructor(constructor);

When adding a parameterless constructor, the CtConstructor class in Javassist is used. The first parameter is the parameter type array of the constructor of the dynamically generated target class, and the second parameter indicates which class to add the constructor to.

Next, set the method body of the no-argument constructor by calling the setBody() method of CtConstructor. It should be noted here that when there is only one line of code in the method body, it can be omitted {}, but in order to prevent errors, Glacier strongly recommends not to omit whether the method has only one line of code or not {}.

Careful friends will definitely find that $0the member variable name is referenced in the method body. It is estimated that the friends also guessed what this $0is for. That's right, it gets compiled into after the User class is generated this.

In Javassist, there will be some other symbols with specific meanings, which we will explain uniformly at the end of the article.

The effect of this code is shown below.

public User() {
    
    
    this.name = "binghe";
}

Next, call the addConstructor() method of CtClass to add a parameterless constructor to the User class.

Add a parameterized constructor

//4.添加有参构造函数
constructor = new CtConstructor(new CtClass[]{
    
    pool.get("java.lang.String")}, ctClass);
constructor.setBody("{" +
                    "$0.name = $1;" +
                    "}");
ctClass.addConstructor(constructor);

The overall process of adding a parameterized constructor is the same as that of adding a parameterless constructor, except that when creating a CtConstructor object, pool.get("java.lang.String")an array element is added to the first parameter type array of the CtConstructor's constructor to represent the generated target The constructor of the class has a parameter of type String.

In addition, when setting the method body, the following code is used.

$0.name = $1;

Indicates that the first parameter of the constructor is assigned to the member variable name. Here, $0means this, $1means the first parameter, $2means the second parameter, and so on.

The effect of this code is shown below.

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

Add get/set methods

//5.添加getter和setter方法
ctClass.addMethod(CtNewMethod.setter("setName", param));
ctClass.addMethod(CtNewMethod.getter("getName", param));

It is relatively simple to add the get/set method, directly use the addMethod() of CtClass to add, and use the setter() method of CtNewMethod to generate the set method, where the first parameter is the name of the generated method, setName, and the second parameter indicates that For which field the setName method is generated.

Use the getter() method of CtNewMethod to generate the get() method. The first parameter is the name of the generated method, getName, and the second parameter indicates which field the getName method is generated for.

The effect of this code is shown below.

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

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

Add printName() method

//6.创建一个输出name的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{
    
    }, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{" +
        "System.out.println(name);" +
        "}");
ctClass.addMethod(ctMethod);

Adding the printName() method uses the CtMethod class in Javassist. When creating an object of the CtMethod class, the first parameter is the return type of the method, the second parameter is the method name printName, and the third parameter is the method parameter type array , the fourth parameter indicates which class to add the generated method to.

Next, call the CtMethod's setModifiers() method to set the access modifiers for the printName() method, which is set to public here. Then set the method body for the printName() method, and simply print the member variable name on the command line in the method body.

Finally, add the generated printName method to the User class through the addMethod() method of CtClass.

The effect of this code is shown below.

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

Complete case

In order to facilitate the friends to see the complete source code more clearly, here I also paste the complete source code, as shown below.

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 使用Javassist生成一个User类, 并测试
 */
public class CreateUserClass {
    
    

    /**
     * 使用Javassist创建一个User对象
     */
    public static void createUser() throws Exception{
    
    
        //使用默认的ClassPool
        ClassPool pool = ClassPool.getDefault();

        //1.创建一个空类
        CtClass ctClass = pool.makeClass("io.binghe.bytecode.javassist.bean.User");

        //2.新增一个字段 private String name; 字段的名称为name
        CtField param = new CtField(pool.get("java.lang.String"), "name", ctClass);
        //设置访问修饰符为private
        param.setModifiers(Modifier.PRIVATE);
        //设置字段的初始值为binghe
        ctClass.addField(param, CtField.Initializer.constant("binghe"));

        //3.添加无参的构造函数
        CtConstructor constructor = new CtConstructor(new CtClass[]{
    
    }, ctClass);
        constructor.setBody("{" +
                " $0.name = \"binghe\"; " +
                "}");
        ctClass.addConstructor(constructor);

        //4.添加有参构造函数
        constructor = new CtConstructor(new CtClass[]{
    
    pool.get("java.lang.String")}, ctClass);
        constructor.setBody("{" +
                "$0.name = $1;" +
                "}");
        ctClass.addConstructor(constructor);

        //5.添加getter和setter方法
        ctClass.addMethod(CtNewMethod.setter("setName", param));
        ctClass.addMethod(CtNewMethod.getter("getName", param));

        //6.创建一个输出name的方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{
    
    }, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{" +
                "System.out.println(name);" +
                "}");
        ctClass.addMethod(ctMethod);

        ctClass.writeFile();
    }
}

Effect demonstration

Write the main method and directly call the createUser() method of the CreateUserClass class, as shown below.

public static void main(String[] args) throws Exception {
    
    
    CreateUserClass.createUser();
}

After running the main() method, the bytecode of the User class we want is generated as shown below.

picture

The effect is in line with our expectations.

Summary

We use Javassist to dynamically generate the expected User class objects. Through the learning of this article, we have mastered how to use Javassist to generate JavaBean objects. Is it very simple? Little friends, quickly open IDEA and start it.

appendix

The article involves the reference variables inside the method in Javassist $0and $1, in Javassist, there are some other reference variables inside the method, which are summarized by Glacier for everyone to learn.

picture

Okay, let's stop here today, I'm Glacier, see you next time~~

write at the end

If you want to enter a big factory, want to get a promotion and a salary increase, or are confused about your current job, you can privately message me to communicate, I hope some of my experiences can help you~~

Recommended reading:

Okay, let's stop here today, friends, like, favorite, comment, and start walking with one click, I'm Glacier, see you in the next issue~~

Guess you like

Origin blog.csdn.net/l1028386804/article/details/123983180