This is the second day of my participation in the First Challenge 2022

Life is too short to have a dog

First, get to know ByteBuddy

In previous blogs, we have learned some basic concepts of Java Agent and how to write a simple Java Agent, but the Agent writing method used in previous blogs is relatively primitive and cumbersome. In the original logic, we use Instrument to perform binary operation and modification directly. This method requires the user to have a clear understanding of the Java Class file format. In short, it requires a non-human operation such as translating binary files manually. To further simplify the process of writing Java Agents, we will introduce ByteBuddy, a bytecode processing tool.

ByteBuddy is a library of code generation and manipulation classes that can be used to create and modify Java classes at runtime without the involvement of a compiler. ByteBuddy is based on ASM, another bytecode manipulation tool, but it’s much easier to use than ASM, which requires some knowledge of Java bytecode. ByteBuddy provides a set of sophisticated and convenient apis that make it easy for users to manipulate bytecodes without having to know Java bytecodes and class file formats (either by using the Java Agent or at program build time).

Second, write a simple Java Agent — method time statistics

From the above description, we can see that ByteBuddy was not created solely to create Java Agents. We just use the API provided by ByteBuddy to generate Java Agents that are more maintainable. Let’s use a simple example to see how to write a Java Agent using ByteBuddy.

The Java Agent we want to write below is mainly used for method execution time statistics, referring to the previous use of AOP way of thinking, we need to carry out the following processing:

  • Specify the object (which can be a class, method, or annotated element) to intercept.
  • Specify how to handle intercepted objects;

In Java Agent, all bytecode operations need to be performed through Instrumentation. In order to complete the above two operations and operations about Instrumentation, ByteBuddy provides the AgentBuilder class to provide the required API interface. Let’s take a look at the APIS provided by AgentBuilder using the following example.

DemoAgent (example Agent)

Public class DemoAgent {/** * process the main thread before it starts ** @param agentArgs Proxy request parameter * @param Instrumentation */ public static void premain(String agentArgs, Instrumentation instrumentation) { handleInstrument(instrumentation); } /** * @param instrumentation pile */ private static void instrumentation (instrumentation instrumentation) { new AgentBuilder.Default() .type(ElementMatchers.nameEndsWith("App")) .transform((builder, type, classLoader, module) -> builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(TimeInterceptor.class))) .installOn(instrumentation); }}Copy the code

TimeInterceptor (method execution time statistics interceptor)

/** * Time statistics interceptor, * * @author Brucebat * @version 1.0 * @since Created at 2021/12/30 10:31 am */ public class TimeInterceptor { /** * method interception, Note that it is possible to block all decorator methods (including private methods) * * @param method method to be processed * @param callable original method execution * @return execution result */ @runtimeType public static Object intercept(@Origin Method method, @SuperCall Callable<? > callable) throws Exception { long start = System.currentTimeMillis(); System.out.println("agent test: before method invoke! Method name: " + method.getName()); try { return callable.call(); } catch (Exception e) {system.out.println (' Exception '+ LLDB message ()); throw e; } finally { System.out.println("agent test: after method invoke! Method name: " + method.getName()); System.out.println(method + ": took " + (System.currentTimeMillis() - start) + " millisecond"); }}}Copy the code

In the example above, we use the default implementation class of AgentBuilder (the creator pattern is not hard to find here, but it is not the focus of the discussion) to do three things with the default implementation class of AgentBuilder:

  1. throughIdentified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher);Method, which specifies the object to be processed by the current Agent. In this case, the object to be processed is all the names of the objectAppType of ending;
  2. throughExtendable transform(Transformer transformer);Method specifies what needs to be done with the object being intercepted, using lambda shorthand forTransformer#transformMethod implementation. Through the implementation processbuilder.method()To further clarify which methods need to be processed, in this case all methods that meet the previous interception criteria are processed and then passedintercept()Methods andMethodDelegationTo inject another implementation of the intercepted method, in this exampleTimeInterceptorTo complete the execution time statistics for the method. See if this feels a bit like the proxy pattern (or AOP in general), especiallyTimeInterceptorThe processing logic in ByteBuddy, except that it doesn’t use reflection in the process, which is an advantage of using ByteBuddy;
  3. Finally, after the completion of the interception of the specified object and the preparation of the object processing logic, throughResettableClassFileTransformer installOn(Instrumentation instrumentation);completeInstrumentationloadingClassFileTransformer(i.e., the logic above regarding file modification) logic;

As you can see from the above flow, AgentBuilder essentially helps us with the implementation and loading logic for ClassFileTransformer. Instead of writing a ClassFileTransformer and modifying the binary data in it, AgentBuilder allows us to be more specific and focused on the entire processing logic. During the writing process, we only need to focus on the objects that need to be modified and the modified logic. All the rest is done through the API.

Third, summary

This article mainly introduces the outline of ByteBuddy and the process of using ByteBuddy to create Java Agent. The specific principles of ByteBuddy are not explained here and will be introduced in the following chapters.

Finally, on the first day of the New Year, I wish you all a happy and healthy New Year and an early end to the epidemic