Byte-buddy’s website is bytebuddy.net

Chinese translation document [some text contents not complete] : notes.diguage.com/byte-buddy-…

Official English document: bytebuddy.net/#/tutorial

Brief introduction:

Byte Buddy is a bytecode generation and manipulation library for creating and modifying Java classes at runtime without the help of a compiler. In addition to the code generation utility that comes with the Java class library, Byte Buddy allows you to create any class and is not limited to implementing an interface for creating run-time proxies. In addition, Byte Buddy provides a convenient API to use Java proxies or manually change classes during the build process.

A brief introduction 0. Instrumentation

Excerpts from: www.ibm.com/developerwo…

Instrumentation is a new feature in Java SE 5 that allows you to build an application-independent Agent to monitor and assist programs running on the JVM, and even replace and modify the definition of some classes. With this capability, developers can implement more flexible runtime virtual machine monitoring and Java class manipulation, which in effect provides a virtual-machine-supported implementation of AOP that allows developers to implement some of THE functionality of AOP without making any upgrades or changes to the JDK.

In Java SE 6, the Instrumentation package provides more powerful capabilities: instrument after startup, native code instrument, dynamic classpath changes, and more. These changes mean that Java has more dynamic control and interpretation capabilities, which makes the Java language more flexible.

In Java SE6, the biggest changes make runtime Instrumentation possible. In Java SE 5, Instrument requires that the agent classes be set with command line arguments or system arguments before execution. In actual execution, instrumentation Settings are started at the time of virtual machine initialization (before most Java libraries have been loaded). A callback function is set up in the virtual machine to detect the loading of specific classes and complete the actual work. However, in many cases, it is not possible to create an agent for the vm when it is started, which limits the application of instrument. The new features of Java SE 6 change this situation, through the Attach method in the Java Tool API, we can easily set the dynamic loading proxy class in the process of running, in order to achieve the purpose of instrumentation.

Instrumentation for native interfaces is also a new feature in Java SE 6, which enables functionality that could not be accomplished before – Instrumentation for native interfaces can be implemented in Java SE 6, This is accomplished through one or a series of prefix additions. Instrumentation in Java SE 6 also adds the ability to dynamically add class Paths. All of these new features enrich the instrument package, which in turn makes the Java language itself more powerful.

The implementation of the java.lang.instrument package depends on JVMTI. The Java Virtual Machine Tool Interface (JVMTI) is a set of local programming interfaces provided by Java Virtual machines for jVM-related tools. JVMTI was introduced with Java SE 5, Integration and replacement of the Java Virtual Machine Profiler Interface (JVMPI) and the Java Virtual Machine Debug Interface (JVMDI), In Java SE 6, JVMPI and JVMDI have disappeared. JVMTI provides a “proxy” program mechanism that enables third-party tools to connect to and access the JVM in a proxy manner, and to perform many JVMTI-related functions using the rich programming interfaces provided by JVMTI. In fact, the java.lang. Instrument package is implemented based on this mechanism: In the implementation of Instrumentation, there is a JVMTI agent, by calling JVMTI Java class related functions to complete the dynamic operation of Java classes. In addition to Instrumentation, JVMTI also provides a number of valuable functions for virtual machine memory management, thread control, methods and variable manipulation, and so on. Detailed information about the JVMTI, please refer to the Java SE 6 document (docs.oracle.com/javase/6/do…). The introduction.

1. Used in JavaAgent

Pom.xml introduces byte-Buddy dependencies and Agent packaging plugins:

// pom.xml ... <dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.19</version> </dependency> </dependencies> <build> <plugins> . < groupId > org, apache maven plugins < / groupId > < artifactId > maven - shade - plugin < / artifactId > < version > 3.2.4 < / version > <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <! --> < premain-class > XXX.AgentPremain</ premain-class > </manifestEntries> </transformer> </transformers>  <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> < artifactId > maven -- the compiler plugin < / artifactId > < version > 3.8.1 < / version > < configuration > < source > 1.8 < / source > <target>1.8</target> </configuration> </plugin> </plugins> </build>...Copy the code

Common code:

public interface Operation<K,V> {
    void put(K key,V value);
    V get(K key);
    void dump();
}
Copy the code
public class DefaultOperation implements Operation<String, String> { private Map<String, String> map = new HashMap<>(); public DefaultOperation() { // } @Override public void put(String key, String value) { map.put(key, value); } @Override public String get(String key) { return map.get(key); } @Override public void dump() { if (map.isEmpty()) { System.out.println("{}"); } StringBuilder sb = new StringBuilder("{"); for (Map.Entry<String, String> entry : map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key ! = null ? "\"" + key + "\"" : "null").append(":"); sb.append(value ! = null ? "\"" + value + "\"" : "null").append(", "); } if (sb.lastIndexOf(", ") == sb.length() - 2) { sb.delete(sb.length() - 2, sb.length()); } sb.append("}"); System.out.println(sb.toString()); }}Copy the code

General code of Agent module:

Public class TransformListener implements AgentBuilder.Listener {@override public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { } @Override public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { } @Override public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { } @Override public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { System.out.printf("Agent throw error before running, typeName=%s, message=%s%n", typeName, throwable.getMessage()); throwable.printStackTrace(); } @Override public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { } }Copy the code

1.1 Add member variables and assign values in constructors

// Define the constructor to inform public class ConstructorAdvice {@advice. OnMethodExit public static void onMethodExit(@Advice.FieldValue(value = "counter", readOnly = false) AtomicInteger counter) { counter = new AtomicInteger(); }}Copy the code
public class AgentPremain { public static void premain(String arguments, Instrumentation instrumentation) { System.out.println("Agent premain!" ); New AgentBuilder.default () // limit scope, Interface Operation to the realization of the class type (ElementMatchers. HasSuperType (ElementMatchers. NamedOneOf (Operation. Class. GetName ())) .and(ElementMatchers.not(ElementMatchers.isInterface())) ) .transform((builder, typeDescription, classLoader, // Define class member variable, name counter, type AtomicInteger, Scope public.defineField ("counter", atomicInteer.class, Visibility. Public) // Determine pointcuts for all constructors, followed by. Intercept (...) Constructor (ElementMatchers. Any ())) // Constructor into the pointcut above. Advice (advice.to (constructoradvice.class)) TransformListener()) .installOn(instrumentation); }}Copy the code

-javaAgent :/ XXX /byte-buddy-agent.jar

Run the test class:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Operation<String, String> operation = new DefaultOperation();
        Field field = operation.getClass().getDeclaredField("counter");
        Object object = field.get(operation);
        int counterValue = ((AtomicInteger) object).incrementAndGet();
        System.out.println(counterValue);
    }
Copy the code

Output result:

Agent premain!
1
Copy the code

Decompilating the DefaultOperation class using Arthas jad:

public class DefaultOperation implements Operation<String, String> { private Map<String, String> map; public AtomicInteger counter; @Override public void dump() { if (this.map.isEmpty()) { System.out.println("{}"); } StringBuilder sb = new StringBuilder("{"); for (Map.Entry<String, String> entry : this.map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key ! = null ? "\"" + key + "\"" : "null").append(":"); sb.append(value ! = null ? "\"" + value + "\"" : "null").append(", "); } if (sb.lastIndexOf(", ") == sb.length() - 2) { sb.delete(sb.length() - 2, sb.length()); } sb.append("}"); System.out.println(sb.toString()); } public DefaultOperation() { DefaultOperation defaultOperation = this; defaultOperation(null); this.counter = new AtomicInteger(); } private /* synthetic */ DefaultOperation(auxiliary.vawGY1XQ vawGY1XQ2) { this.map = new HashMap<String, String>(); } static { ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, -923819650); } @Override public String get(String key) { return this.map.get(key); } @Override public void put(String key, String value) { this.map.put(key, value); }}Copy the code

1.2 Adding Methods

Public class AddMethod {// @argument specifies the method entry reference. @fieldValue public static void method(@argument (0) String key, @argument (1) String value, @FieldValue("map") Map map) { map.put(key, value); }}Copy the code
public class AgentPremain { public static void premain(String arguments, Instrumentation instrumentation) { System.out.println("Agent premain!" ); new AgentBuilder.Default() .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName())) .and(ElementMatchers.not(ElementMatchers.isInterface())) ) .transform((builder, typeDescription, classLoader, Module) -> builder // Define a new method called put2, return type void, Class for public. DefineMethod ("put2", void. Class, PUBLIC) // Define the number and type of the new method. WithParameters (Arrays.asList(string.class, String.class)) // Delegate the implementation of the new method.intercept (methoddelegation.to (addmethod.class)). With (new TransformListener()) .installOn(instrumentation); }}Copy the code

-javaAgent :/ XXX /byte-buddy-agent.jar

Run the test class:

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Operation<String, String> operation = new DefaultOperation();
        Method put2 = operation.getClass().getDeclaredMethod("put2", String.class, String.class);
        put2.invoke(operation, "A", "a");
        put2.invoke(operation, "B", "b");
        operation.dump();
    }
Copy the code

Output result:

Agent premain!
{"A":"a", "B":"b"}
Copy the code

1.3 Interception Methods (using Advice)

// Constructor Advice public class ConstructorMethodAdvice {@advice.OnMethodEnter public static void onMethodEnter(@Advice.Origin String methodName) { System.out.printf("constructor %s start. \n", methodName); } @Advice.OnMethodExit public static void onMethodExit(@Advice.Origin String methodName) { System.out.printf("constructor %s end. \n", methodName); }}Copy the code
// put(K,V) method Advice public class PutMethodAdvice {@advice. OnMethodEnter public static Long onMethodEnter(@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) { Long start = System.currentTimeMillis(); if (value instanceof String) { value = "_" + value; } try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } return start; } @Advice.OnMethodExit public static void onMethodExit(@Advice.Origin Method method, @Advice.Enter Long start) { System.out.printf("method %s cost %d ms. \n", method.getName(), System.currentTimeMillis() - start); }}Copy the code
public class AgentPremain {


    public static void premain(String arguments, Instrumentation instrumentation) {
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        .method(ElementMatchers.named("put"))
                        .intercept(Advice.to(PutMethodAdvice.class))
                        .constructor(ElementMatchers.any())
                        .intercept(Advice.to(ConstructorMethodAdvice.class))
                )
                .with(new TransformListener())
                .installOn(instrumentation);
    }

}
Copy the code

-javaAgent :/ XXX /byte-buddy-agent.jar

Run the test class:

    public static void main(String[] args) {
        Operation<String, String> operation = new DefaultOperation();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();
    }
Copy the code

Output result:

Agent premain!
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() start. 
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() end. 
method put cost 21 ms. 
method put cost 20 ms. 
method put cost 20 ms. 
{"A":"_a", "B":"_b", "C":null}
Copy the code

Decomcompile DefaultOperation using Arthas’s JAD command:

public class DefaultOperation implements Operation<String, String> { private Map<String, String> map; @Override public void dump() { if (this.map.isEmpty()) { System.out.println("{}"); } StringBuilder sb = new StringBuilder("{"); for (Map.Entry<String, String> entry : this.map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key ! = null ? "\"" + key + "\"" : "null").append(":"); sb.append(value ! = null ? "\"" + value + "\"" : "null").append(", "); } if (sb.lastIndexOf(", ") == sb.length() - 2) { sb.delete(sb.length() - 2, sb.length()); } sb.append("}"); System.out.println(sb.toString()); } private /* synthetic */ void put$original$QVENVsOQ(String key, String value) { this.map.put(key, value); } public DefaultOperation() { System.out.printf("constructor %s start. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()"); DefaultOperation defaultOperation = this; defaultOperation(null); System.out.printf("constructor %s end. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()"); } private /* synthetic */ DefaultOperation(auxiliary.8KQdfbi7 kQdfbi7) { this.map = new HashMap<String, String>(); } static { ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 0x91E11EE); } @Override public String get(String key) { return this.map.get(key); } @Override public void put(String string, String string2) { Long l = System.currentTimeMillis(); if (string2 instanceof String) { string2 = "_" + string2; } try { TimeUnit.MILLISECONDS.sleep(20L); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } DefaultOperation defaultOperation = this; String string3 = string; String string4 = string2; defaultOperation.put$original$QVENVsOQ(string3, string4); System.out.printf("method %s cost %d ms. \n", DefaultOperation.class.getMethod("put", String.class, String.class).getName(), System.currentTimeMillis() - l); }}Copy the code

1.4 Intercepting methods (using MethodDelegation)

// Delegation public class PutMethodDelegation {@runtimeType public static void put(@runtimeType) @Argument(0) Object key, @RuntimeType @Argument(1) Object value, @SuperCall Runnable runnable) { System.out.printf("enter method put, key=%s, value=%s \n", key, value); try { runnable.run(); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit method put"); }}Copy the code
Delegation public class GetMethodDelegation {@runtimeType public Static Object get(@runtimeType) @Argument(0) Object key, @SuperCall Callable<Object> callable) { System.out.println("enter method get"); Object result = null; Try {// @supercall accessorized Callable<Object> encapsulates the original method call, returns void Runnable result = callable.call(); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit method get"); return result; }}Copy the code
public class AgentPremain {

    public static void premain(String arguments, Instrumentation instrumentation) {
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        .method(ElementMatchers.named("put"))
                        .intercept(MethodDelegation.to(PutMethodDelegation.class))
                        .method(ElementMatchers.named("get"))
                        .intercept(MethodDelegation.to(GetMethodDelegation.class))
                )
                .with(new TransformListener())
                .installOn(instrumentation);
    }
}
Copy the code

-javaAgent :/ XXX /byte-buddy-agent.jar

Run the test class:

    public static void main(String[] args) {
        Operation<String, String> operation = new DefaultOperation();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();
    }
Copy the code

Output result:

Agent premain!
enter method put, key=A, value=a 
exit method put
enter method put, key=B, value=b 
exit method put
enter method put, key=C, value=null 
exit method put
{"A":"a", "B":"b", "C":null}
Copy the code

Decomcompile DefaultOperation using Arthas’s JAD command:

public class DefaultOperation implements Operation<String, String> { private Map<String, String> map = new HashMap<String, String>(); @Override public void dump() { if (this.map.isEmpty()) { System.out.println("{}"); } StringBuilder sb = new StringBuilder("{"); for (Map.Entry<String, String> entry : this.map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key ! = null ? "\"" + key + "\"" : "null").append(":"); sb.append(value ! = null ? "\"" + value + "\"" : "null").append(", "); } if (sb.lastIndexOf(", ") == sb.length() - 2) { sb.delete(sb.length() - 2, sb.length()); } sb.append("}"); System.out.println(sb.toString()); } private /* synthetic */ void put$original$FYSPUuUe(String key, String value) { this.map.put(key, value); } private /* synthetic */ String get$original$FYSPUuUe(String key) { return this.map.get(key); } final /* synthetic */ String get$original$FYSPUuUe$accessor$qaEMliZ2(String string) { return this.get$original$FYSPUuUe(string); } final /* synthetic */ void put$original$FYSPUuUe$accessor$qaEMliZ2(String string, String string2) { this.put$original$FYSPUuUe(string, string2); } static { ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 420827887); } @Override public String get(String string) { return (String)GetMethodDelegation.get(string, new DefaultOperation$auxiliary$zU9K9I52(this, string)); } @Override public void put(String string, String string2) { PutMethodDelegation.put(string, string2, new DefaultOperation$auxiliary$ywsBi3iU(this, string, string2)); }}Copy the code

2. Use it in Runtime

Add dependencies in pom.xml

< the dependency > < groupId > net. Bytebuddy < / groupId > < artifactId > byte - buddy < / artifactId > < version > 1.10.19 < / version > </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> The < version > 1.10.19 < / version > < / dependency >Copy the code

2.1 Interception Method (using Advice)

public class PutMethodAdvice { @Advice.OnMethodEnter public static Long onEnter(@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object key, @Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) { key = "_" + key; value = "_" + (value ! = null ? value : ""); Long startMillis = System.currentTimeMillis(); try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return startMillis; } @Advice.OnMethodExit public static void onExit(@Advice.Enter Long startMillis) { System.out.printf("method put cost %d  ms \n", System.currentTimeMillis() - startMillis); }}Copy the code
public class InterceptMethodEnhancer { public static void interceptByAdvice(){ ByteBuddyAgent.install(); new ByteBuddy() .rebase(DefaultOperation.class) .visit(Advice.to(PutMethodAdvice.class).on(ElementMatchers.named("put"))) .visit(Advice.to(GetMethodAdvice.class).on(ElementMatchers.named("get"))) .make() .load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); }}Copy the code

Run the test class:

        Operation<String, String> operation = new DefaultOperation();
        InterceptMethodEnhancer.interceptByAdvice();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();
Copy the code

Output result:

method put cost 19 ms 
method put cost 20 ms 
method put cost 11 ms 
{"_A":"_a", "_B":"_b", "_C":"_"}
Copy the code

2.2 Adding annotations (on classes, member variables, methods)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Mark {
    String value() default "";

}
Copy the code
public class AddAnnotationEnhancer { public static void addOnClassAndFieldAndMethod() { ByteBuddyAgent.install(); / / build the annotation description AnnotationDescription AnnotationDescription = AnnotationDescription. Latent. Builder / / Set the annotation constant value.oftype (mark.class). Define ("value", "marked").build(); new ByteBuddy() .redefine(DefaultOperation.class) .annotateType(annotationDescription) .visit(new MemberAttributeExtension.ForField() .annotate(annotationDescription) .on(ElementMatchers.named("map"))) .visit(new MemberAttributeExtension.ForMethod() .annotateMethod(annotationDescription) .on(ElementMatchers.named("put"))) .make() .load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); }}Copy the code

Run the test class:

Operation<String, String> operation = new DefaultOperation(); AddAnnotationEnhancer.addOnClassAndFieldAndMethod(); Field field = operation.getClass().getDeclaredField("map"); if (! field.isAccessible()) { field.setAccessible(true); } Mark markOnClass = operation.getClass().getAnnotation(Mark.class); Mark markOnField = field.getAnnotation(Mark.class); Mark markOnMethod = operation.getClass().getMethod("put", String.class, String.class).getAnnotation(Mark.class); System.out.printf("Class's Mark value is %s \n", markOnClass.value()); System.out.printf("Field's Mark value is %s \n", markOnField.value()); System.out.printf("Method's Mark value is %s \n", markOnMethod.value());Copy the code

Output result:

Class's Mark value is marked 
Field's Mark value is marked 
Method's Mark value is marked 
Copy the code