Cache-safety check for sibling loaders resolving the same classes
Issue: SPR-16714
This commit is contained in:
parent
1fa5f03635
commit
46e3a919fe
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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…
Reference in New Issue