From f53da047175e21cb7619475b885ba1b4967e8d05 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 11 Feb 2025 22:10:02 +0100 Subject: [PATCH 1/2] Align with SmartClassLoader handling for AOP proxy classes Closes gh-34274 --- .../ConfigurationClassEnhancer.java | 27 +++- .../ConfigurationClassEnhancerTests.java | 153 +++++++++++++++++- 2 files changed, 169 insertions(+), 11 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 25631c7e08b..2895cd86b86 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -109,8 +109,16 @@ class ConfigurationClassEnhancer { } return configClass; } + try { - Class enhancedClass = createClass(newEnhancer(configClass, classLoader)); + // Use original ClassLoader if config class not locally loaded in overriding class loader + if (classLoader instanceof SmartClassLoader smartClassLoader && + classLoader != configClass.getClassLoader()) { + classLoader = smartClassLoader.getOriginalClassLoader(); + } + Enhancer enhancer = newEnhancer(configClass, classLoader); + boolean classLoaderMismatch = (classLoader != null && classLoader != configClass.getClassLoader()); + Class enhancedClass = createClass(enhancer, classLoaderMismatch); if (logger.isTraceEnabled()) { logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); @@ -155,8 +163,21 @@ class ConfigurationClassEnhancer { * Uses enhancer to generate a subclass of superclass, * ensuring that callbacks are registered for the new subclass. */ - private Class createClass(Enhancer enhancer) { - Class subclass = enhancer.createClass(); + private Class createClass(Enhancer enhancer, boolean fallback) { + Class subclass; + try { + subclass = enhancer.createClass(); + } + catch (CodeGenerationException ex) { + if (!fallback) { + throw ex; + } + // Possibly a package-visible @Bean method declaration not accessible + // in the given ClassLoader -> retry with original ClassLoader + enhancer.setClassLoader(null); + subclass = enhancer.createClass(); + } + // Registering callbacks statically (as opposed to thread-local) // is critical for usage in an OSGi environment (SPR-5932)... Enhancer.registerStaticCallbacks(subclass, CALLBACKS); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java index 96051f16172..052f27f43f2 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -18,11 +18,16 @@ package org.springframework.context.annotation; import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.ProtectionDomain; import java.security.SecureClassLoader; import org.junit.jupiter.api.Test; +import org.springframework.core.OverridingClassLoader; import org.springframework.core.SmartClassLoader; +import org.springframework.lang.Nullable; import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -36,19 +41,108 @@ class ConfigurationClassEnhancerTests { @Test void enhanceReloadedClass() throws Exception { ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); + ClassLoader parentClassLoader = getClass().getClassLoader(); - CustomClassLoader classLoader = new CustomClassLoader(parentClassLoader); + ClassLoader classLoader = new CustomSmartClassLoader(parentClassLoader); Class myClass = parentClassLoader.loadClass(MyConfig.class.getName()); - configurationClassEnhancer.enhance(myClass, parentClassLoader); - Class myReloadedClass = classLoader.loadClass(MyConfig.class.getName()); - Class enhancedReloadedClass = configurationClassEnhancer.enhance(myReloadedClass, classLoader); - assertThat(enhancedReloadedClass.getClassLoader()).isEqualTo(classLoader); + Class enhancedClass = configurationClassEnhancer.enhance(myClass, parentClassLoader); + assertThat(myClass).isAssignableFrom(enhancedClass); + + myClass = classLoader.loadClass(MyConfig.class.getName()); + enhancedClass = configurationClassEnhancer.enhance(myClass, classLoader); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader); + assertThat(myClass).isAssignableFrom(enhancedClass); + } + + @Test + void withPublicClass() { + ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); + + ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + Class enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader); + assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader); + + classLoader = new OverridingClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader); + assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new CustomSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader); + assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new BasicSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader); + assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + } + + @Test + void withNonPublicClass() { + ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); + + ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + Class enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader); + assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new OverridingClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader); + assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new CustomSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader); + assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new BasicSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader); + assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + } + + @Test + void withNonPublicMethod() { + ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); + + ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + Class enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader); + assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader); + + classLoader = new OverridingClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader); + assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new CustomSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader); + assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + + classLoader = new BasicSmartClassLoader(getClass().getClassLoader()); + enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader); + assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); } @Configuration static class MyConfig { + @Bean + String myBean() { + return "bean"; + } + } + + + @Configuration + public static class MyConfigWithPublicClass { + @Bean public String myBean() { return "bean"; @@ -56,9 +150,29 @@ class ConfigurationClassEnhancerTests { } - static class CustomClassLoader extends SecureClassLoader implements SmartClassLoader { + @Configuration + static class MyConfigWithNonPublicClass { - CustomClassLoader(ClassLoader parent) { + @Bean + public String myBean() { + return "bean"; + } + } + + + @Configuration + public static class MyConfigWithNonPublicMethod { + + @Bean + String myBean() { + return "bean"; + } + } + + + static class CustomSmartClassLoader extends SecureClassLoader implements SmartClassLoader { + + CustomSmartClassLoader(ClassLoader parent) { super(parent); } @@ -82,6 +196,29 @@ class ConfigurationClassEnhancerTests { public boolean isClassReloadable(Class clazz) { return clazz.getName().contains("MyConfig"); } + + @Override + public ClassLoader getOriginalClassLoader() { + return getParent(); + } + + @Override + public Class publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) { + return defineClass(name, b, 0, b.length, protectionDomain); + } + } + + + static class BasicSmartClassLoader extends SecureClassLoader implements SmartClassLoader { + + BasicSmartClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + public Class publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) { + return defineClass(name, b, 0, b.length, protectionDomain); + } } } From 2df90e32c0c1dfb7edf0d439ba8643e620c3f12e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 11 Feb 2025 22:10:15 +0100 Subject: [PATCH 2/2] Upgrade to Netty 4.1.118 and Checkstyle 10.21.2 --- .../java/org/springframework/build/CheckstyleConventions.java | 2 +- framework-platform/framework-platform.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java index 5e0d2249b90..f955f18ce6f 100644 --- a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java @@ -50,7 +50,7 @@ public class CheckstyleConventions { project.getPlugins().apply(CheckstylePlugin.class); project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g")); CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class); - checkstyle.setToolVersion("10.21.1"); + checkstyle.setToolVersion("10.21.2"); checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle")); String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies(); diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index c20b66ed22c..9d88ee463e9 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -9,7 +9,7 @@ javaPlatform { dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.18.2")) api(platform("io.micrometer:micrometer-bom:1.14.4")) - api(platform("io.netty:netty-bom:4.1.117.Final")) + api(platform("io.netty:netty-bom:4.1.118.Final")) api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) api(platform("io.projectreactor:reactor-bom:2024.0.3")) api(platform("io.rsocket:rsocket-bom:1.1.5"))