001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.instrument.classloading; 018 019import java.lang.instrument.ClassFileTransformer; 020import java.lang.instrument.IllegalClassFormatException; 021import java.lang.instrument.Instrumentation; 022import java.security.ProtectionDomain; 023import java.util.ArrayList; 024import java.util.List; 025 026import org.springframework.instrument.InstrumentationSavingAgent; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ClassUtils; 030 031/** 032 * {@link LoadTimeWeaver} relying on VM {@link Instrumentation}. 033 * 034 * <p>Start the JVM specifying the Java agent to be used — for example, as 035 * follows where <code>spring-instrument-{version}.jar</code> is a JAR file 036 * containing the {@link InstrumentationSavingAgent} class shipped with Spring 037 * and where <code>{version}</code> is the release version of the Spring 038 * Framework (e.g., {@code 5.1.5.RELEASE}). 039 * 040 * <p><code>-javaagent:path/to/spring-instrument-{version}.jar</code> 041 * 042 * <p>In Eclipse, for example, add something similar to the following to the 043 * JVM arguments for the Eclipse "Run configuration": 044 * 045 * <p><code>-javaagent:${project_loc}/lib/spring-instrument-{version}.jar</code> 046 * 047 * @author Rod Johnson 048 * @author Juergen Hoeller 049 * @since 2.0 050 * @see InstrumentationSavingAgent 051 */ 052public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver { 053 054 private static final boolean AGENT_CLASS_PRESENT = ClassUtils.isPresent( 055 "org.springframework.instrument.InstrumentationSavingAgent", 056 InstrumentationLoadTimeWeaver.class.getClassLoader()); 057 058 059 @Nullable 060 private final ClassLoader classLoader; 061 062 @Nullable 063 private final Instrumentation instrumentation; 064 065 private final List<ClassFileTransformer> transformers = new ArrayList<>(4); 066 067 068 /** 069 * Create a new InstrumentationLoadTimeWeaver for the default ClassLoader. 070 */ 071 public InstrumentationLoadTimeWeaver() { 072 this(ClassUtils.getDefaultClassLoader()); 073 } 074 075 /** 076 * Create a new InstrumentationLoadTimeWeaver for the given ClassLoader. 077 * @param classLoader the ClassLoader that registered transformers are supposed to apply to 078 */ 079 public InstrumentationLoadTimeWeaver(@Nullable ClassLoader classLoader) { 080 this.classLoader = classLoader; 081 this.instrumentation = getInstrumentation(); 082 } 083 084 085 @Override 086 public void addTransformer(ClassFileTransformer transformer) { 087 Assert.notNull(transformer, "Transformer must not be null"); 088 FilteringClassFileTransformer actualTransformer = 089 new FilteringClassFileTransformer(transformer, this.classLoader); 090 synchronized (this.transformers) { 091 Assert.state(this.instrumentation != null, 092 "Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation."); 093 this.instrumentation.addTransformer(actualTransformer); 094 this.transformers.add(actualTransformer); 095 } 096 } 097 098 /** 099 * We have the ability to weave the current class loader when starting the 100 * JVM in this way, so the instrumentable class loader will always be the 101 * current loader. 102 */ 103 @Override 104 public ClassLoader getInstrumentableClassLoader() { 105 Assert.state(this.classLoader != null, "No ClassLoader available"); 106 return this.classLoader; 107 } 108 109 /** 110 * This implementation always returns a {@link SimpleThrowawayClassLoader}. 111 */ 112 @Override 113 public ClassLoader getThrowawayClassLoader() { 114 return new SimpleThrowawayClassLoader(getInstrumentableClassLoader()); 115 } 116 117 /** 118 * Remove all registered transformers, in inverse order of registration. 119 */ 120 public void removeTransformers() { 121 synchronized (this.transformers) { 122 if (this.instrumentation != null && !this.transformers.isEmpty()) { 123 for (int i = this.transformers.size() - 1; i >= 0; i--) { 124 this.instrumentation.removeTransformer(this.transformers.get(i)); 125 } 126 this.transformers.clear(); 127 } 128 } 129 } 130 131 132 /** 133 * Check whether an Instrumentation instance is available for the current VM. 134 * @see #getInstrumentation() 135 */ 136 public static boolean isInstrumentationAvailable() { 137 return (getInstrumentation() != null); 138 } 139 140 /** 141 * Obtain the Instrumentation instance for the current VM, if available. 142 * @return the Instrumentation instance, or {@code null} if none found 143 * @see #isInstrumentationAvailable() 144 */ 145 @Nullable 146 private static Instrumentation getInstrumentation() { 147 if (AGENT_CLASS_PRESENT) { 148 return InstrumentationAccessor.getInstrumentation(); 149 } 150 else { 151 return null; 152 } 153 } 154 155 156 /** 157 * Inner class to avoid InstrumentationSavingAgent dependency. 158 */ 159 private static class InstrumentationAccessor { 160 161 public static Instrumentation getInstrumentation() { 162 return InstrumentationSavingAgent.getInstrumentation(); 163 } 164 } 165 166 167 /** 168 * Decorator that only applies the given target transformer to a specific ClassLoader. 169 */ 170 private static class FilteringClassFileTransformer implements ClassFileTransformer { 171 172 private final ClassFileTransformer targetTransformer; 173 174 @Nullable 175 private final ClassLoader targetClassLoader; 176 177 public FilteringClassFileTransformer( 178 ClassFileTransformer targetTransformer, @Nullable ClassLoader targetClassLoader) { 179 180 this.targetTransformer = targetTransformer; 181 this.targetClassLoader = targetClassLoader; 182 } 183 184 @Override 185 @Nullable 186 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 187 ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 188 189 if (this.targetClassLoader != loader) { 190 return null; 191 } 192 return this.targetTransformer.transform( 193 loader, className, classBeingRedefined, protectionDomain, classfileBuffer); 194 } 195 196 @Override 197 public String toString() { 198 return "FilteringClassFileTransformer for: " + this.targetTransformer.toString(); 199 } 200 } 201 202}