diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 9297635fc7c..97e77f376e7 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.21.2")) api(platform("io.micrometer:micrometer-bom:1.16.5")) - api(platform("io.netty:netty-bom:4.2.13.Final")) + api(platform("io.netty:netty-bom:4.2.14.Final")) api(platform("io.projectreactor:reactor-bom:2025.0.5")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:5.0.6")) @@ -120,7 +120,7 @@ dependencies { api("org.glassfish:jakarta.el:4.0.2") api("org.graalvm.sdk:graal-sdk:22.3.1") api("org.hamcrest:hamcrest:3.0") - api("org.hibernate.orm:hibernate-core:7.3.4.Final") + api("org.hibernate.orm:hibernate-core:7.3.6.Final") api("org.hibernate.validator:hibernate-validator:9.1.0.Final") api("org.hsqldb:hsqldb:2.7.4") api("org.htmlunit:htmlunit:4.21.0") 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 bc2e9f718ac..a2a57bcb819 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 @@ -1154,12 +1154,19 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (mbd.isBackgroundInit()) { Executor executor = getBootstrapExecutor(); if (executor != null) { + // Force initialization of depends-on beans in mainline thread. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { getBean(dep); } } + // Force initialization of factory reference in mainline thread. + String factoryBeanName = mbd.getFactoryBeanName(); + if (factoryBeanName != null) { + getBean(factoryBeanName); + } + // Instantiate current bean in background thread. CompletableFuture future = CompletableFuture.runAsync( () -> instantiateSingletonInBackgroundThread(beanName), executor); addSingletonFactory(beanName, () -> { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 782bbe8ab99..d2af1f31a48 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -260,9 +260,11 @@ class ConfigurationClassParser { return; } else if (configClass.isScanned()) { - String beanName = configClass.getBeanName(); - if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { - this.registry.removeBeanDefinition(beanName); + if (existingClass.isImported()) { + String beanName = configClass.getBeanName(); + if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { + this.registry.removeBeanDefinition(beanName); + } } // An implicitly scanned bean definition should not override an explicit import. return; diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java index 91a9e726fd7..85ff05b6f43 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java @@ -197,6 +197,16 @@ class BackgroundBootstrapTests { } } + @Test + @Timeout(10) + @EnabledForTestGroups(LONG_RUNNING) + void bootstrapWithCustomExecutorAndLazyConfig() { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorLazyBeanConfig.class); + assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isTrue(); + assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isTrue(); + ctx.close(); + } + @Configuration(proxyBeanMethods = false) static class UnmanagedThreadBeanConfig { @@ -542,4 +552,36 @@ class BackgroundBootstrapTests { } } + + @Configuration(proxyBeanMethods = false) + static class CustomExecutorLazyBeanConfig { + + @Bean + public ThreadPoolTaskExecutor bootstrapExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("Custom-"); + executor.setCorePoolSize(2); + executor.initialize(); + return executor; + } + + @Configuration(proxyBeanMethods = false) + @Lazy + static class LazyBeanConfig { + + @Bean(bootstrap = BACKGROUND) + public TestBean testBean1() throws InterruptedException { + Thread.sleep(6000); + return new TestBean(); + } + + @Bean(bootstrap = BACKGROUND) + @Lazy + public TestBean testBean2() throws InterruptedException { + Thread.sleep(6000); + return new TestBean(); + } + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java index 133b44d5716..e8f189fdcf4 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java @@ -26,6 +26,7 @@ import example.scannable.CustomComponent; import example.scannable.CustomStereotype; import example.scannable.DefaultNamedComponent; import example.scannable.FooService; +import example.scannable.FooServiceImpl; import example.scannable.MessageBean; import example.scannable.ScopedProxyTestBean; import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImplicitBasePackage; @@ -43,6 +44,7 @@ import org.springframework.beans.factory.annotation.CustomAutowireConfigurer; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; @@ -84,6 +86,17 @@ class ComponentScanAnnotationIntegrationTests { assertContextContainsBean(ctx, "fooServiceImpl"); } + @Test + void controlScanWithExplicitRegistration() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.registerBeanDefinition("myFooService", new RootBeanDefinition(FooServiceImpl.class)); + ctx.scan(example.scannable.PackageMarker.class.getPackage().getName()); + ctx.refresh(); + + assertContextContainsBean(ctx, "myFooService"); + assertContextContainsBean(ctx, "fooServiceImpl"); + } + @Test void viaContextRegistration() { ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig.class); diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index c55b9805180..b730f042d5b 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -117,8 +117,8 @@ public class MethodParameter { * return type; 0 for the first method parameter; 1 for the second method * parameter, etc. * @param nestingLevel the nesting level of the target type - * (typically 1; for example, in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) + * (1 for the top-level type; in case of a List of Lists, 2 would indicate + * the nested List, whereas 3 would indicate the element of the nested List) */ public MethodParameter(Method method, int parameterIndex, int nestingLevel) { Assert.notNull(method, "Method must not be null"); @@ -142,8 +142,8 @@ public class MethodParameter { * @param constructor the Constructor to specify a parameter for * @param parameterIndex the index of the parameter * @param nestingLevel the nesting level of the target type - * (typically 1; for example, in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List) + * (1 for the top-level type; in case of a List of Lists, 2 would indicate + * the nested List, whereas 3 would indicate the element of the nested List) */ public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel) { Assert.notNull(constructor, "Constructor must not be null"); @@ -292,8 +292,8 @@ public class MethodParameter { /** * Return the nesting level of the target type - * (typically 1; for example, in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List). + * (1 for the top-level type; in case of a List of Lists, 2 would indicate + * the nested List, whereas 3 would indicate the element of the nested List). */ public int getNestingLevel() { return this.nestingLevel;