org.springframework.core.OverridingClassLoader Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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.core;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
/**
* {@code ClassLoader} that does not always delegate to the parent loader
* as normal class loaders do. This enables, for example, instrumentation to be
* forced in the overriding ClassLoader, or a "throwaway" class loading behavior
* where selected application classes are temporarily loaded in the overriding
* {@code ClassLoader} for introspection purposes before eventually loading an
* instrumented version of the class in the given parent {@code ClassLoader}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 2.0.1
*/
public class OverridingClassLoader extends DecoratingClassLoader {
/** Packages that are excluded by default. */
public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[]
{"java.", "javax.", "sun.", "oracle.", "javassist.", "org.aspectj.", "net.sf.cglib."};
private static final String CLASS_FILE_SUFFIX = ".class";
static {
ClassLoader.registerAsParallelCapable();
}
@Nullable
private final ClassLoader overrideDelegate;
/**
* Create a new OverridingClassLoader for the given ClassLoader.
* @param parent the ClassLoader to build an overriding ClassLoader for
*/
public OverridingClassLoader(@Nullable ClassLoader parent) {
this(parent, null);
}
/**
* Create a new OverridingClassLoader for the given ClassLoader.
* @param parent the ClassLoader to build an overriding ClassLoader for
* @param overrideDelegate the ClassLoader to delegate to for overriding
* @since 4.3
*/
public OverridingClassLoader(@Nullable ClassLoader parent, @Nullable ClassLoader overrideDelegate) {
super(parent);
this.overrideDelegate = overrideDelegate;
for (String packageName : DEFAULT_EXCLUDED_PACKAGES) {
excludePackage(packageName);
}
}
@Override
public Class loadClass(String name) throws ClassNotFoundException {
if (this.overrideDelegate != null && isEligibleForOverriding(name)) {
return this.overrideDelegate.loadClass(name);
}
return super.loadClass(name);
}
@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (isEligibleForOverriding(name)) {
Class result = loadClassForOverriding(name);
if (result != null) {
if (resolve) {
resolveClass(result);
}
return result;
}
}
return super.loadClass(name, resolve);
}
/**
* Determine whether the specified class is eligible for overriding
* by this class loader.
* @param className the class name to check
* @return whether the specified class is eligible
* @see #isExcluded
*/
protected boolean isEligibleForOverriding(String className) {
return !isExcluded(className);
}
/**
* Load the specified class for overriding purposes in this ClassLoader.
* The default implementation delegates to {@link #findLoadedClass},
* {@link #loadBytesForClass} and {@link #defineClass}.
* @param name the name of the class
* @return the Class object, or {@code null} if no class defined for that name
* @throws ClassNotFoundException if the class for the given name couldn't be loaded
*/
@Nullable
protected Class loadClassForOverriding(String name) throws ClassNotFoundException {
Class result = findLoadedClass(name);
if (result == null) {
byte[] bytes = loadBytesForClass(name);
if (bytes != null) {
result = defineClass(name, bytes, 0, bytes.length);
}
}
return result;
}
/**
* Load the defining bytes for the given class,
* to be turned into a Class object through a {@link #defineClass} call.
*
The default implementation delegates to {@link #openStreamForClass}
* and {@link #transformIfNecessary}.
* @param name the name of the class
* @return the byte content (with transformers already applied),
* or {@code null} if no class defined for that name
* @throws ClassNotFoundException if the class for the given name couldn't be loaded
*/
@Nullable
protected byte[] loadBytesForClass(String name) throws ClassNotFoundException {
InputStream is = openStreamForClass(name);
if (is == null) {
return null;
}
try {
// Load the raw bytes.
byte[] bytes = FileCopyUtils.copyToByteArray(is);
// Transform if necessary and use the potentially transformed bytes.
return transformIfNecessary(name, bytes);
}
catch (IOException ex) {
throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
}
}
/**
* Open an InputStream for the specified class.
*
The default implementation loads a standard class file through
* the parent ClassLoader's {@code getResourceAsStream} method.
* @param name the name of the class
* @return the InputStream containing the byte code for the specified class
*/
@Nullable
protected InputStream openStreamForClass(String name) {
String internalName = name.replace('.', '/') + CLASS_FILE_SUFFIX;
return getParent().getResourceAsStream(internalName);
}
/**
* Transformation hook to be implemented by subclasses.
*
The default implementation simply returns the given bytes as-is.
* @param name the fully-qualified name of the class being transformed
* @param bytes the raw bytes of the class
* @return the transformed bytes (never {@code null};
* same as the input bytes if the transformation produced no changes)
*/
protected byte[] transformIfNecessary(String name, byte[] bytes) {
return bytes;
}
}