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 add9bcd7f6f..52a1479f725 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 @@ -1656,7 +1656,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); } } @@ -1736,7 +1741,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass()); } return result; - } private @Nullable Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, 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 7a8a43e4eac..6b51e6dd4a5 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; } 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 05b303e455e..beb6fcd7dec 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 4f2b5cf0596..ff801346c76 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 {