Browse Source

DATACMNS-993 - Polishing.

Removed custom JUnit integration as we can just create HidingClassLoader instances in the test and the integration actually causes more code being needed (additional JUnit rule, method level annotations etc.).

Tweaked ShadowingClassLoader to make obvious what has been changed over the Spring Framework variant. Created upstream ticket [0] to ask for the tweaks that would allow us to remove the class again.

Original pull request: #202.
Related ticket: SPR-15439
[0] https://jira.spring.io/browse/SPR-15439
pull/347/head
Oliver Gierke 9 years ago
parent
commit
805c70fbfa
  1. 60
      src/test/java/org/springframework/data/classloadersupport/ClassLoaderConfiguration.java
  2. 113
      src/test/java/org/springframework/data/classloadersupport/ClassLoaderRule.java
  3. 51
      src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java
  4. 51
      src/test/java/org/springframework/data/classloadersupport/ShadowingClassLoader.java
  5. 22
      src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java

60
src/test/java/org/springframework/data/classloadersupport/ClassLoaderConfiguration.java

@ -1,60 +0,0 @@ @@ -1,60 +0,0 @@
/*
* 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 {};
}

113
src/test/java/org/springframework/data/classloadersupport/ClassLoaderRule.java

@ -1,113 +0,0 @@ @@ -1,113 +0,0 @@
/*
* 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()));
}
}
}
}

51
src/test/java/org/springframework/data/classloadersupport/HidingClassLoader.java

@ -16,29 +16,60 @@ @@ -16,29 +16,60 @@
package org.springframework.data.classloadersupport;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
/**
* 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>
* 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
* @author Oliver Gierke
*/
public class HidingClassLoader extends ShadowingClassLoader {
private final Collection<String> hidden;
HidingClassLoader(Collection<String> hidden) {
super(URLClassLoader.getSystemClassLoader());
this.hidden = hidden;
}
/**
* Creates a new {@link HidingClassLoader} with the packages of the given classes hidden.
*
* @param packages must not be {@literal null}.
* @return
*/
public static HidingClassLoader hide(Class<?>... packages) {
Assert.notNull(packages, "Packages must not be null!");
List<String> result = new ArrayList<String>(packages.length);
for (Class<?> type : packages) {
result.add(type.getPackage().getName());
}
return new HidingClassLoader(result);
}
/*
* (non-Javadoc)
* @see org.springframework.data.classloadersupport.ShadowingClassLoader#loadClass(java.lang.String)
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
@ -46,6 +77,10 @@ public class HidingClassLoader extends ShadowingClassLoader { @@ -46,6 +77,10 @@ public class HidingClassLoader extends ShadowingClassLoader {
return super.loadClass(name);
}
/*
* (non-Javadoc)
* @see java.lang.ClassLoader#findClass(java.lang.String)
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {

51
src/test/java/org/springframework/data/classloadersupport/ShadowingClassLoader.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-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.
@ -33,34 +33,31 @@ import org.springframework.util.FileCopyUtils; @@ -33,34 +33,31 @@ import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* ClassLoader decorator that shadows an enclosing ClassLoader,
* applying registered transformers to all affected classes.
* Copy of Spring Frameworks {@link org.springframework.instrument.classloading.ShadowingClassLoader} tweaked to not
* register default exclusions. ClassLoader decorator that shadows an enclosing ClassLoader, applying registered
* transformers to all affected classes.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Costin Leau
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
* @see #addTransformer
* @see org.springframework.core.OverridingClassLoader
* @see https://jira.spring.io/browse/SPR-15439
*/
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) {
@ -68,10 +65,9 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -68,10 +65,9 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
this.enclosingClassLoader = enclosingClassLoader;
}
/**
* Add the given ClassFileTransformer to the list of transformers that this
* ClassLoader will apply.
* Add the given ClassFileTransformer to the list of transformers that this ClassLoader will apply.
*
* @param transformer the ClassFileTransformer
*/
public void addTransformer(ClassFileTransformer transformer) {
@ -80,8 +76,9 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -80,8 +76,9 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
}
/**
* Copy all ClassFileTransformers from the given ClassLoader to the list of
* transformers that this ClassLoader will apply.
* 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) {
@ -89,7 +86,6 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -89,7 +86,6 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
this.classFileTransformers.addAll(other.classFileTransformers);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (shouldShadow(name)) {
@ -99,25 +95,25 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -99,25 +95,25 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
return cls;
}
return doLoadClass(name);
}
else {
} 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));
return (!className.equals(getClass().getName()) && !className.endsWith("ShadowingClassLoader")
&& isEligibleForShadowing(className));
}
/**
* Determine whether the specified class is eligible for shadowing
* by this class loader.
* 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
@ -126,7 +122,6 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -126,7 +122,6 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
return isExcluded(className);
}
private Class<?> doLoadClass(String name) throws ClassNotFoundException {
String internalName = StringUtils.replace(name, ".", "/") + ".class";
InputStream is = this.enclosingClassLoader.getResourceAsStream(internalName);
@ -147,8 +142,7 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -147,8 +142,7 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
}
this.classCache.put(name, cls);
return cls;
}
catch (IOException ex) {
} catch (IOException ex) {
throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
}
}
@ -161,13 +155,11 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -161,13 +155,11 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
bytes = (transformed != null ? transformed : bytes);
}
return bytes;
}
catch (IllegalClassFormatException ex) {
} catch (IllegalClassFormatException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public URL getResource(String name) {
return this.enclosingClassLoader.getResource(name);
@ -182,5 +174,4 @@ public class ShadowingClassLoader extends DecoratingClassLoader { @@ -182,5 +174,4 @@ public class ShadowingClassLoader extends DecoratingClassLoader {
public Enumeration<URL> getResources(String name) throws IOException {
return this.enclosingClassLoader.getResources(name);
}
}

22
src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java

@ -25,14 +25,12 @@ import java.util.Arrays; @@ -25,14 +25,12 @@ import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.classloadersupport.ClassLoaderConfiguration;
import org.springframework.data.classloadersupport.ClassLoaderRule;
import org.springframework.data.classloadersupport.HidingClassLoader;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
@ -45,10 +43,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -45,10 +43,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Integration tests for {@link EnableSpringDataWebSupport}.
*
@ -56,9 +55,6 @@ import org.springframework.web.util.UriComponentsBuilder; @@ -56,9 +55,6 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Jens Schauder
* @see DATACMNS-330
*/
@ClassLoaderConfiguration(shadowPackage = { WebMvcConfigurationSupport.class, EnableSpringDataWebSupport.class,
EnableSpringDataWebSupportIntegrationTests.SampleConfig.class })
public class EnableSpringDataWebSupportIntegrationTests {
@Configuration
@ -71,8 +67,6 @@ public class EnableSpringDataWebSupportIntegrationTests { @@ -71,8 +67,6 @@ public class EnableSpringDataWebSupportIntegrationTests {
}
}
@Rule public ClassLoaderRule classLoaderRule = new ClassLoaderRule();
@Test
public void registersBasicBeanDefinitions() throws Exception {
@ -96,10 +90,11 @@ public class EnableSpringDataWebSupportIntegrationTests { @@ -96,10 +90,11 @@ public class EnableSpringDataWebSupportIntegrationTests {
}
@Test
@ClassLoaderConfiguration(hidePackage = Link.class)
public void doesNotRegisterHateoasSpecificComponentsIfHateoasNotPresent() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(classLoaderRule.classLoader, SampleConfig.class);
HidingClassLoader classLoader = HidingClassLoader.hide(Link.class);
ApplicationContext context = WebTestUtils.createApplicationContext(classLoader, SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
@ -122,11 +117,10 @@ public class EnableSpringDataWebSupportIntegrationTests { @@ -122,11 +117,10 @@ public class EnableSpringDataWebSupportIntegrationTests {
/**
* @see DATACMNS-475
*/
@Test
@ClassLoaderConfiguration(hidePackage = com.fasterxml.jackson.databind.ObjectMapper.class)
public void doesNotRegisterJacksonSpecificComponentsIfJacksonNotPresent() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(classLoaderRule.classLoader, SampleConfig.class);
ApplicationContext context = WebTestUtils.createApplicationContext(HidingClassLoader.hide(ObjectMapper.class),
SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());

Loading…
Cancel
Save