Introduce public defineClass variant for SmartClassLoader implementations

Closes gh-26403
This commit is contained in:
Juergen Hoeller
2021-01-19 12:03:18 +01:00
parent 3c959d3515
commit 799885fd6d
3 changed files with 76 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -17,6 +17,7 @@
package org.springframework.context.support;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -74,6 +75,11 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
return (clazz.getClassLoader() instanceof ContextOverridingClassLoader);
}
@Override
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
return defineClass(name, b, 0, b.length, protectionDomain);
}
/**
* ClassLoader to be created for each loaded class.

View File

@@ -522,26 +522,46 @@ public class ReflectUtils {
}
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
// Direct defineClass attempt on the target Classloader
if (c == null) {
if (protectionDomain == null) {
protectionDomain = PROTECTION_DOMAIN;
}
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
// Look for publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain)
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
Method publicDefineClass = loader.getClass().getMethod(
"publicDefineClass", String.class, byte[].class, ProtectionDomain.class);
c = (Class) publicDefineClass.invoke(loader, className, b, protectionDomain);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException(ex.getTargetException());
if (!(ex.getTargetException() instanceof UnsupportedOperationException)) {
throw new CodeGenerationException(ex.getTargetException());
}
// in case of UnsupportedOperationException, fall through
}
catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
// publicDefineClass method not available -> fall through
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException(ex.getTargetException());
}
catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2021 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.
@@ -16,6 +16,10 @@
package org.springframework.core;
import java.security.ProtectionDomain;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by a reloading-aware ClassLoader
* (e.g. a Groovy-based ClassLoader). Detected for example by
@@ -34,10 +38,41 @@ public interface SmartClassLoader {
* Determine whether the given class is reloadable (in this ClassLoader).
* <p>Typically used to check whether the result may be cached (for this
* ClassLoader) or whether it should be reobtained every time.
* The default implementation always returns {@code false}.
* @param clazz the class to check (usually loaded from this ClassLoader)
* @return whether the class should be expected to appear in a reloaded
* version (with a different {@code Class} object) later on
*/
boolean isClassReloadable(Class<?> clazz);
default boolean isClassReloadable(Class<?> clazz) {
return false;
}
/**
* Define a custom class (typically a CGLIB proxy class) in this class loader.
* <p>This is a public equivalent of the protected
* {@code defineClass(String, byte[], int, int, ProtectionDomain)} method
* in {@link ClassLoader} which is traditionally invoked via reflection.
* A concrete implementation in a custom class loader should simply delegate
* to that protected method in order to make classloader-specific definitions
* publicly available without "illegal access" warnings on JDK 9+:
* {@code return defineClass(name, b, 0, b.length, protectionDomain)}.
* Note that the JDK 9+ {@code Lookup#defineClass} method does not support
* a custom target class loader for the new definition; it rather always
* defines the class in the same class loader as the lookup's context class.
* @param name the name of the class
* @param b the bytes defining the class
* @param protectionDomain the protection domain for the class, if any
* @return the newly created class
* @throws LinkageError in case of a bad class definition
* @throws SecurityException in case of an invalid definition attempt
* @throws UnsupportedOperationException in case of a custom definition attempt
* not being possible (thrown by the default implementation in this interface)
* @since 5.3.4
* @see ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)
* @see java.lang.invoke.MethodHandles.Lookup#defineClass(byte[])
*/
default Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
throw new UnsupportedOperationException();
}
}