/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.instrument.classloading;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;

import org.springframework.instrument.InstrumentationSavingAgent;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

LoadTimeWeaver relying on VM Instrumentation.

Start the JVM specifying the Java agent to be used — for example, as follows where spring-instrument-{version}.jar is a JAR file containing the InstrumentationSavingAgent class shipped with Spring and where {version} is the release version of the Spring Framework (e.g., 5.1.5.RELEASE).

-javaagent:path/to/spring-instrument-{version}.jar

In Eclipse, for example, add something similar to the following to the JVM arguments for the Eclipse "Run configuration":

-javaagent:${project_loc}/lib/spring-instrument-{version}.jar

Author:Rod Johnson, Juergen Hoeller
See Also:
  • InstrumentationSavingAgent
Since:2.0
/** * {@link LoadTimeWeaver} relying on VM {@link Instrumentation}. * * <p>Start the JVM specifying the Java agent to be used &mdash; for example, as * follows where <code>spring-instrument-{version}.jar</code> is a JAR file * containing the {@link InstrumentationSavingAgent} class shipped with Spring * and where <code>{version}</code> is the release version of the Spring * Framework (e.g., {@code 5.1.5.RELEASE}). * * <p><code>-javaagent:path/to/spring-instrument-{version}.jar</code> * * <p>In Eclipse, for example, add something similar to the following to the * JVM arguments for the Eclipse "Run configuration": * * <p><code>-javaagent:${project_loc}/lib/spring-instrument-{version}.jar</code> * * @author Rod Johnson * @author Juergen Hoeller * @since 2.0 * @see InstrumentationSavingAgent */
public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver { private static final boolean AGENT_CLASS_PRESENT = ClassUtils.isPresent( "org.springframework.instrument.InstrumentationSavingAgent", InstrumentationLoadTimeWeaver.class.getClassLoader()); @Nullable private final ClassLoader classLoader; @Nullable private final Instrumentation instrumentation; private final List<ClassFileTransformer> transformers = new ArrayList<>(4);
Create a new InstrumentationLoadTimeWeaver for the default ClassLoader.
/** * Create a new InstrumentationLoadTimeWeaver for the default ClassLoader. */
public InstrumentationLoadTimeWeaver() { this(ClassUtils.getDefaultClassLoader()); }
Create a new InstrumentationLoadTimeWeaver for the given ClassLoader.
Params:
  • classLoader – the ClassLoader that registered transformers are supposed to apply to
/** * Create a new InstrumentationLoadTimeWeaver for the given ClassLoader. * @param classLoader the ClassLoader that registered transformers are supposed to apply to */
public InstrumentationLoadTimeWeaver(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; this.instrumentation = getInstrumentation(); } @Override public void addTransformer(ClassFileTransformer transformer) { Assert.notNull(transformer, "Transformer must not be null"); FilteringClassFileTransformer actualTransformer = new FilteringClassFileTransformer(transformer, this.classLoader); synchronized (this.transformers) { Assert.state(this.instrumentation != null, "Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation."); this.instrumentation.addTransformer(actualTransformer); this.transformers.add(actualTransformer); } }
We have the ability to weave the current class loader when starting the JVM in this way, so the instrumentable class loader will always be the current loader.
/** * We have the ability to weave the current class loader when starting the * JVM in this way, so the instrumentable class loader will always be the * current loader. */
@Override public ClassLoader getInstrumentableClassLoader() { Assert.state(this.classLoader != null, "No ClassLoader available"); return this.classLoader; }
This implementation always returns a SimpleThrowawayClassLoader.
/** * This implementation always returns a {@link SimpleThrowawayClassLoader}. */
@Override public ClassLoader getThrowawayClassLoader() { return new SimpleThrowawayClassLoader(getInstrumentableClassLoader()); }
Remove all registered transformers, in inverse order of registration.
/** * Remove all registered transformers, in inverse order of registration. */
public void removeTransformers() { synchronized (this.transformers) { if (this.instrumentation != null && !this.transformers.isEmpty()) { for (int i = this.transformers.size() - 1; i >= 0; i--) { this.instrumentation.removeTransformer(this.transformers.get(i)); } this.transformers.clear(); } } }
Check whether an Instrumentation instance is available for the current VM.
See Also:
  • getInstrumentation()
/** * Check whether an Instrumentation instance is available for the current VM. * @see #getInstrumentation() */
public static boolean isInstrumentationAvailable() { return (getInstrumentation() != null); }
Obtain the Instrumentation instance for the current VM, if available.
See Also:
Returns:the Instrumentation instance, or null if none found
/** * Obtain the Instrumentation instance for the current VM, if available. * @return the Instrumentation instance, or {@code null} if none found * @see #isInstrumentationAvailable() */
@Nullable private static Instrumentation getInstrumentation() { if (AGENT_CLASS_PRESENT) { return InstrumentationAccessor.getInstrumentation(); } else { return null; } }
Inner class to avoid InstrumentationSavingAgent dependency.
/** * Inner class to avoid InstrumentationSavingAgent dependency. */
private static class InstrumentationAccessor { public static Instrumentation getInstrumentation() { return InstrumentationSavingAgent.getInstrumentation(); } }
Decorator that only applies the given target transformer to a specific ClassLoader.
/** * Decorator that only applies the given target transformer to a specific ClassLoader. */
private static class FilteringClassFileTransformer implements ClassFileTransformer { private final ClassFileTransformer targetTransformer; @Nullable private final ClassLoader targetClassLoader; public FilteringClassFileTransformer( ClassFileTransformer targetTransformer, @Nullable ClassLoader targetClassLoader) { this.targetTransformer = targetTransformer; this.targetClassLoader = targetClassLoader; } @Override @Nullable public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (this.targetClassLoader != loader) { return null; } return this.targetTransformer.transform( loader, className, classBeingRedefined, protectionDomain, classfileBuffer); } @Override public String toString() { return "FilteringClassFileTransformer for: " + this.targetTransformer.toString(); } } }