Skip to content

Commit 799885f

Browse files
committed
Introduce public defineClass variant for SmartClassLoader implementations
Closes gh-26403
1 parent 3c959d3 commit 799885f

File tree

3 files changed

+76
-15
lines changed

3 files changed

+76
-15
lines changed

spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.context.support;
1818

1919
import java.lang.reflect.Method;
20+
import java.security.ProtectionDomain;
2021
import java.util.Map;
2122
import java.util.concurrent.ConcurrentHashMap;
2223

@@ -74,6 +75,11 @@ public boolean isClassReloadable(Class<?> clazz) {
7475
return (clazz.getClassLoader() instanceof ContextOverridingClassLoader);
7576
}
7677

78+
@Override
79+
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
80+
return defineClass(name, b, 0, b.length, protectionDomain);
81+
}
82+
7783

7884
/**
7985
* ClassLoader to be created for each loaded class.

spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java

+32-12
Original file line numberDiff line numberDiff line change
@@ -522,26 +522,46 @@ public static Class defineClass(String className, byte[] b, ClassLoader loader,
522522
}
523523
}
524524

525-
// Classic option: protected ClassLoader.defineClass method
526-
if (c == null && classLoaderDefineClassMethod != null) {
525+
// Direct defineClass attempt on the target Classloader
526+
if (c == null) {
527527
if (protectionDomain == null) {
528528
protectionDomain = PROTECTION_DOMAIN;
529529
}
530-
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
530+
531+
// Look for publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain)
531532
try {
532-
if (!classLoaderDefineClassMethod.isAccessible()) {
533-
classLoaderDefineClassMethod.setAccessible(true);
534-
}
535-
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
533+
Method publicDefineClass = loader.getClass().getMethod(
534+
"publicDefineClass", String.class, byte[].class, ProtectionDomain.class);
535+
c = (Class) publicDefineClass.invoke(loader, className, b, protectionDomain);
536536
}
537537
catch (InvocationTargetException ex) {
538-
throw new CodeGenerationException(ex.getTargetException());
538+
if (!(ex.getTargetException() instanceof UnsupportedOperationException)) {
539+
throw new CodeGenerationException(ex.getTargetException());
540+
}
541+
// in case of UnsupportedOperationException, fall through
539542
}
540543
catch (Throwable ex) {
541-
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
542-
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
543-
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
544-
throw new CodeGenerationException(ex);
544+
// publicDefineClass method not available -> fall through
545+
}
546+
547+
// Classic option: protected ClassLoader.defineClass method
548+
if (c == null && classLoaderDefineClassMethod != null) {
549+
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
550+
try {
551+
if (!classLoaderDefineClassMethod.isAccessible()) {
552+
classLoaderDefineClassMethod.setAccessible(true);
553+
}
554+
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
555+
}
556+
catch (InvocationTargetException ex) {
557+
throw new CodeGenerationException(ex.getTargetException());
558+
}
559+
catch (Throwable ex) {
560+
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
561+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
562+
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
563+
throw new CodeGenerationException(ex);
564+
}
545565
}
546566
}
547567
}

spring-core/src/main/java/org/springframework/core/SmartClassLoader.java

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.core;
1818

19+
import java.security.ProtectionDomain;
20+
21+
import org.springframework.lang.Nullable;
22+
1923
/**
2024
* Interface to be implemented by a reloading-aware ClassLoader
2125
* (e.g. a Groovy-based ClassLoader). Detected for example by
@@ -34,10 +38,41 @@ public interface SmartClassLoader {
3438
* Determine whether the given class is reloadable (in this ClassLoader).
3539
* <p>Typically used to check whether the result may be cached (for this
3640
* ClassLoader) or whether it should be reobtained every time.
41+
* The default implementation always returns {@code false}.
3742
* @param clazz the class to check (usually loaded from this ClassLoader)
3843
* @return whether the class should be expected to appear in a reloaded
3944
* version (with a different {@code Class} object) later on
4045
*/
41-
boolean isClassReloadable(Class<?> clazz);
46+
default boolean isClassReloadable(Class<?> clazz) {
47+
return false;
48+
}
49+
50+
/**
51+
* Define a custom class (typically a CGLIB proxy class) in this class loader.
52+
* <p>This is a public equivalent of the protected
53+
* {@code defineClass(String, byte[], int, int, ProtectionDomain)} method
54+
* in {@link ClassLoader} which is traditionally invoked via reflection.
55+
* A concrete implementation in a custom class loader should simply delegate
56+
* to that protected method in order to make classloader-specific definitions
57+
* publicly available without "illegal access" warnings on JDK 9+:
58+
* {@code return defineClass(name, b, 0, b.length, protectionDomain)}.
59+
* Note that the JDK 9+ {@code Lookup#defineClass} method does not support
60+
* a custom target class loader for the new definition; it rather always
61+
* defines the class in the same class loader as the lookup's context class.
62+
* @param name the name of the class
63+
* @param b the bytes defining the class
64+
* @param protectionDomain the protection domain for the class, if any
65+
* @return the newly created class
66+
* @throws LinkageError in case of a bad class definition
67+
* @throws SecurityException in case of an invalid definition attempt
68+
* @throws UnsupportedOperationException in case of a custom definition attempt
69+
* not being possible (thrown by the default implementation in this interface)
70+
* @since 5.3.4
71+
* @see ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)
72+
* @see java.lang.invoke.MethodHandles.Lookup#defineClass(byte[])
73+
*/
74+
default Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
75+
throw new UnsupportedOperationException();
76+
}
4277

4378
}

0 commit comments

Comments
 (0)