From 15d1455acb919ee7c474f9376a32b9213106e9af Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 27 May 2025 09:47:29 +0200 Subject: [PATCH 1/2] Check for package-visible constructor in case of ClassLoader mismatch Closes gh-34950 --- .../ConfigurationClassEnhancer.java | 11 +++++- .../ConfigurationClassEnhancerTests.java | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 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 9db1a1253a2..98645c369b2 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 @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -139,14 +140,20 @@ class ConfigurationClassEnhancer { } /** - * Checks whether the given config class relies on package visibility, - * either for the class itself or for any of its {@code @Bean} methods. + * Checks whether the given config class relies on package visibility, either for + * the class and any of its constructors or for any of its {@code @Bean} methods. */ private boolean reliesOnPackageVisibility(Class configSuperClass) { int mod = configSuperClass.getModifiers(); if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) { 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)) { if (BeanAnnotationHelper.isBeanAnnotated(method)) { mod = method.getModifiers(); 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 2dc8ba872a3..38779f588cc 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 @@ -104,6 +104,31 @@ class ConfigurationClassEnhancerTests { 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 void withNonPublicMethod() { ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); @@ -160,6 +185,19 @@ class ConfigurationClassEnhancerTests { } + @Configuration + public static class MyConfigWithNonPublicConstructor { + + MyConfigWithNonPublicConstructor() { + } + + @Bean + public String myBean() { + return "bean"; + } + } + + @Configuration public static class MyConfigWithNonPublicMethod { From d9e261aecd8c70859d5a2c7d993384b4c6846d16 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 27 May 2025 09:47:39 +0200 Subject: [PATCH 2/2] Enforce backwards compatibility for null bean in shortcut code path Closes gh-34929 --- .../support/DefaultListableBeanFactory.java | 6 ++++- ...wiredAnnotationBeanPostProcessorTests.java | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 29271d5b011..4aec20e2d47 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1679,7 +1679,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (autowiredBeanNames != null) { autowiredBeanNames.add(dependencyName); } + boolean preExisting = containsSingleton(dependencyName); Object dependencyBean = getBean(dependencyName); + if (preExisting && dependencyBean instanceof NullBean) { + // for backwards compatibility with addCandidateEntry in the regular code path + dependencyBean = null; + } return resolveInstance(dependencyBean, descriptor, type, dependencyName); } } @@ -1760,7 +1765,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass()); } return result; - } @Nullable diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index fea47ae12c2..dd7d68b1900 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -960,11 +960,34 @@ class AutowiredAnnotationBeanPostProcessorTests { @Test void constructorResourceInjectionWithNoCandidatesAndNoFallback() { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class)); + assertThatExceptionOfType(UnsatisfiedDependencyException.class) .isThrownBy(() -> bf.getBean("annotatedBean")) .satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class)); } + @Test + void constructorResourceInjectionWithCandidateAndNoFallback() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class)); + RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class); + tb.setFactoryMethodName("createTestBean"); + bf.registerBeanDefinition("testBean", tb); + + bf.getBean("testBean"); + assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull(); + } + + @Test + void constructorResourceInjectionWithNameMatchingCandidateAndNoFallback() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class)); + RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class); + tb.setFactoryMethodName("createTestBean"); + bf.registerBeanDefinition("testBean3", tb); + + bf.getBean("testBean3"); + assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull(); + } + @Test void constructorResourceInjectionWithSometimesNullBeanEarly() { RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class); @@ -1193,6 +1216,7 @@ class AutowiredAnnotationBeanPostProcessorTests { @Test void singleConstructorInjectionWithMissingDependency() { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class)); + assertThatExceptionOfType(UnsatisfiedDependencyException.class) .isThrownBy(() -> bf.getBean("annotatedBean")); } @@ -1203,6 +1227,7 @@ class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class); tb.setFactoryMethodName("createTestBean"); bf.registerBeanDefinition("testBean", tb); + assertThatExceptionOfType(UnsatisfiedDependencyException.class) .isThrownBy(() -> bf.getBean("annotatedBean")); } @@ -3060,7 +3085,6 @@ class AutowiredAnnotationBeanPostProcessorTests { protected ITestBean testBean3; - @Autowired(required = false) public ConstructorWithoutFallbackBean(ITestBean testBean3) { this.testBean3 = testBean3; } @@ -3075,7 +3099,6 @@ class AutowiredAnnotationBeanPostProcessorTests { protected ITestBean testBean3; - @Autowired(required = false) public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) { this.testBean3 = testBean3; }