001/* 002 * Copyright 2002-2012 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.util.Assert; 028import org.springframework.util.ClassUtils; 029 030/** 031 * {@link LoadTimeWeaver} relying on VM {@link Instrumentation}. 032 * 033 * <p>Start the JVM specifying the Java agent to be used, like as follows: 034 * 035 * <p><code class="code">-javaagent:path/to/org.springframework.instrument.jar</code> 036 * 037 * <p>where {@code org.springframework.instrument.jar} is a JAR file containing 038 * the {@link InstrumentationSavingAgent} class, as shipped with Spring. 039 * 040 * <p>In Eclipse, for example, set the "Run configuration"'s JVM args to be of the form: 041 * 042 * <p><code class="code">-javaagent:${project_loc}/lib/org.springframework.instrument.jar</code> 043 * 044 * @author Rod Johnson 045 * @author Juergen Hoeller 046 * @since 2.0 047 * @see InstrumentationSavingAgent 048 */ 049public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver { 050 051 private static final boolean AGENT_CLASS_PRESENT = ClassUtils.isPresent( 052 "org.springframework.instrument.InstrumentationSavingAgent", 053 InstrumentationLoadTimeWeaver.class.getClassLoader()); 054 055 056 private final ClassLoader classLoader; 057 058 private final Instrumentation instrumentation; 059 060 private final List<ClassFileTransformer> transformers = new ArrayList<ClassFileTransformer>(4); 061 062 063 /** 064 * Create a new InstrumentationLoadTimeWeaver for the default ClassLoader. 065 */ 066 public InstrumentationLoadTimeWeaver() { 067 this(ClassUtils.getDefaultClassLoader()); 068 } 069 070 /** 071 * Create a new InstrumentationLoadTimeWeaver for the given ClassLoader. 072 * @param classLoader the ClassLoader that registered transformers are supposed to apply to 073 */ 074 public InstrumentationLoadTimeWeaver(ClassLoader classLoader) { 075 Assert.notNull(classLoader, "ClassLoader must not be null"); 076 this.classLoader = classLoader; 077 this.instrumentation = getInstrumentation(); 078 } 079 080 081 @Override 082 public void addTransformer(ClassFileTransformer transformer) { 083 Assert.notNull(transformer, "Transformer must not be null"); 084 FilteringClassFileTransformer actualTransformer = 085 new FilteringClassFileTransformer(transformer, this.classLoader); 086 synchronized (this.transformers) { 087 if (this.instrumentation == null) { 088 throw new IllegalStateException( 089 "Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation."); 090 } 091 this.instrumentation.addTransformer(actualTransformer); 092 this.transformers.add(actualTransformer); 093 } 094 } 095 096 /** 097 * We have the ability to weave the current class loader when starting the 098 * JVM in this way, so the instrumentable class loader will always be the 099 * current loader. 100 */ 101 @Override 102 public ClassLoader getInstrumentableClassLoader() { 103 return this.classLoader; 104 } 105 106 /** 107 * This implementation always returns a {@link SimpleThrowawayClassLoader}. 108 */ 109 @Override 110 public ClassLoader getThrowawayClassLoader() { 111 return new SimpleThrowawayClassLoader(getInstrumentableClassLoader()); 112 } 113 114 /** 115 * Remove all registered transformers, in inverse order of registration. 116 */ 117 public void removeTransformers() { 118 synchronized (this.transformers) { 119 if (!this.transformers.isEmpty()) { 120 for (int i = this.transformers.size() - 1; i >= 0; i--) { 121 this.instrumentation.removeTransformer(this.transformers.get(i)); 122 } 123 this.transformers.clear(); 124 } 125 } 126 } 127 128 129 /** 130 * Check whether an Instrumentation instance is available for the current VM. 131 * @see #getInstrumentation() 132 */ 133 public static boolean isInstrumentationAvailable() { 134 return (getInstrumentation() != null); 135 } 136 137 /** 138 * Obtain the Instrumentation instance for the current VM, if available. 139 * @return the Instrumentation instance, or {@code null} if none found 140 * @see #isInstrumentationAvailable() 141 */ 142 private static Instrumentation getInstrumentation() { 143 if (AGENT_CLASS_PRESENT) { 144 return InstrumentationAccessor.getInstrumentation(); 145 } 146 else { 147 return null; 148 } 149 } 150 151 152 /** 153 * Inner class to avoid InstrumentationSavingAgent dependency. 154 */ 155 private static class InstrumentationAccessor { 156 157 public static Instrumentation getInstrumentation() { 158 return InstrumentationSavingAgent.getInstrumentation(); 159 } 160 } 161 162 163 /** 164 * Decorator that only applies the given target transformer to a specific ClassLoader. 165 */ 166 private static class FilteringClassFileTransformer implements ClassFileTransformer { 167 168 private final ClassFileTransformer targetTransformer; 169 170 private final ClassLoader targetClassLoader; 171 172 public FilteringClassFileTransformer(ClassFileTransformer targetTransformer, ClassLoader targetClassLoader) { 173 this.targetTransformer = targetTransformer; 174 this.targetClassLoader = targetClassLoader; 175 } 176 177 @Override 178 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 179 ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 180 181 if (!this.targetClassLoader.equals(loader)) { 182 return null; 183 } 184 return this.targetTransformer.transform( 185 loader, className, classBeingRedefined, protectionDomain, classfileBuffer); 186 } 187 188 @Override 189 public String toString() { 190 return "FilteringClassFileTransformer for: " + this.targetTransformer.toString(); 191 } 192 } 193 194}