Dubbo source code analysis - the use of dynamic compilation javaAssist

preface:

    In Dubbo, dynamic agent related technologies are widely used. Dynamic agents are mainly dynamic agents based on JDK and Javassist.

    For the use of JDK dynamic agent and source code analysis, please refer to the above.  

    This article focuses on the use of Javassist and the implementation of its dynamic agent.

1. Introduction to javassist

     Javassist is an open source class library for analyzing, editing and creating Java bytecode. It was founded by Shigeru Chiba of the Department of mathematics and computer science of Tokyo University of technology. It has joined the open source JBoss application server project to implement a dynamic AOP framework for JBoss by using javassist to operate on bytecode. Javassist is a sub project of JBoss. Its main advantage is that it is simple and fast. Directly use the form of java coding without understanding the virtual machine instructions, you can dynamically change the class structure or dynamically generate classes. (from Baidu Encyclopedia)

    Through this introduction, we know that javassist can operate java bytecode, and can also realize dynamic proxy by operating bytecode.

2. Basic use of javassist

2.1 introducing maven dependency

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

2.2 creating entity classes

package xw.demo.proxy.javassist;
public class Student {
    private String name;
    private int age;
	// Omit the get set method
    
    public String study(String book) {
        return name + "study " + book;
    }
}

2.3 basic API usage

2.3.1 ClassPool loading known classes

ClassPool pool = ClassPool.getDefault();
// Load Student
CtClass studentClass = pool.get("xw.demo.proxy.javassist.Student");

2.3.2 CtField get attribute

CtField nameField = studentClass.getField("name");
// Get the attribute type, which is java.lang.String in this example
String nameType = nameField.getType().getName();
// Get attribute annotation
Object[] annotations = nameField.getAnnotations();
...

2.3.3 CTMethod obtaining method information

CtMethod method = studentClass.getDeclaredMethod("study");
// Get method return type
CtClass returnType = method.getReturnType();
// Get method parameter type
CtClass[] parameterTypes = method.getParameterTypes();

2.3.4 obtaining construction method information from ctconstructor

CtConstructor[] declaredConstructors = studentClass.getDeclaredConstructors();
if (null != declaredConstructors) {
    for (CtConstructor ctConstuctor : declaredConstructors) {
        // Get constructor name
        String name = ctConstuctor.getName();
        // Gets the constructor parameter type
        CtClass[] ctConstuctorParameterTypes = ctConstuctor.getParameterTypes();
    }
}

The use of javassist is relatively simple. But this is just an appetizer. What's really important is that it is used to dynamically create a new class. Let's use javassist to create a new Student class with the same properties and methods as above.

2.3.5 javassist creates a new class

public static void newClassTest() {

    ClassPool pool = ClassPool.getDefault();
    // Create class information
    CtClass studentClass = pool.makeClass("xw.demo.proxy.javassist.Student_copy");

    try {
        // Create name attribute
        CtField nameField = new CtField(pool.get("java.lang.String"), "name", studentClass);
        nameField.setModifiers(Modifier.PUBLIC);
        studentClass.addField(nameField);

        // Create age attribute
        CtField ageField = new CtField(CtClass.intType, "age", studentClass);
        ageField.setModifiers(Modifier.PUBLIC);
        studentClass.addField(ageField);

        CtMethod studyMethod = new CtMethod(pool.get("java.lang.String"),
                                            "study", new CtClass[]{pool.get("java.lang.String")}, studentClass);
        studyMethod.setModifiers(Modifier.PUBLIC);
        studyMethod.setBody("return name + \"study \" + $1;");
        studentClass.addMethod(studyMethod);

        // Write out to file
        studentClass.writeFile("D:\\test");
    } catch (CannotCompileException | IOException e) {
        e.printStackTrace();
    } catch (NotFoundException e) {
        e.printStackTrace();
    }
}

Finally, a student will be generated_ Copy.class. The details are as follows

package xw.demo.proxy.javassist;

public class Student_copy
{
  public String name;
  public int age;
  
  public String study(String paramString)
  {
    return this.name + "study " + paramString;
  }
}

2.4 creation of dynamic agent

    Previously, I wrote an article on implementing dynamic proxy through JDK proxy. It is more convenient to use JDK's own. So when we use Javassist, how to implement dynamic proxy is actually very simple. Look directly at the example.

2.4.1 create example method

// Create proxied class
public class EchoService {
    public String echo(String msg) {
        return "echo " + msg;
    }
}

public static void proyTest() {
    ClassPool pool = new ClassPool();
    try {
        CtClass echoServiceClass = pool.get("xw.demo.proxy.javassist.EchoService");
        // Get its echo method and add an execution statement before and after the method
        CtMethod echoMethod = echoServiceClass.getDeclaredMethod("echo");
        echoMethod.insertBefore("before echo...");
        echoMethod.insertAfter("after echo...");

        // Execute echo method
        EchoService echoService = (EchoService)echoServiceClass.toClass().newInstance();
        String world = echoService.echo("world");
        System.out.println(world);
    } catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

// Execution results:
before echo...
echo world    
after echo...

Javassist can implement dynamic proxy for methods by executing bytecode and adding custom code before and after method execution.

2.4.2 use of Javassist dynamic agent in Dubbo

    Dubbo uses Javassist to implement dynamic proxy. The code is as follows:

public class JavassistProxyFactory extends AbstractProxyFactory {

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
		// See the getProxy() method for details
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
}

The author will not show the specific details, but also dynamically generate a Proxy class to realize the dynamic Proxy of invoker.

Tags: Java Back-end

Posted on Mon, 22 Nov 2021 16:56:23 -0500 by weevil