“This is the second day of my participation in the November Gwen Challenge. See details of the event: The last Gwen Challenge 2021”.

preface

In the previous article, we approach the Java Agent probe technology through actual practice. In the process of hot replacement, we use javasist to enhance the bytecode of the target class. The so-called bytecode enhancement technology is a kind of technology that can modify the existing bytecode or dynamically generate new bytecode files. There are many implementations of bytecode enhancement technology, such as: ASM, Cglib, javasist, etc. Javasist was used to modify the behavior of our target class in the previous article, but it did not focus on the specific use of javasist. This article will bring you closer to the art of bytecode enhancement: javasist

Approached Javasist

Official website: www.javassist.org/ GitHub: github.com/jboss-javas…

Javasist (Java Programming Assistant) makes it easier for us to manipulate bytecode. Javasist is a class library that allows developers to define a new class or modify a class file at runtime. Javasist provides two levels of API: source level and bytecode level. The so-called source level, to put it simply is more convenient for us to fool to call, we do not have to pay attention to the specification details of bytecode implementation, which is a major advantage of Javasist, simple programming, improve efficiency, who does not love it; The corresponding bytecode level is a bit more demanding, allowing us to write bytecode files as if we were writing code in the editor.

In writing this article, I purposely went to Javasist community github.com/jboss-javas… , basically two or three versions a year, so it is quite active, temporarily do not have to worry about the open source version no maintenance and difficult to choose. So nonsense to this, then into the actual combat link!

Javasist of actual combat

First introduce poM dependencies:

<dependency>
   <groupId>org.javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.28.0 - GA</version>
</dependency>
Copy the code

The entire Javasist operation bytecode has four classes that are the most critical and core. In fact, if you look at the composition of a class (method + attribute), you probably know what there are:

The name of the class describe
Javassist.CtClass Javassist.CtClass represents an abstraction of a class file, and a CtClass object is a handle to a class file
ClassPool ClassPool is a container that holds CtClass objects. When we need to retrieve the CtClass of a specified class, we can specify the fully qualified name of the class to retrieve the CtClass. To manipulate bytecode, the first step is to get the corresponding CtClass from the ClassPool. From a development perspective, the ClassPool is a hash table of CtClass objects, with the class name as the key and CtClass as the value
CtMethod An instance represents a method
CtField An instance represents an attribute

With the key classes in place, let’s take a look at the key apis for those key classes (mainly ClassPool and CtClass) :

CtClass

The title
writeFile() translates the CtClass object into a class file and writes it on a local disk.
toBytecode() Get bytecode
toClass() Gets the contextClassLoader of the current thread of execution to load the bytecode file
setName() The name of the class is changed, but it is actually copied based on the current CtClass, so it sets the name of the new class
defrost() If a CtClass object is converted to a class file by writeFile(), toClass(), or toBytecode(), Javassist freezes the CtClass object. Further modification of the CtClass object is not allowed. This is meant to warn developers when they try to modify loaded class files, because the JVM does not allow classes to be reloaded.Then we can use this method to unfreeze the CtClass and then modify it
detach() Note that while ClassPool is running, all ctClasses created willforever. If the number of CtClass objects becomes alarmingly large, the ClassPool specification could resultHuge memory consumption(This rarely happens, because Javassist tries to reduce memory consumption in various ways). To avoid this problem, you can explicitly remove an unnecessary CtClass object from the ClassPool. If called on a CtClass objectdetach(), the CtClass object will be removed from the ClassPool. Of course, in addition to avoiding this problem,There is another way: the current ClassPool is removed, and when garbage is collected, all associated CTClasses are removed

ClassPool

The title
getDefault() The default search Path for the ClassPool obtained by getDefault() is System Path
insertClassPath If our javasist runs in the implementation of customized loaders such as Jboss and Tomcat, we cannot get some classes under the path if we get by getDefault. At this time, we can specify some search paths through this method

InsertClassPath = insertClassPath = insertClassPath = insertClassPath = insertClassPath The classloading mechanism provides parent delegates, but javasist provides a similar mechanism. We can assign the parent ClassPool to the ClassPool, so that whenever we call get(), the parent ClassPool will be looked for first, like this:

ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");
Copy the code

Javasist, in addition to this, also supports to find from the son to grandpa’s head, that is, to find their own first, and then ask their father to find, this can be controlled by the property childFirstLookup.

The code field

With that said, let’s go straight to the code

Modify the behavior of an existing class through Javasist

Here’s a definition of the class we want to modify:

public class TargetObject {
    public void sayHello(a){
        System.out.println("hello"); }}Copy the code

Next I’ll change sayHello’s behavior using javasist, just like we would with AOP:

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.cf.study.DailyStudy.javasisttest.TargetObject");
CtMethod method = ctClass.getDeclaredMethod("sayHello");

String beforeExecute = "System.out.println("before...");";
String afterExecute = "System.out.println("after...");";
method.insertBefore(beforeExecute);
method.insertAfter(afterExecute);

TargetObject targetObjectAgent = (TargetObject)ctClass.toClass().getConstructor().newInstance();
targetObjectAgent.sayHello();
Copy the code

Output:

before...
hello
after...
Copy the code

reference

Tech.meituan.com/2019/09/05/… www.javassist.org/tutorial/tu…