Browse Source
ClassLoaderRule with @ClassLoaderConfiguration allows easy creation FilteringClassLoader. Changed usage pattern of ClassUtils.isPresent(…) in order to ease testing. Copied the ShadowingClassloader from SpringFramework to get a constructor that doesn't shadow anything by default. Original pull request: #202.pull/18/merge
9 changed files with 492 additions and 86 deletions
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright 2017 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 |
||||
* |
||||
* http://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.data.classloadersupport; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* configures the {@link ClassLoaderRule}. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
@Target({ElementType.METHOD, ElementType.TYPE}) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
public @interface ClassLoaderConfiguration { |
||||
|
||||
/** |
||||
* classes of which the package that contains them will be loaded by the {@link HidingClassLoader} not by |
||||
* the normal {@code ClassLoader}. |
||||
* |
||||
* @return list of classes not to shadow. |
||||
*/ |
||||
Class[] shadowPackage() default {}; |
||||
|
||||
/** |
||||
* prefixes of class names that will be loaded by the {@link HidingClassLoader}. |
||||
* |
||||
* @return list of class prefixes which will not be shadowed. |
||||
*/ |
||||
String[] shadowByPrefix() default {}; |
||||
|
||||
/** |
||||
* classes of which the package that contains them will be hidden by the {@link HidingClassLoader}. |
||||
* |
||||
* @return list of classes to hidePackage. |
||||
*/ |
||||
Class[] hidePackage() default {}; |
||||
|
||||
/** |
||||
* classes from packages that will be hidden by the {@link HidingClassLoader}. |
||||
* |
||||
* @return list of classes of which the package will be hidden. |
||||
*/ |
||||
String[] hideByPrefix() default {}; |
||||
} |
||||
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
/* |
||||
* Copyright 2017 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 |
||||
* |
||||
* http://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.data.classloadersupport; |
||||
|
||||
import static java.util.Arrays.*; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import org.junit.rules.MethodRule; |
||||
import org.junit.runners.model.FrameworkMethod; |
||||
import org.junit.runners.model.Statement; |
||||
|
||||
/** |
||||
* supports creation of tests that need to load classes with a {@link HidingClassLoader}. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class ClassLoaderRule implements MethodRule { |
||||
|
||||
public HidingClassLoader classLoader; |
||||
|
||||
@Override |
||||
public Statement apply(final Statement base, FrameworkMethod method, Object target) { |
||||
|
||||
CombinedClassLoaderConfiguration combinedConfiguration = new CombinedClassLoaderConfiguration( |
||||
method.getAnnotation(ClassLoaderConfiguration.class), |
||||
method.getDeclaringClass().getAnnotation(ClassLoaderConfiguration.class) |
||||
); |
||||
|
||||
classLoader = createClassLoader(combinedConfiguration); |
||||
|
||||
return new Statement() { |
||||
|
||||
@Override |
||||
public void evaluate() throws Throwable { |
||||
|
||||
try { |
||||
base.evaluate(); |
||||
} finally { |
||||
classLoader = null; |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private static HidingClassLoader createClassLoader(CombinedClassLoaderConfiguration configuration) { |
||||
|
||||
HidingClassLoader classLoader = new HidingClassLoader(mergeHidden(configuration)); |
||||
|
||||
for (Class shadow : configuration.shadowPackages) { |
||||
classLoader.excludeClass(shadow.getPackage().getName()); |
||||
} |
||||
|
||||
for (String shadow : configuration.shadowByPrefix) { |
||||
classLoader.excludePackage(shadow); |
||||
} |
||||
|
||||
return classLoader; |
||||
} |
||||
|
||||
private static List<String> mergeHidden(CombinedClassLoaderConfiguration configuration) { |
||||
|
||||
List<String> hidden = new ArrayList<String>(); |
||||
|
||||
for (Class aClass : configuration.hidePackages) { |
||||
hidden.add(aClass.getPackage().getName()); |
||||
} |
||||
|
||||
for (String aPackage : configuration.hideByPrefix) { |
||||
hidden.add(aPackage); |
||||
} |
||||
|
||||
return hidden; |
||||
} |
||||
|
||||
private static class CombinedClassLoaderConfiguration { |
||||
|
||||
final List<Class> shadowPackages = new ArrayList<Class>(); |
||||
final List<String> shadowByPrefix = new ArrayList<String>(); |
||||
final List<Class> hidePackages = new ArrayList<Class>(); |
||||
final List<String> hideByPrefix = new ArrayList<String>(); |
||||
|
||||
CombinedClassLoaderConfiguration(ClassLoaderConfiguration methodAnnotation, ClassLoaderConfiguration classAnnotation) { |
||||
|
||||
mergeAnnotation(methodAnnotation); |
||||
mergeAnnotation(classAnnotation); |
||||
} |
||||
|
||||
private void mergeAnnotation(ClassLoaderConfiguration methodAnnotation) { |
||||
|
||||
if (methodAnnotation != null) { |
||||
|
||||
shadowPackages.addAll(asList(methodAnnotation.shadowPackage())); |
||||
shadowByPrefix.addAll(asList(methodAnnotation.shadowByPrefix())); |
||||
hidePackages.addAll(asList(methodAnnotation.hidePackage())); |
||||
hideByPrefix.addAll(asList(methodAnnotation.hideByPrefix())); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
/* |
||||
* Copyright 2017 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 |
||||
* |
||||
* http://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.data.classloadersupport; |
||||
|
||||
import java.net.URLClassLoader; |
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* is intended for testing code that depends on the presence/absence of certain classes. |
||||
* |
||||
* Classes can be: |
||||
* <ul> |
||||
* <li>shadowed: reloaded by this classloader no matter if they are loaded already by the SystemClassLoader</li> |
||||
* <li>hidden: not loaded by this classloader no matter if they are loaded already by the SystemClassLoader. Trying to load these classes results in a {@link ClassNotFoundException}</li> |
||||
* <li>all other classes get loaded by the SystemClassLoader</li> |
||||
* </ul> |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class HidingClassLoader extends ShadowingClassLoader { |
||||
|
||||
private final Collection<String> hidden; |
||||
|
||||
HidingClassLoader(Collection<String> hidden) { |
||||
super(URLClassLoader.getSystemClassLoader()); |
||||
this.hidden = hidden; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> loadClass(String name) throws ClassNotFoundException { |
||||
|
||||
checkIfHidden(name); |
||||
return super.loadClass(name); |
||||
} |
||||
|
||||
@Override |
||||
protected Class<?> findClass(String name) throws ClassNotFoundException { |
||||
|
||||
checkIfHidden(name); |
||||
return super.findClass(name); |
||||
} |
||||
|
||||
private void checkIfHidden(String name) throws ClassNotFoundException { |
||||
|
||||
for (String prefix : hidden) { |
||||
if (name.startsWith(prefix)) { |
||||
throw new ClassNotFoundException(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
/* |
||||
* Copyright 2002-2012 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 |
||||
* |
||||
* http://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.data.classloadersupport; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.lang.instrument.ClassFileTransformer; |
||||
import java.lang.instrument.IllegalClassFormatException; |
||||
import java.net.URL; |
||||
import java.util.Enumeration; |
||||
import java.util.HashMap; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.core.DecoratingClassLoader; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* ClassLoader decorator that shadows an enclosing ClassLoader, |
||||
* applying registered transformers to all affected classes. |
||||
* |
||||
* @author Rob Harrop |
||||
* @author Juergen Hoeller |
||||
* @author Costin Leau |
||||
* @since 2.0 |
||||
* @see #addTransformer |
||||
* @see org.springframework.core.OverridingClassLoader |
||||
*/ |
||||
public class ShadowingClassLoader extends DecoratingClassLoader { |
||||
|
||||
/** Packages that are excluded by default */ |
||||
public static final String[] DEFAULT_EXCLUDED_PACKAGES = |
||||
new String[] {"java.", "javax.", "sun.", "oracle.", "com.sun.", "com.ibm.", "COM.ibm.", |
||||
"org.w3c.", "org.xml.", "org.dom4j.", "org.eclipse", "org.aspectj.", "net.sf.cglib", |
||||
"org.springframework.cglib", "org.apache.xerces.", "org.apache.commons.logging."}; |
||||
|
||||
|
||||
private final ClassLoader enclosingClassLoader; |
||||
|
||||
private final List<ClassFileTransformer> classFileTransformers = new LinkedList<ClassFileTransformer>(); |
||||
|
||||
private final Map<String, Class<?>> classCache = new HashMap<String, Class<?>>(); |
||||
|
||||
|
||||
/** |
||||
* Create a new ShadowingClassLoader, decorating the given ClassLoader. |
||||
* @param enclosingClassLoader the ClassLoader to decorate |
||||
*/ |
||||
public ShadowingClassLoader(ClassLoader enclosingClassLoader) { |
||||
Assert.notNull(enclosingClassLoader, "Enclosing ClassLoader must not be null"); |
||||
this.enclosingClassLoader = enclosingClassLoader; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Add the given ClassFileTransformer to the list of transformers that this |
||||
* ClassLoader will apply. |
||||
* @param transformer the ClassFileTransformer |
||||
*/ |
||||
public void addTransformer(ClassFileTransformer transformer) { |
||||
Assert.notNull(transformer, "Transformer must not be null"); |
||||
this.classFileTransformers.add(transformer); |
||||
} |
||||
|
||||
/** |
||||
* Copy all ClassFileTransformers from the given ClassLoader to the list of |
||||
* transformers that this ClassLoader will apply. |
||||
* @param other the ClassLoader to copy from |
||||
*/ |
||||
public void copyTransformers(ShadowingClassLoader other) { |
||||
Assert.notNull(other, "Other ClassLoader must not be null"); |
||||
this.classFileTransformers.addAll(other.classFileTransformers); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Class<?> loadClass(String name) throws ClassNotFoundException { |
||||
if (shouldShadow(name)) { |
||||
System.out.println("shadowing " + name); |
||||
Class<?> cls = this.classCache.get(name); |
||||
if (cls != null) { |
||||
return cls; |
||||
} |
||||
return doLoadClass(name); |
||||
} |
||||
else { |
||||
return this.enclosingClassLoader.loadClass(name); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Determine whether the given class should be excluded from shadowing. |
||||
* @param className the name of the class
|
||||
* @return whether the specified class should be shadowed |
||||
*/ |
||||
private boolean shouldShadow(String className) { |
||||
return (!className.equals(getClass().getName()) && !className.endsWith("ShadowingClassLoader") && |
||||
isEligibleForShadowing(className)); |
||||
} |
||||
|
||||
/** |
||||
* Determine whether the specified class is eligible for shadowing |
||||
* by this class loader. |
||||
* @param className the class name to check |
||||
* @return whether the specified class is eligible |
||||
* @see #isExcluded |
||||
*/ |
||||
protected boolean isEligibleForShadowing(String className) { |
||||
return isExcluded(className); |
||||
} |
||||
|
||||
|
||||
private Class<?> doLoadClass(String name) throws ClassNotFoundException { |
||||
String internalName = StringUtils.replace(name, ".", "/") + ".class"; |
||||
InputStream is = this.enclosingClassLoader.getResourceAsStream(internalName); |
||||
if (is == null) { |
||||
throw new ClassNotFoundException(name); |
||||
} |
||||
try { |
||||
byte[] bytes = FileCopyUtils.copyToByteArray(is); |
||||
bytes = applyTransformers(name, bytes); |
||||
Class<?> cls = defineClass(name, bytes, 0, bytes.length); |
||||
// Additional check for defining the package, if not defined yet.
|
||||
if (cls.getPackage() == null) { |
||||
int packageSeparator = name.lastIndexOf('.'); |
||||
if (packageSeparator != -1) { |
||||
String packageName = name.substring(0, packageSeparator); |
||||
definePackage(packageName, null, null, null, null, null, null, null); |
||||
} |
||||
} |
||||
this.classCache.put(name, cls); |
||||
return cls; |
||||
} |
||||
catch (IOException ex) { |
||||
throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex); |
||||
} |
||||
} |
||||
|
||||
private byte[] applyTransformers(String name, byte[] bytes) { |
||||
String internalName = StringUtils.replace(name, ".", "/"); |
||||
try { |
||||
for (ClassFileTransformer transformer : this.classFileTransformers) { |
||||
byte[] transformed = transformer.transform(this, internalName, null, null, bytes); |
||||
bytes = (transformed != null ? transformed : bytes); |
||||
} |
||||
return bytes; |
||||
} |
||||
catch (IllegalClassFormatException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public URL getResource(String name) { |
||||
return this.enclosingClassLoader.getResource(name); |
||||
} |
||||
|
||||
@Override |
||||
public InputStream getResourceAsStream(String name) { |
||||
return this.enclosingClassLoader.getResourceAsStream(name); |
||||
} |
||||
|
||||
@Override |
||||
public Enumeration<URL> getResources(String name) throws IOException { |
||||
return this.enclosingClassLoader.getResources(name); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue