diff --git a/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java b/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java index 30e44b57e1..1f17f45719 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java +++ b/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java @@ -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. diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java index a8f4cb5da7..4420c50c9e 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -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); + } } } } diff --git a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java index 21948da57c..fe7d363709 100644 --- a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java +++ b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java @@ -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). *
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. + *
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(); + } }