How Byte Buddy Powers Java Agents: Classloader Tricks and Dependency Solutions
This article explores using Byte Buddy to build Java agents, detailing the premain method, class loading mechanisms, handling dependency conflicts with Maven shade versus custom classloaders, and implementing a Dispatcher to bridge agent and application classloaders, providing practical code snippets and diagrams for each step.
Introduction
Byte Buddy is a code generation and manipulation library that allows creating and modifying Java classes at runtime without a compiler. The author revisits Byte Buddy documentation and a Bilibili tutorial by "Yuan Wei" to share practical experiences of building Java agents.
Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application and without the help of a compiler.1. Java Agent Loading
When a Java application starts, the JVM loads classes through a series of steps: verification, transformation, and loading. A Java agent can intercept this process, similar to AOP, to apply custom transformations.
The premain method provided by Byte Buddy is invoked before the application's main method. Its signature is:
public static void premain(String args, Instrumentation inst)During execution of a simple HelloWorld program, the JVM loads HelloWorld.class, the agent’s premain intercepts it, applies the transformation defined in the agent, and the transformed class ( helloWorld.class) is then loaded by the ClassLoader.
2. Dependency Conflicts
2.1 Conventional Solution
In real projects, an agent may bring many dependencies that clash with the application's own libraries. If the conflicting dependency is API‑compatible, you can exclude it in pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>If the APIs differ, simple exclusions are insufficient. The maven-shade-plugin can relocate packages to avoid clashes, but this approach has drawbacks in large projects, such as missed transitive dependencies, runtime surprises, and debugging difficulty.
Complex dependency graphs in big projects.
Easy to overlook a transitive dependency, causing runtime failures.
Renamed packages hinder debugging.
Therefore, the agent should avoid using maven-shade-plugin for conflict resolution and instead adopt a custom ClassLoader strategy.
2.2 Custom ClassLoader
Java class loading follows the parent‑delegation model: Application ClassLoader → Extension ClassLoader → Bootstrap ClassLoader. EaseAgent implements a custom ClassLoader whose parent is the Bootstrap ClassLoader, isolating agent dependencies from the application’s own classes.
3. ClassLoader Dependency Transfer
To enhance a user method printiInfo, the agent injects advice code. However, the advice references ObjectUtils.isEmpty(obj), which resides in a library loaded by the agent’s custom ClassLoader. When the application’s ClassLoader tries to load this advice, it cannot find ObjectUtils, resulting in a ClassNotFoundException.
The solution is to create a Dispatcher that lives in the Bootstrap ClassLoader. The Dispatcher holds a map of Action implementations (provided by the agent). When the application’s ClassLoader loads the advice, it delegates the lookup of the Action to the Dispatcher, which already has access to the required dependencies.
// Byte Buddy API to register a class into the Bootstrap ClassLoader
ClassInjector.UsingInstrumentation
.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation)
.inject(Collections.singletonMap(describe.resolve(),
classFileLocator.locate(className).resolve()));The Dispatcher is essentially a Map<String, Action> where each Action encapsulates the code to be executed. The agent registers its Actions during startup, and the application retrieves and runs them via the Dispatcher, eliminating the missing‑dependency problem.
Summary
Java agents built with Byte Buddy offer powerful, non‑intrusive instrumentation, making them attractive for APM and monitoring solutions. However, developing agents is challenging due to limited documentation, the need for deep Java knowledge, and the fast pace of JDK releases (EaseAgent currently supports only Java 8). Proper handling of classloader isolation and dependency transfer—using custom ClassLoaders and a Bootstrap‑level Dispatcher—mitigates conflicts and ensures reliable agent behavior.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
