Browse Source

Cache-safety check for sibling loaders resolving the same classes

Issue: SPR-16714
pull/1776/merge
Juergen Hoeller 7 years ago
parent
commit
46e3a919fe
  1. 36
      spring-core/src/main/java/org/springframework/util/ClassUtils.java
  2. 52
      spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java

36
spring-core/src/main/java/org/springframework/util/ClassUtils.java

@ -406,24 +406,40 @@ public abstract class ClassUtils {
Assert.notNull(clazz, "Class must not be null"); Assert.notNull(clazz, "Class must not be null");
try { try {
ClassLoader target = clazz.getClassLoader(); ClassLoader target = clazz.getClassLoader();
if (target == null) { // Common cases
if (target == classLoader || target == null) {
return true; return true;
} }
ClassLoader cur = classLoader; if (classLoader == null) {
if (cur == target) { return false;
return true;
} }
while (cur != null) { // Check for match in ancestors -> positive
cur = cur.getParent(); ClassLoader current = classLoader;
if (cur == target) { while (current != null) {
current = current.getParent();
if (current == target) {
return true; return true;
} }
} }
return false; // Check for match in children -> negative
while (target != null) {
target = target.getParent();
if (target == classLoader) {
return false;
}
}
} }
catch (SecurityException ex) { catch (SecurityException ex) {
// Probably from the system ClassLoader - let's consider it safe. // Fall through to Class reference comparison below
return true; }
try {
// Fallback for ClassLoaders without parent/child relationship:
// safe if same Class can be loaded from given ClassLoader
return (clazz == forName(clazz.getName(), classLoader));
}
catch (ClassNotFoundException ex) {
return false;
} }
} }

52
spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java

@ -16,6 +16,7 @@
package org.springframework.util; package org.springframework.util;
import java.io.Externalizable;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -44,20 +45,21 @@ import static org.junit.Assert.*;
* @author Rob Harrop * @author Rob Harrop
* @author Rick Evans * @author Rick Evans
*/ */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClassUtilsTests { public class ClassUtilsTests {
private ClassLoader classLoader = getClass().getClassLoader(); private ClassLoader classLoader = getClass().getClassLoader();
@Before @Before
public void setUp() { public void clearStatics() {
InnerClass.noArgCalled = false; InnerClass.noArgCalled = false;
InnerClass.argCalled = false; InnerClass.argCalled = false;
InnerClass.overloadedCalled = false; InnerClass.overloadedCalled = false;
} }
@Test @Test
public void testIsPresent() throws Exception { public void testIsPresent() {
assertTrue(ClassUtils.isPresent("java.lang.String", classLoader)); assertTrue(ClassUtils.isPresent("java.lang.String", classLoader));
assertFalse(ClassUtils.isPresent("java.lang.MySpecialString", classLoader)); assertFalse(ClassUtils.isPresent("java.lang.MySpecialString", classLoader));
} }
@ -114,6 +116,36 @@ public class ClassUtilsTests {
assertEquals(double[].class, ClassUtils.forName(double[].class.getName(), classLoader)); assertEquals(double[].class, ClassUtils.forName(double[].class.getName(), classLoader));
} }
@Test
public void testIsCacheSafe() {
ClassLoader childLoader1 = new ClassLoader(classLoader) {};
ClassLoader childLoader2 = new ClassLoader(classLoader) {};
ClassLoader childLoader3 = new ClassLoader(classLoader) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return childLoader1.loadClass(name);
}
};
Class<?> composite = ClassUtils.createCompositeInterface(
new Class<?>[] {Serializable.class, Externalizable.class}, childLoader1);
assertTrue(ClassUtils.isCacheSafe(String.class, null));
assertTrue(ClassUtils.isCacheSafe(String.class, classLoader));
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader1));
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader2));
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader3));
assertFalse(ClassUtils.isCacheSafe(InnerClass.class, null));
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, classLoader));
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader1));
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader2));
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader3));
assertFalse(ClassUtils.isCacheSafe(composite, null));
assertFalse(ClassUtils.isCacheSafe(composite, classLoader));
assertTrue(ClassUtils.isCacheSafe(composite, childLoader1));
assertFalse(ClassUtils.isCacheSafe(composite, childLoader2));
assertTrue(ClassUtils.isCacheSafe(composite, childLoader3));
}
@Test @Test
public void testGetShortName() { public void testGetShortName() {
String className = ClassUtils.getShortName(getClass()); String className = ClassUtils.getShortName(getClass());
@ -199,7 +231,7 @@ public class ClassUtilsTests {
} }
@Test @Test
public void testHasMethod() throws Exception { public void testHasMethod() {
assertTrue(ClassUtils.hasMethod(Collection.class, "size")); assertTrue(ClassUtils.hasMethod(Collection.class, "size"));
assertTrue(ClassUtils.hasMethod(Collection.class, "remove", Object.class)); assertTrue(ClassUtils.hasMethod(Collection.class, "remove", Object.class));
assertFalse(ClassUtils.hasMethod(Collection.class, "remove")); assertFalse(ClassUtils.hasMethod(Collection.class, "remove"));
@ -207,7 +239,7 @@ public class ClassUtilsTests {
} }
@Test @Test
public void testGetMethodIfAvailable() throws Exception { public void testGetMethodIfAvailable() {
Method method = ClassUtils.getMethodIfAvailable(Collection.class, "size"); Method method = ClassUtils.getMethodIfAvailable(Collection.class, "size");
assertNotNull(method); assertNotNull(method);
assertEquals("size", method.getName()); assertEquals("size", method.getName());
@ -278,7 +310,7 @@ public class ClassUtilsTests {
@Test @Test
public void testClassPackageAsResourcePath() { public void testClassPackageAsResourcePath() {
String result = ClassUtils.classPackageAsResourcePath(Proxy.class); String result = ClassUtils.classPackageAsResourcePath(Proxy.class);
assertTrue(result.equals("java/lang/reflect")); assertEquals("java/lang/reflect", result);
} }
@Test @Test
@ -294,7 +326,7 @@ public class ClassUtilsTests {
@Test @Test
public void testGetAllInterfaces() { public void testGetAllInterfaces() {
DerivedTestObject testBean = new DerivedTestObject(); DerivedTestObject testBean = new DerivedTestObject();
List ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean)); List<Class<?>> ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean));
assertEquals("Correct number of interfaces", 4, ifcs.size()); assertEquals("Correct number of interfaces", 4, ifcs.size());
assertTrue("Contains Serializable", ifcs.contains(Serializable.class)); assertTrue("Contains Serializable", ifcs.contains(Serializable.class));
assertTrue("Contains ITestBean", ifcs.contains(ITestObject.class)); assertTrue("Contains ITestBean", ifcs.contains(ITestObject.class));
@ -303,13 +335,13 @@ public class ClassUtilsTests {
@Test @Test
public void testClassNamesToString() { public void testClassNamesToString() {
List ifcs = new LinkedList(); List<Class<?>> ifcs = new LinkedList<>();
ifcs.add(Serializable.class); ifcs.add(Serializable.class);
ifcs.add(Runnable.class); ifcs.add(Runnable.class);
assertEquals("[interface java.io.Serializable, interface java.lang.Runnable]", ifcs.toString()); assertEquals("[interface java.io.Serializable, interface java.lang.Runnable]", ifcs.toString());
assertEquals("[java.io.Serializable, java.lang.Runnable]", ClassUtils.classNamesToString(ifcs)); assertEquals("[java.io.Serializable, java.lang.Runnable]", ClassUtils.classNamesToString(ifcs));
List classes = new LinkedList(); List<Class<?>> classes = new LinkedList<>();
classes.add(LinkedList.class); classes.add(LinkedList.class);
classes.add(Integer.class); classes.add(Integer.class);
assertEquals("[class java.util.LinkedList, class java.lang.Integer]", classes.toString()); assertEquals("[class java.util.LinkedList, class java.lang.Integer]", classes.toString());
@ -319,7 +351,7 @@ public class ClassUtilsTests {
assertEquals("[java.util.List]", ClassUtils.classNamesToString(List.class)); assertEquals("[java.util.List]", ClassUtils.classNamesToString(List.class));
assertEquals("[]", Collections.EMPTY_LIST.toString()); assertEquals("[]", Collections.EMPTY_LIST.toString());
assertEquals("[]", ClassUtils.classNamesToString(Collections.EMPTY_LIST)); assertEquals("[]", ClassUtils.classNamesToString(Collections.emptyList()));
} }
@Test @Test

Loading…
Cancel
Save