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 &mdash; 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}