From 0c2e71cd08285c33a19eabfe10e48b0f0bc0398e Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Wed, 20 Mar 2019 12:28:38 -0700 Subject: [PATCH] Prevent early initialization of factory beans in text context customizers Until Spring Framework 5.1.15, a FactoryBean with a non-default constructor defined via component scanning would cause an error. This behavior has changed as of https://github.com/spring-projects/spring-framework/issues/22409. Regardless of this change we want to ensure that we avoid triggering eager initialisation. `SimpleFactoryBean` has been written this way so that the tests fail if early initialization is triggered regardless of the Spring Framework version. Fixes gh-15898 --- .../TestRestTemplateContextCustomizer.java | 4 +- .../WebTestClientContextCustomizer.java | 4 +- ...ContextCustomizerWithFactoryBeanTests.java | 61 +++++++++++++++++++ .../web/client/scan/SimpleFactoryBean.java | 51 ++++++++++++++++ 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerWithFactoryBeanTests.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/scan/SimpleFactoryBean.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java index 9066ea64a3..956d875f89 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java @@ -111,8 +111,8 @@ class TestRestTemplateContextCustomizer implements ContextCustomizer { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - (ListableBeanFactory) this.beanFactory, - TestRestTemplate.class).length == 0) { + (ListableBeanFactory) this.beanFactory, TestRestTemplate.class, false, + false).length == 0) { registry.registerBeanDefinition(TestRestTemplate.class.getName(), new RootBeanDefinition(TestRestTemplateFactory.class)); } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java index 3b8f67987d..2f30705012 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java @@ -111,8 +111,8 @@ class WebTestClientContextCustomizer implements ContextCustomizer { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - (ListableBeanFactory) this.beanFactory, - WebTestClient.class).length == 0) { + (ListableBeanFactory) this.beanFactory, WebTestClient.class, false, + false).length == 0) { registry.registerBeanDefinition(WebTestClient.class.getName(), new RootBeanDefinition(WebTestClientFactory.class)); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerWithFactoryBeanTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerWithFactoryBeanTests.java new file mode 100644 index 0000000000..5d93617c47 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerWithFactoryBeanTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2019 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.web.client; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link TestRestTemplateContextCustomizer} to ensure + * early-initialization of factory beans doesn't occur. + * + * @author Madhura Bhave + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestRestTemplateContextCustomizerWithFactoryBeanTests.TestClassWithFactoryBean.class, webEnvironment = WebEnvironment.RANDOM_PORT) +@DirtiesContext +public class TestRestTemplateContextCustomizerWithFactoryBeanTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void test() { + } + + @Configuration + @ComponentScan("org.springframework.boot.test.web.client.scan") + static class TestClassWithFactoryBean { + + @Bean + public TomcatServletWebServerFactory webServerFactory() { + return new TomcatServletWebServerFactory(0); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/scan/SimpleFactoryBean.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/scan/SimpleFactoryBean.java new file mode 100644 index 0000000000..d455c980bd --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/scan/SimpleFactoryBean.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2019 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.boot.test.web.client.scan; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * @author Madhura Bhave + */ +@Component +public class SimpleFactoryBean implements FactoryBean { + + private static boolean isInitializedEarly = false; + + public SimpleFactoryBean() { + isInitializedEarly = true; + throw new RuntimeException(); + } + + @Autowired + public SimpleFactoryBean(ApplicationContext context) { + if (isInitializedEarly) { + throw new RuntimeException(); + } + } + + public Object getObject() { + return new Object(); + } + + public Class getObjectType() { + return Object.class; + } + +}