Explain the underlying mechanism of Spring LTW: how does @EnableLoadTimeWeaving actually get bytecode transformed at class-load time?
answer
- java.lang.instrument: Instrumentation + ClassFileTransformer
- agent premain runs before main, saves Instrumentation
- InstrumentationLoadTimeWeaver.addTransformer
- transform(byte[]) → AspectJ rewrites → JVM defines woven bytes
- classes loaded before transformer = never woven
basics
~20 sThe Java agent captures the JVM's Instrumentation handle. Spring's InstrumentationLoadTimeWeaver registers an AspectJ ClassFileTransformer through it. As each class loads, the JVM passes its bytes to that transformer, which weaves in matching advice before the class is defined.
solid answer
~40 sIt rests on java.lang.instrument. A -javaagent (spring-instrument or aspectjweaver) runs before main and grabs the Instrumentation object. @EnableLoadTimeWeaving creates a LoadTimeWeaver bean, typically InstrumentationLoadTimeWeaver, wrapping that Instrumentation. Spring then registers an AspectJ weaving ClassFileTransformer via Instrumentation.addTransformer. From then on, whenever any classloader is about to define a class, the JVM invokes every registered transformer's transform() with the raw byte[]; the AspectJ transformer consults META-INF/aop.xml, and if a pointcut matches it rewrites the bytecode to inline the advice, returning the modified bytes which the JVM then defines. So the class that ends up in memory is already woven — no proxy object exists. A key consequence: classes loaded before the transformer is registered are never woven, so bootstrap ordering matters. Spring also offers ReflectiveLoadTimeWeaver and server-specific weavers where a plain -javaagent isn't available.
code
java · 19 lines// Conceptual sketch of what the agent + weaver wire up under the hood.
// (You do NOT write this yourself — Spring/AspectJ provide it — but this
// is the mechanism @EnableLoadTimeWeaving relies on.)
public class DemoAgent {
// JVM calls premain BEFORE main when launched with -javaagent
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd, byte[] classfileBuffer) {
// AspectJ's real transformer consults META-INF/aop.xml here
// and returns rewritten (woven) bytecode when a pointcut matches.
return maybeWeave(className, classfileBuffer);
}
});
}
}go deeper
Just know a Java agent + a class transformer rewrite bytecode as classes load.
Name Instrumentation, ClassFileTransformer, and InstrumentationLoadTimeWeaver and roughly how they connect.
Trace the full premain → addTransformer → transform() → define-woven-bytes flow and the load-ordering gotcha.
Reason about weaver variants (container instrumentable classloaders), bootstrap ordering, and the operational/debug implications of bytecode rewriting.
## The foundation: `java.lang.instrument` The JDK exposes a package, **`java.lang.instrument`**, that lets code inspect and rewrite class bytecode as it loads. Its two central types: - **`Instrumentation`** — the handle that lets you register transformers (`addTransformer`), re-transform classes, and query loaded classes. - **`ClassFileTransformer`** — an interface with one method, `byte[] transform(...)`, called by the JVM for each class being loaded/redefined; it receives the raw bytes and returns (possibly modified) bytes. Crucially, an `Instrumentation` handle is normally obtained **only by a Java agent** whose `premain(String args, Instrumentation inst)` is invoked by the JVM **before `main`**. That's why an agent is mandatory. ## Step-by-step, the Spring path 1. **Agent captures Instrumentation.** You launch with `-javaagent:spring-instrument-<ver>.jar`. Its agent class (`InstrumentationSavingAgent`) runs `premain` and stashes the `Instrumentation` in a static field so Spring can retrieve it later. (`aspectjweaver.jar` does the whole job itself, reading `aop.xml` and weaving without Spring's plumbing.) 2. **`@EnableLoadTimeWeaving` builds a `LoadTimeWeaver`.** At context startup Spring registers a `loadTimeWeaver` bean. The default is **`InstrumentationLoadTimeWeaver`**, which asks the saved agent for the `Instrumentation`. (Alternatives: **`ReflectiveLoadTimeWeaver`**, and container-specific weavers Spring can autodetect.) 3. **Spring registers the AspectJ transformer.** Spring adds an AspectJ `ClassFileTransformer` (`ClassPreProcessorAgentAdapter` / `AspectJWeavingEnabler`) to the `Instrumentation` via `addTransformer`. This transformer knows how to read `META-INF/aop.xml`. 4. **Class load triggers transform.** From now on, every time a classloader calls `defineClass`, the JVM first routes the raw `byte[]` through each registered transformer. The AspectJ transformer checks the class against `aop.xml`'s `<include>`/`<exclude>` scope and the aspects' pointcuts. 5. **Weaving happens.** If a pointcut matches, AspectJ rewrites the method bodies to inline the advice (e.g. inserts a call to `@Before` logic at method entry) and returns the new bytes. The JVM then defines *those* woven bytes as the class. If nothing matches, the original bytes pass through unchanged. The end state: the loaded `Class` object *is* the woven class. There is no separate proxy, and self-invocations, private methods, etc. carry the advice because the code itself changed. ## Why ordering matters (a core gotcha) `addTransformer` only affects classes loaded **after** it is registered. Any class loaded during early bootstrap — before the Spring context installs the transformer — is defined un-woven and **cannot be retroactively woven** (barring explicit `retransformClasses`, which AspectJ LTW does not generally do for you). This is why aspects targeting very-early or infrastructure classes may silently miss, and why the weaver should be enabled as early as possible. `-verbose` / `-showWeaveInfo` help detect "class already loaded, not woven" situations. ## Weaver variants - **`InstrumentationLoadTimeWeaver`** — the normal case, backed by a real `-javaagent`. - **`ReflectiveLoadTimeWeaver`** — uses reflection against a weaver the environment already exposes. - **App-server weavers** (Tomcat's `TomcatLoadTimeWeaver`, etc.) — some servers provide an **instrumentable classloader**, so Spring can weave *without* a `-javaagent` flag. `@EnableLoadTimeWeaving`'s `AUTODETECT` and the `LoadTimeWeavingConfigurer` interface exist to plug these in. ## Practical implications - Missing agent → `InstrumentationLoadTimeWeaver` can't get `Instrumentation` → no transformer → no weaving (or a clear startup error about instrumentation being unavailable). - The transformer runs on the hot class-loading path, so scope filters in `aop.xml` directly affect startup time. - Because bytecode differs from source, debugging/stack traces are less obvious; treat `-showWeaveInfo` as your source of truth for what got woven.
- Why can't a class that was already loaded during JVM bootstrap be woven by LTW?ClassFileTransformers only run when a class is defined. If the class was loaded before Spring registered the AspectJ transformer, its bytes were already defined un-woven, and standard LTW does not re-transform it afterward. Hence the transformer must be installed as early as possible.
- What role does the spring-instrument agent play versus the aspectjweaver agent?spring-instrument is minimal: its premain just captures the Instrumentation handle so Spring's InstrumentationLoadTimeWeaver can register the AspectJ transformer. aspectjweaver is the full AspectJ agent that reads aop.xml and performs weaving itself, independent of Spring's LoadTimeWeaver plumbing.
saying these in an interview costs you the question
- Saying LTW creates a proxy — it rewrites bytecode, no proxy
- Thinking transform() can weave classes already loaded
- Believing Instrumentation is obtainable from ordinary app code without an agent
- Confusing InstrumentationLoadTimeWeaver with a bean-post-processor proxy