Check for package-visible constructor in case of ClassLoader mismatch

Closes gh-34950
This commit is contained in:
Juergen Hoeller 2025-05-27 09:47:29 +02:00
parent 53844b068c
commit 15d1455acb
2 changed files with 47 additions and 2 deletions

View File

@ -16,6 +16,7 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -139,14 +140,20 @@ class ConfigurationClassEnhancer {
} }
/** /**
* Checks whether the given config class relies on package visibility, * Checks whether the given config class relies on package visibility, either for
* either for the class itself or for any of its {@code @Bean} methods. * the class and any of its constructors or for any of its {@code @Bean} methods.
*/ */
private boolean reliesOnPackageVisibility(Class<?> configSuperClass) { private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
int mod = configSuperClass.getModifiers(); int mod = configSuperClass.getModifiers();
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) { if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
return true; return true;
} }
for (Constructor<?> ctor : configSuperClass.getDeclaredConstructors()) {
mod = ctor.getModifiers();
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
return true;
}
}
for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) { for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
if (BeanAnnotationHelper.isBeanAnnotated(method)) { if (BeanAnnotationHelper.isBeanAnnotated(method)) {
mod = method.getModifiers(); mod = method.getModifiers();

View File

@ -104,6 +104,31 @@ class ConfigurationClassEnhancerTests {
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
} }
@Test
void withNonPublicConstructor() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new OverridingClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Test @Test
void withNonPublicMethod() { void withNonPublicMethod() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
@ -160,6 +185,19 @@ class ConfigurationClassEnhancerTests {
} }
@Configuration
public static class MyConfigWithNonPublicConstructor {
MyConfigWithNonPublicConstructor() {
}
@Bean
public String myBean() {
return "bean";
}
}
@Configuration @Configuration
public static class MyConfigWithNonPublicMethod { public static class MyConfigWithNonPublicMethod {