From 7faa606920db354b65f749f2acfb442fb1cca102 Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Mon, 21 Oct 2019 14:59:21 +0300 Subject: [PATCH 1/2] Restore proxying of @Bean methods in @TestConfiguration See gh-18675 --- .../boot/test/context/TestConfiguration.java | 26 +++++++++ .../test/context/TestConfigurationTests.java | 55 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java index afadef9401c..8361b443145 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java @@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AliasFor; @@ -51,4 +52,29 @@ public @interface TestConfiguration { @AliasFor(annotation = Configuration.class) String value() default ""; + /** + * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce + * bean lifecycle behavior, e.g. to return shared singleton bean instances even in + * case of direct {@code @Bean} method calls in user code. This feature requires + * method interception, implemented through a runtime-generated CGLIB subclass which + * comes with limitations such as the configuration class and its methods not being + * allowed to declare {@code final}. + *

+ * The default is {@code true}, allowing for 'inter-bean references' within the + * configuration class as well as for external calls to this configuration's + * {@code @Bean} methods, e.g. from another configuration class. If this is not needed + * since each of this particular configuration's {@code @Bean} methods is + * self-contained and designed as a plain factory method for container use, switch + * this flag to {@code false} in order to avoid CGLIB subclass processing. + *

+ * Turning off bean method interception effectively processes {@code @Bean} methods + * individually like when declared on non-{@code @Configuration} classes, a.k.a. + * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally + * equivalent to removing the {@code @Configuration} stereotype. + * @since 2.2.1 + * @return whether to proxy {@code @Bean} methods + */ + @AliasFor(annotation = Configuration.class) + boolean proxyBeanMethods() default true; + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java new file mode 100644 index 00000000000..dade7f74ba6 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * 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.context; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * Tests for {@link TestConfiguration}. + * + * @author Dmytro Nosan + */ +class TestConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void shouldProxyBeanMethods() { + this.contextRunner.withUserConfiguration(ProxyBeanMethodsConfiguration.class) + .run((context) -> Assertions.assertThat(context).hasFailed()); + } + + @Test + void shouldNotProxyBeanMethods() { + this.contextRunner.withUserConfiguration(ProxyBeanMethodsDisableConfiguration.class) + .run((context) -> Assertions.assertThat(context).hasNotFailed()); + } + + @TestConfiguration + final static class ProxyBeanMethodsConfiguration { + + } + + @TestConfiguration(proxyBeanMethods = false) + final static class ProxyBeanMethodsDisableConfiguration { + + } + +} From fd94608f73a7e4693b9e8af2a68d0897ea574ba5 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 22 Oct 2019 09:10:32 +0200 Subject: [PATCH 2/2] Polish "Restore proxying of @Bean methods in @TestConfiguration" See gh-18675 --- .../main/asciidoc/spring-boot-features.adoc | 2 +- .../AdvancedConfigurationExample.java | 2 +- .../AdvancedConfigurationExample.java | 2 +- .../docs/web/client/SampleWebClientTests.java | 2 +- .../test/autoconfigure/ExampleTestConfig.java | 2 +- ...atabaseWithNoDatabaseIntegrationTests.java | 2 +- ...AdvancedConfigurationIntegrationTests.java | 2 +- ...AdvancedConfigurationIntegrationTests.java | 2 +- ...AdvancedConfigurationIntegrationTests.java | 2 +- ...rRegistrationDisabledIntegrationTests.java | 2 +- .../boot/test/context/TestConfiguration.java | 4 +-- .../test/context/TestConfigurationTests.java | 31 ++++++++++--------- ...stContextBootstrapperIntegrationTests.java | 2 +- 13 files changed, 30 insertions(+), 27 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 5a42d993f32..944587faacd 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -6840,7 +6840,7 @@ The following example shows a `RestDocumentationResultHandler` being defined: [source,java,indent=0] ---- - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class ResultHandlerConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/restassured/AdvancedConfigurationExample.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/restassured/AdvancedConfigurationExample.java index cc89f3a3d5e..1e3247b893f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/restassured/AdvancedConfigurationExample.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/restassured/AdvancedConfigurationExample.java @@ -24,7 +24,7 @@ import org.springframework.restdocs.templates.TemplateFormats; public class AdvancedConfigurationExample { // tag::configuration[] - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) public static class CustomizationConfiguration implements RestDocsRestAssuredConfigurationCustomizer { @Override diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/webclient/AdvancedConfigurationExample.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/webclient/AdvancedConfigurationExample.java index 3491726cb27..a5b574a59dc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/webclient/AdvancedConfigurationExample.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/test/autoconfigure/restdocs/webclient/AdvancedConfigurationExample.java @@ -23,7 +23,7 @@ import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation public class AdvancedConfigurationExample { // tag::configuration[] - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) public static class CustomizationConfiguration implements RestDocsWebTestClientConfigurationCustomizer { @Override diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/web/client/SampleWebClientTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/web/client/SampleWebClientTests.java index f6c39e38851..117b892a3bd 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/web/client/SampleWebClientTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/web/client/SampleWebClientTests.java @@ -49,7 +49,7 @@ class SampleWebClientTests { assertThat(headers.getLocation()).hasHost("other.example.com"); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class Config { @Bean diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ExampleTestConfig.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ExampleTestConfig.java index afac195f504..59e086bf7e4 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ExampleTestConfig.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ExampleTestConfig.java @@ -25,7 +25,7 @@ import org.springframework.boot.test.context.TestConfiguration; * * @author Phillip Webb */ -@TestConfiguration +@TestConfiguration(proxyBeanMethods = false) @EntityScan("some.other.package") public class ExampleTestConfig { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests.java index 00815827375..6e2e2d932de 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests.java @@ -48,7 +48,7 @@ class AutoConfigureTestDatabaseWithNoDatabaseIntegrationTests { assertThat(this.context.getBeanNamesForType(DataSource.class)).isNotEmpty(); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class Config { } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java index d36b7cf38f4..40d41b3c611 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java @@ -78,7 +78,7 @@ class MockMvcRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests { assertThat(new File(defaultSnippetsDir, "response-fields.md")).isFile(); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class CustomizationConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestAssuredRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestAssuredRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java index 3fb4d3b5be1..0ef4cd8cfc6 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestAssuredRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestAssuredRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java @@ -82,7 +82,7 @@ class RestAssuredRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests assertThat(new File(defaultSnippetsDir, "response-fields.md")).isFile(); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class CustomizationConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java index 8dac2c1ef2e..eb870231310 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java @@ -72,7 +72,7 @@ class WebTestClientRestDocsAutoConfigurationAdvancedConfigurationIntegrationTest assertThat(new File(defaultSnippetsDir, "response-fields.md")).isFile(); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class CustomizationConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java index b6727b92597..d0ab9210be3 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/mockmvc/WebMvcTestServletFilterRegistrationDisabledIntegrationTests.java @@ -44,7 +44,7 @@ class WebMvcTestServletFilterRegistrationDisabledIntegrationTests { this.mvc.perform(get("/one")).andExpect(header().string("x-test", (String) null)); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class DisabledRegistrationConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java index 8361b443145..4fee6426fbf 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestConfiguration.java @@ -40,7 +40,7 @@ import org.springframework.core.annotation.AliasFor; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Configuration(proxyBeanMethods = false) +@Configuration @TestComponent public @interface TestConfiguration { @@ -71,8 +71,8 @@ public @interface TestConfiguration { * individually like when declared on non-{@code @Configuration} classes, a.k.a. * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally * equivalent to removing the {@code @Configuration} stereotype. - * @since 2.2.1 * @return whether to proxy {@code @Bean} methods + * @since 2.2.1 */ @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java index dade7f74ba6..1984d4e648e 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/TestConfigurationTests.java @@ -16,39 +16,42 @@ package org.springframework.boot.test.context; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; + +import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link TestConfiguration}. + * Tests for {@link TestConfiguration @TestConfiguration}. * - * @author Dmytro Nosan + * @author Stephane Nicoll */ class TestConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - @Test - void shouldProxyBeanMethods() { - this.contextRunner.withUserConfiguration(ProxyBeanMethodsConfiguration.class) - .run((context) -> Assertions.assertThat(context).hasFailed()); + void proxyBeanMethodsIsEnabledByDefault() { + AnnotationAttributes attributes = AnnotatedElementUtils + .getMergedAnnotationAttributes(DefaultTestConfiguration.class, Configuration.class); + assertThat(attributes.get("proxyBeanMethods")).isEqualTo(true); } @Test - void shouldNotProxyBeanMethods() { - this.contextRunner.withUserConfiguration(ProxyBeanMethodsDisableConfiguration.class) - .run((context) -> Assertions.assertThat(context).hasNotFailed()); + void proxyBeanMethodsCanBeDisabled() { + AnnotationAttributes attributes = AnnotatedElementUtils + .getMergedAnnotationAttributes(NoBeanMethodProxyingTestConfiguration.class, Configuration.class); + assertThat(attributes.get("proxyBeanMethods")).isEqualTo(false); } @TestConfiguration - final static class ProxyBeanMethodsConfiguration { + static class DefaultTestConfiguration { } @TestConfiguration(proxyBeanMethods = false) - final static class ProxyBeanMethodsDisableConfiguration { + static class NoBeanMethodProxyingTestConfiguration { } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java index 66bc35a0642..9f663e68776 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperIntegrationTests.java @@ -67,7 +67,7 @@ class SpringBootTestContextBootstrapperIntegrationTests { assertThat(this.defaultTestExecutionListenersPostProcessorCalled).isTrue(); } - @TestConfiguration + @TestConfiguration(proxyBeanMethods = false) static class TestConfig { @Bean