diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java index e74e286796..612df46f2a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java @@ -25,8 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -41,7 +40,7 @@ class ReactiveManagementContextFactory implements ManagementContextFactory { @Override public ConfigurableApplicationContext createManagementContext( ApplicationContext parent, Class... configClasses) { - ReactiveWebServerApplicationContext child = new ReactiveWebServerApplicationContext(); + AnnotationConfigReactiveWebServerApplicationContext child = new AnnotationConfigReactiveWebServerApplicationContext(); child.setParent(parent); child.register(configClasses); child.register(ReactiveWebServerAutoConfiguration.class); @@ -50,7 +49,7 @@ class ReactiveManagementContextFactory implements ManagementContextFactory { } private void registerReactiveWebServerFactory(ApplicationContext parent, - GenericReactiveWebApplicationContext childContext) { + AnnotationConfigReactiveWebServerApplicationContext childContext) { try { ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); if (beanFactory instanceof BeanDefinitionRegistry) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index f79f48f0e8..bc05eef7f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -40,7 +40,7 @@ import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.context.ReactiveWebServerInitializedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -165,21 +165,17 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { .doesNotExist().jsonPath("_links.test-part").doesNotExist()); } - private ReactiveWebServerApplicationContext createApplicationContext( + private AnnotationConfigReactiveWebServerApplicationContext createApplicationContext( Class... config) { - ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); + AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); context.register(config); return context; } - protected int getPort(ReactiveWebServerApplicationContext context) { - return context.getBean(CloudFoundryReactiveConfiguration.class).port; - } - private void load(Class configuration, Consumer clientConsumer) { BiConsumer consumer = (context, client) -> clientConsumer.accept(client); - ReactiveWebServerApplicationContext context = createApplicationContext( + AnnotationConfigReactiveWebServerApplicationContext context = createApplicationContext( configuration, CloudFoundryReactiveConfiguration.class); context.refresh(); try { @@ -191,6 +187,10 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { } } + protected int getPort(AnnotationConfigReactiveWebServerApplicationContext context) { + return context.getBean(CloudFoundryReactiveConfiguration.class).port; + } + private String mockAccessToken() { return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index cf63872e7b..a34cb91340 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -38,7 +38,7 @@ import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurat import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -61,11 +61,11 @@ import static org.mockito.Mockito.mock; */ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { - private GenericReactiveWebApplicationContext context; + private AnnotationConfigReactiveWebApplicationContext context; @Before public void setup() { - this.context = new GenericReactiveWebApplicationContext(); + this.context = new AnnotationConfigReactiveWebApplicationContext(); } @After diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java index d454cb8773..af37cd8989 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.context.ReactiveWebServerInitializedEvent; import org.springframework.context.ApplicationContext; @@ -80,9 +81,9 @@ public class WebFluxEndpointIntegrationTests } @Override - protected ReactiveWebServerApplicationContext createApplicationContext( + protected AnnotationConfigReactiveWebServerApplicationContext createApplicationContext( Class... config) { - ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); + AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); context.register(config); return context; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java index b24bbda68e..42683280d0 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java @@ -34,7 +34,7 @@ import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurat import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; @@ -58,7 +58,7 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner { } private static ConfigurableApplicationContext createContext(List> classes) { - ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); + AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); classes.add(WebFluxEndpointConfiguration.class); context.register(classes.toArray(new Class[classes.size()])); context.refresh(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java index af98818e56..7ae98f285b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition; import java.util.Map; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment; import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; @@ -114,6 +115,10 @@ class OnWebApplicationCondition extends SpringBootCondition { private ConditionOutcome isReactiveWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); + if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) { + return ConditionOutcome + .match(message.foundExactly("ConfigurableReactiveWebEnvironment")); + } if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) { return ConditionOutcome .match(message.foundExactly("ReactiveWebApplicationContext")); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnNotWebApplicationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnNotWebApplicationTests.java index 8b90bb0025..a038042d1c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnNotWebApplicationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnNotWebApplicationTests.java @@ -21,7 +21,7 @@ import org.junit.Test; import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.web.reactive.MockReactiveWebServerFactory; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -57,18 +57,17 @@ public class ConditionalOnNotWebApplicationTests { ctx.register(NotWebApplicationConfiguration.class); ctx.setServletContext(new MockServletContext()); ctx.refresh(); - this.context = ctx; assertThat(this.context.getBeansOfType(String.class)).isEmpty(); } @Test public void testNotWebApplicationWithReactiveContext() { - GenericReactiveWebApplicationContext ctx = new GenericReactiveWebApplicationContext(); - ctx.register(ReactiveApplicationConfig.class, + AnnotationConfigReactiveWebApplicationContext context = new AnnotationConfigReactiveWebApplicationContext(); + context.register(ReactiveApplicationConfig.class, NotWebApplicationConfiguration.class); - ctx.refresh(); - this.context = ctx; + context.refresh(); + this.context = context; assertThat(this.context.getBeansOfType(String.class)).isEmpty(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplicationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplicationTests.java index 33293be3a6..df21b9a5b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplicationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplicationTests.java @@ -22,7 +22,7 @@ import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.reactive.MockReactiveWebServerFactory; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -67,12 +67,12 @@ public class ConditionalOnWebApplicationTests { @Test public void testWebApplicationWithReactiveContext() { - GenericReactiveWebApplicationContext ctx = new GenericReactiveWebApplicationContext(); - ctx.register(AnyWebApplicationConfiguration.class, + AnnotationConfigReactiveWebApplicationContext context = new AnnotationConfigReactiveWebApplicationContext(); + context.register(AnyWebApplicationConfiguration.class, ServletWebApplicationConfiguration.class, ReactiveWebApplicationConfiguration.class); - ctx.refresh(); - this.context = ctx; + context.refresh(); + this.context = context; assertThat(this.context.getBeansOfType(String.class)) .containsExactly(entry("any", "any"), entry("reactive", "reactive")); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfigurationTests.java index d56cde04bf..02d8e370e5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfigurationTests.java @@ -20,7 +20,7 @@ import com.samskivert.mustache.Mustache; import org.junit.Test; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.servlet.view.MustacheViewResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,7 +38,7 @@ public class MustacheAutoConfigurationTests { private AnnotationConfigWebApplicationContext webContext; - private GenericReactiveWebApplicationContext reactiveWebContext; + private AnnotationConfigReactiveWebApplicationContext reactiveWebContext; @Test public void registerBeansForServletApp() { @@ -102,7 +102,7 @@ public class MustacheAutoConfigurationTests { } private void loadWithReactive(Class config) { - this.reactiveWebContext = new GenericReactiveWebApplicationContext(); + this.reactiveWebContext = new AnnotationConfigReactiveWebApplicationContext(); TestPropertyValues.of("spring.mustache.prefix=classpath:/mustache-templates/") .applyTo(this.reactiveWebContext); if (config != null) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java index e6bf1278f9..899bc875fe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java @@ -21,8 +21,7 @@ import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.MockReactiveWebServerFactory; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -47,8 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class ReactiveSecurityAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner( - ReactiveWebServerApplicationContext::new); + private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(); @Test public void enablesWebFluxSecurity() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java index 240554ac96..92fc598bb1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java @@ -36,7 +36,7 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -57,7 +57,7 @@ public class ThymeleafReactiveAutoConfigurationTests { @Rule public OutputCapture output = new OutputCapture(); - private GenericReactiveWebApplicationContext context; + private AnnotationConfigReactiveWebApplicationContext context; @After public void close() { @@ -193,7 +193,7 @@ public class ThymeleafReactiveAutoConfigurationTests { } private void load(Class config, String... envVariables) { - this.context = new GenericReactiveWebApplicationContext(); + this.context = new AnnotationConfigReactiveWebApplicationContext(); TestPropertyValues.of(envVariables).applyTo(this.context); if (config != null) { this.context.register(config); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java index d937b1d351..ac0df680af 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerAutoConfigurationTests.java @@ -22,7 +22,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; @@ -41,14 +41,15 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class ReactiveWebServerAutoConfigurationTests { - private ReactiveWebServerApplicationContext context; + private AnnotationConfigReactiveWebServerApplicationContext context; @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void createFromConfigClass() { - this.context = new ReactiveWebServerApplicationContext(BaseConfiguration.class); + this.context = new AnnotationConfigReactiveWebServerApplicationContext( + BaseConfiguration.class); assertThat(this.context.getBeansOfType(ReactiveWebServerFactory.class)) .hasSize(1); assertThat(this.context.getBeansOfType(WebServerFactoryCustomizer.class)) @@ -61,7 +62,7 @@ public class ReactiveWebServerAutoConfigurationTests { public void missingHttpHandler() { this.thrown.expect(ApplicationContextException.class); this.thrown.expectMessage(Matchers.containsString("missing HttpHandler bean")); - this.context = new ReactiveWebServerApplicationContext( + this.context = new AnnotationConfigReactiveWebServerApplicationContext( MissingHttpHandlerConfiguration.class); } @@ -70,14 +71,14 @@ public class ReactiveWebServerAutoConfigurationTests { this.thrown.expect(ApplicationContextException.class); this.thrown.expectMessage(Matchers.containsString( "multiple HttpHandler beans : httpHandler,additionalHttpHandler")); - this.context = new ReactiveWebServerApplicationContext(BaseConfiguration.class, - TooManyHttpHandlers.class); + this.context = new AnnotationConfigReactiveWebServerApplicationContext( + BaseConfiguration.class, TooManyHttpHandlers.class); } @Test public void customizeReactiveWebServer() { - this.context = new ReactiveWebServerApplicationContext(BaseConfiguration.class, - ReactiveWebServerCustomization.class); + this.context = new AnnotationConfigReactiveWebServerApplicationContext( + BaseConfiguration.class, ReactiveWebServerCustomization.class); MockReactiveWebServerFactory factory = this.context .getBean(MockReactiveWebServerFactory.class); assertThat(factory.getPort()).isEqualTo(9000); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 9cb203cd27..0cf8775409 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguratio import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.web.codec.CodecCustomizer; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -72,7 +72,7 @@ import static org.mockito.Mockito.verify; */ public class WebFluxAutoConfigurationTests { - private GenericReactiveWebApplicationContext context; + private AnnotationConfigReactiveWebApplicationContext context; @Test public void shouldNotProcessIfExistingWebReactiveConfiguration() throws Exception { @@ -293,7 +293,7 @@ public class WebFluxAutoConfigurationTests { } private void load(Class config, Class[] exclude, String... environment) { - this.context = new GenericReactiveWebApplicationContext(); + this.context = new AnnotationConfigReactiveWebApplicationContext(); TestPropertyValues.of(environment).applyTo(this.context); List> configClasses = new ArrayList<>(); if (config != null) { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java index 0513cbe969..c1fea9cd27 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java @@ -22,8 +22,8 @@ import java.util.function.Supplier; import org.springframework.boot.context.annotation.Configurations; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext; -import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.context.ApplicationContext; /** @@ -42,10 +42,10 @@ public final class ReactiveWebApplicationContextRunner extends /** * Create a new {@link ReactiveWebApplicationContextRunner} instance using a - * {@link GenericReactiveWebApplicationContext} as the underlying source. + * {@link AnnotationConfigReactiveWebApplicationContext} as the underlying source. */ public ReactiveWebApplicationContextRunner() { - this(GenericReactiveWebApplicationContext::new); + this(AnnotationConfigReactiveWebApplicationContext::new); } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebApplicationContext.java new file mode 100644 index 0000000000..656e69684e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebApplicationContext.java @@ -0,0 +1,340 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.AnnotationConfigRegistry; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ScopeMetadataResolver; +import org.springframework.context.support.AbstractRefreshableConfigApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * {@link ConfigurableReactiveWebApplicationContext} that accepts annotated classes as + * input - in particular + * {@link org.springframework.context.annotation.Configuration @Configuration} -annotated + * classes, but also plain {@link Component @Component} classes and JSR-330 compliant + * classes using {@code javax.inject} annotations. Allows for registering classes one by + * one (specifying class names as config location) as well as for classpath scanning + * (specifying base packages as config location). + *

+ * Note: In case of multiple {@code @Configuration} classes, later {@code @Bean} + * definitions will override ones defined in earlier loaded files. This can be leveraged + * to deliberately override certain bean definitions via an extra Configuration class. + * + * @author Phillip Webb + * @since 2.0.0 + * @see #register(Class...) + * @see #scan(String...) + */ +public class AnnotationConfigReactiveWebApplicationContext + extends AbstractRefreshableConfigApplicationContext + implements ConfigurableReactiveWebApplicationContext, AnnotationConfigRegistry { + + private BeanNameGenerator beanNameGenerator; + + private ScopeMetadataResolver scopeMetadataResolver; + + private final Set> annotatedClasses = new LinkedHashSet<>(); + + private final Set basePackages = new LinkedHashSet<>(); + + private String namespace; + + @Override + protected ConfigurableEnvironment createEnvironment() { + return new StandardReactiveWebEnvironment(); + } + + /** + * Set a custom {@link BeanNameGenerator} for use with + * {@link AnnotatedBeanDefinitionReader} and/or + * {@link ClassPathBeanDefinitionScanner}. + *

+ * Default is + * {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. + * @param beanNameGenerator the bean name generator + * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator + * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator + */ + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.beanNameGenerator = beanNameGenerator; + } + + /** + * Return the custom {@link BeanNameGenerator} for use with + * {@link AnnotatedBeanDefinitionReader} and/or + * {@link ClassPathBeanDefinitionScanner}, if any. + * @return the bean name generator + */ + protected BeanNameGenerator getBeanNameGenerator() { + return this.beanNameGenerator; + } + + /** + * Set a custom {@link ScopeMetadataResolver} for use with + * {@link AnnotatedBeanDefinitionReader} and/or + * {@link ClassPathBeanDefinitionScanner}. + *

+ * Default is an + * {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}. + * @param scopeMetadataResolver the scope metadata resolver + * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver + * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver + */ + public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { + this.scopeMetadataResolver = scopeMetadataResolver; + } + + /** + * Return the custom {@link ScopeMetadataResolver} for use with + * {@link AnnotatedBeanDefinitionReader} and/or + * {@link ClassPathBeanDefinitionScanner}, if any. + * @return the scope metadata resolver + */ + protected ScopeMetadataResolver getScopeMetadataResolver() { + return this.scopeMetadataResolver; + } + + /** + * Register one or more annotated classes to be processed. + *

+ * Note that {@link #refresh()} must be called in order for the context to fully + * process the new classes. + * @param annotatedClasses one or more annotated classes, e.g. + * {@link org.springframework.context.annotation.Configuration @Configuration} classes + * @see #scan(String...) + * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * @see #setConfigLocation(String) + * @see #refresh() + */ + @Override + public void register(Class... annotatedClasses) { + Assert.notEmpty(annotatedClasses, + "At least one annotated class must be specified"); + this.annotatedClasses.addAll(Arrays.asList(annotatedClasses)); + } + + /** + * Perform a scan within the specified base packages. + *

+ * Note that {@link #refresh()} must be called in order for the context to fully + * process the new classes. + * @param basePackages the packages to check for annotated classes + * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * @see #register(Class...) + * @see #setConfigLocation(String) + * @see #refresh() + */ + @Override + public void scan(String... basePackages) { + Assert.notEmpty(basePackages, "At least one base package must be specified"); + this.basePackages.addAll(Arrays.asList(basePackages)); + } + + /** + * Register a {@link org.springframework.beans.factory.config.BeanDefinition} for any + * classes specified by {@link #register(Class...)} and scan any packages specified by + * {@link #scan(String...)}. + *

+ * For any values specified by {@link #setConfigLocation(String)} or + * {@link #setConfigLocations(String[])}, attempt first to load each location as a + * class, registering a {@code BeanDefinition} if class loading is successful, and if + * class loading fails (i.e. a {@code ClassNotFoundException} is raised), assume the + * value is a package and attempt to scan it for annotated classes. + *

+ * Enables the default set of annotation configuration post processors, such that + * {@code @Autowired}, {@code @Required}, and associated annotations can be used. + *

+ * Configuration class bean definitions are registered with generated bean definition + * names unless the {@code value} attribute is provided to the stereotype annotation. + * @param beanFactory the bean factory to load bean definitions into + * @see #register(Class...) + * @see #scan(String...) + * @see #setConfigLocation(String) + * @see #setConfigLocations(String[]) + * @see AnnotatedBeanDefinitionReader + * @see ClassPathBeanDefinitionScanner + */ + @Override + protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { + AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader( + beanFactory); + ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner( + beanFactory); + applyBeanNameGenerator(beanFactory, reader, scanner); + applyScopeMetadataResolver(reader, scanner); + loadBeanDefinitions(reader, scanner); + } + + private void applyBeanNameGenerator(DefaultListableBeanFactory beanFactory, + AnnotatedBeanDefinitionReader reader, + ClassPathBeanDefinitionScanner scanner) { + BeanNameGenerator beanNameGenerator = getBeanNameGenerator(); + if (beanNameGenerator != null) { + reader.setBeanNameGenerator(beanNameGenerator); + scanner.setBeanNameGenerator(beanNameGenerator); + beanFactory.registerSingleton( + AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, + beanNameGenerator); + } + } + + private void applyScopeMetadataResolver(AnnotatedBeanDefinitionReader reader, + ClassPathBeanDefinitionScanner scanner) { + ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver(); + if (scopeMetadataResolver != null) { + reader.setScopeMetadataResolver(scopeMetadataResolver); + scanner.setScopeMetadataResolver(scopeMetadataResolver); + } + } + + private void loadBeanDefinitions(AnnotatedBeanDefinitionReader reader, + ClassPathBeanDefinitionScanner scanner) throws LinkageError { + if (!this.annotatedClasses.isEmpty()) { + registerAnnotatedClasses(reader); + } + if (!this.basePackages.isEmpty()) { + scanBasePackages(scanner); + } + String[] configLocations = getConfigLocations(); + if (configLocations != null) { + registerConfigLocations(reader, scanner, configLocations); + } + } + + private void registerAnnotatedClasses(AnnotatedBeanDefinitionReader reader) { + if (this.logger.isInfoEnabled()) { + this.logger.info("Registering annotated classes: [" + + StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) + + "]"); + } + reader.register(this.annotatedClasses + .toArray(new Class[this.annotatedClasses.size()])); + } + + private void scanBasePackages(ClassPathBeanDefinitionScanner scanner) { + if (this.logger.isInfoEnabled()) { + this.logger.info("Scanning base packages: [" + + StringUtils.collectionToCommaDelimitedString(this.basePackages) + + "]"); + } + scanner.scan(this.basePackages.toArray(new String[this.basePackages.size()])); + } + + private void registerConfigLocations(AnnotatedBeanDefinitionReader reader, + ClassPathBeanDefinitionScanner scanner, String[] configLocations) + throws LinkageError { + for (String configLocation : configLocations) { + try { + register(reader, configLocation); + } + catch (ClassNotFoundException ex) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Could not load class for config location [" + + configLocation + "] - trying package scan. " + ex); + } + int count = scanner.scan(configLocation); + if (this.logger.isInfoEnabled()) { + logScanResult(configLocation, count); + } + } + } + } + + private void register(AnnotatedBeanDefinitionReader reader, String configLocation) + throws ClassNotFoundException, LinkageError { + Class clazz = ClassUtils.forName(configLocation, getClassLoader()); + if (this.logger.isInfoEnabled()) { + this.logger.info("Successfully resolved class for [" + configLocation + "]"); + } + reader.register(clazz); + } + + private void logScanResult(String configLocation, int count) { + if (count == 0) { + this.logger.info("No annotated classes found for specified class/package [" + + configLocation + "]"); + } + else { + this.logger.info("Found " + count + " annotated classes in package [" + + configLocation + "]"); + } + } + + /** + * Build an {@link AnnotatedBeanDefinitionReader} for the given bean factory. + *

+ * This should be pre-configured with the {@code Environment} (if desired) but not + * with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet. + * @param beanFactory the bean factory to load bean definitions into + * @return the annotated bean definition reader + * @see #getEnvironment() + * @see #getBeanNameGenerator() + * @see #getScopeMetadataResolver() + */ + protected AnnotatedBeanDefinitionReader getAnnotatedBeanDefinitionReader( + DefaultListableBeanFactory beanFactory) { + return new AnnotatedBeanDefinitionReader(beanFactory, getEnvironment()); + } + + /** + * Build a {@link ClassPathBeanDefinitionScanner} for the given bean factory. + *

+ * This should be pre-configured with the {@code Environment} (if desired) but not + * with a {@code BeanNameGenerator} or {@code ScopeMetadataResolver} yet. + * @param beanFactory the bean factory to load bean definitions into + * @return the class path bean definition scanner + * @since 4.1.9 + * @see #getEnvironment() + * @see #getBeanNameGenerator() + * @see #getScopeMetadataResolver() + */ + protected ClassPathBeanDefinitionScanner getClassPathBeanDefinitionScanner( + DefaultListableBeanFactory beanFactory) { + return new ClassPathBeanDefinitionScanner(beanFactory, true, getEnvironment()); + } + + @Override + protected Resource getResourceByPath(String path) { + // We must be careful not to expose classpath resources + return new FilteredReactiveWebContextResource(path); + } + + @Override + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + @Override + public String getNamespace() { + return this.namespace; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java new file mode 100644 index 0000000000..5d52691266 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java @@ -0,0 +1,194 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.AnnotationScopeMetadataResolver; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ScopeMetadataResolver; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +/** + * {@link ReactiveWebServerApplicationContext} that accepts annotated classes as input - + * in particular + * {@link org.springframework.context.annotation.Configuration @Configuration} -annotated + * classes, but also plain {@link Component @Component} classes and JSR-330 compliant + * classes using {@code javax.inject} annotations. Allows for registering classes one by + * one (specifying class names as config location) as well as for classpath scanning + * (specifying base packages as config location). + *

+ * Note: In case of multiple {@code @Configuration} classes, later {@code @Bean} + * definitions will override ones defined in earlier loaded files. This can be leveraged + * to deliberately override certain bean definitions via an extra Configuration class. + * + * @author Phillip Webb + * @since 2.0.0 + * @see #register(Class...) + * @see #scan(String...) + * @see ReactiveWebServerApplicationContext + * @see AnnotationConfigApplicationContext + */ +public class AnnotationConfigReactiveWebServerApplicationContext + extends ReactiveWebServerApplicationContext { + + private final AnnotatedBeanDefinitionReader reader; + + private final ClassPathBeanDefinitionScanner scanner; + + private Class[] annotatedClasses; + + private String[] basePackages; + + /** + * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext} that needs + * to be populated through {@link #register} calls and then manually + * {@linkplain #refresh refreshed}. + */ + public AnnotationConfigReactiveWebServerApplicationContext() { + this.reader = new AnnotatedBeanDefinitionReader(this); + this.scanner = new ClassPathBeanDefinitionScanner(this); + } + + /** + * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext}, deriving + * bean definitions from the given annotated classes and automatically refreshing the + * context. + * @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration} + * classes + */ + public AnnotationConfigReactiveWebServerApplicationContext( + Class... annotatedClasses) { + this(); + register(annotatedClasses); + refresh(); + } + + /** + * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext}, scanning + * for bean definitions in the given packages and automatically refreshing the + * context. + * @param basePackages the packages to check for annotated classes + */ + public AnnotationConfigReactiveWebServerApplicationContext(String... basePackages) { + this(); + scan(basePackages); + refresh(); + } + + /** + * {@inheritDoc} + *

+ * Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and + * {@link ClassPathBeanDefinitionScanner} members. + */ + @Override + public void setEnvironment(ConfigurableEnvironment environment) { + super.setEnvironment(environment); + this.reader.setEnvironment(environment); + this.scanner.setEnvironment(environment); + } + + /** + * Provide a custom {@link BeanNameGenerator} for use with + * {@link AnnotatedBeanDefinitionReader} and/or {@link ClassPathBeanDefinitionScanner} + * , if any. + *

+ * Default is + * {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. + *

+ * Any call to this method must occur prior to calls to {@link #register(Class...)} + * and/or {@link #scan(String...)}. + * @param beanNameGenerator the bean name generator + * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator + * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator + */ + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.reader.setBeanNameGenerator(beanNameGenerator); + this.scanner.setBeanNameGenerator(beanNameGenerator); + this.getBeanFactory().registerSingleton( + AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, + beanNameGenerator); + } + + /** + * Set the {@link ScopeMetadataResolver} to use for detected bean classes. + *

+ * The default is an {@link AnnotationScopeMetadataResolver}. + *

+ * Any call to this method must occur prior to calls to {@link #register(Class...)} + * and/or {@link #scan(String...)}. + * @param scopeMetadataResolver the scope metadata resolver + */ + public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { + this.reader.setScopeMetadataResolver(scopeMetadataResolver); + this.scanner.setScopeMetadataResolver(scopeMetadataResolver); + } + + /** + * Register one or more annotated classes to be processed. Note that + * {@link #refresh()} must be called in order for the context to fully process the new + * class. + *

+ * Calls to {@code #register} are idempotent; adding the same annotated class more + * than once has no additional effect. + * @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration} + * classes + * @see #scan(String...) + * @see #refresh() + */ + public final void register(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; + Assert.notEmpty(annotatedClasses, + "At least one annotated class must be specified"); + } + + /** + * Perform a scan within the specified base packages. Note that {@link #refresh()} + * must be called in order for the context to fully process the new class. + * @param basePackages the packages to check for annotated classes + * @see #register(Class...) + * @see #refresh() + */ + public final void scan(String... basePackages) { + this.basePackages = basePackages; + Assert.notEmpty(basePackages, "At least one base package must be specified"); + } + + @Override + protected void prepareRefresh() { + this.scanner.clearCache(); + super.prepareRefresh(); + } + + @Override + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.postProcessBeanFactory(beanFactory); + if (this.basePackages != null && this.basePackages.length > 0) { + this.scanner.scan(this.basePackages); + } + if (this.annotatedClasses != null && this.annotatedClasses.length > 0) { + this.reader.register(this.annotatedClasses); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ConfigurableReactiveWebEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ConfigurableReactiveWebEnvironment.java new file mode 100644 index 0000000000..252bce11e4 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ConfigurableReactiveWebEnvironment.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * Specialization of {@link ConfigurableEnvironment} for reactive application contexts. + * + * @author Phillip Webb + * @since 2.0.0 + * @see ConfigurableReactiveWebApplicationContext#getEnvironment() + */ +public interface ConfigurableReactiveWebEnvironment extends ConfigurableEnvironment { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/FilteredReactiveWebContextResource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/FilteredReactiveWebContextResource.java new file mode 100644 index 0000000000..3e81b39b50 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/FilteredReactiveWebContextResource.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.core.io.AbstractResource; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +/** + * Resource implementation that replaces the + * {@link org.springframework.web.context.support.ServletContextResource} in a reactive + * web application. + *

+ * {@link #exists()} always returns {@code false} in order to avoid exposing the whole + * classpath in a non-servlet environment. + * + * @author Brian Clozel + */ +class FilteredReactiveWebContextResource extends AbstractResource { + + private final String path; + + FilteredReactiveWebContextResource(String path) { + this.path = path; + } + + @Override + public boolean exists() { + return false; + } + + @Override + public Resource createRelative(String relativePath) throws IOException { + String pathToUse = StringUtils.applyRelativePath(this.path, relativePath); + return new FilteredReactiveWebContextResource(pathToUse); + } + + @Override + public String getDescription() { + return "ReactiveWebContext resource [" + this.path + "]"; + } + + @Override + public InputStream getInputStream() throws IOException { + throw new FileNotFoundException( + this.getDescription() + " cannot be opened because it does not exist"); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/GenericReactiveWebApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/GenericReactiveWebApplicationContext.java index 3837ba2f8e..7371618e42 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/GenericReactiveWebApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/GenericReactiveWebApplicationContext.java @@ -16,35 +16,46 @@ package org.springframework.boot.web.reactive.context; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.io.AbstractResource; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; -import org.springframework.util.StringUtils; /** - * Subclass of {@link AnnotationConfigApplicationContext}, suitable for reactive web - * environments. + * Subclass of {@link GenericApplicationContext}, suitable for reactive web environments. * * @author Stephane Nicoll * @author Brian Clozel * @since 2.0.0 */ -public class GenericReactiveWebApplicationContext - extends AnnotationConfigApplicationContext +public class GenericReactiveWebApplicationContext extends GenericApplicationContext implements ConfigurableReactiveWebApplicationContext { private String namespace; + /** + * Create a new {@link GenericReactiveWebApplicationContext}. + * @see #registerBeanDefinition + * @see #refresh + */ public GenericReactiveWebApplicationContext() { super(); } - public GenericReactiveWebApplicationContext(Class[] annotatedClasses) { - super(annotatedClasses); + /** + * Create a new {@link GenericReactiveWebApplicationContext} with the given + * DefaultListableBeanFactory. + * @param beanFactory the DefaultListableBeanFactory instance to use for this context + * @see #registerBeanDefinition + * @see #refresh + */ + public GenericReactiveWebApplicationContext(DefaultListableBeanFactory beanFactory) { + super(beanFactory); + } + + @Override + protected ConfigurableEnvironment createEnvironment() { + return new StandardReactiveWebEnvironment(); } @Override @@ -63,43 +74,4 @@ public class GenericReactiveWebApplicationContext return new FilteredReactiveWebContextResource(path); } - /** - * Resource implementation that replaces the - * {@link org.springframework.web.context.support.ServletContextResource} in a - * reactive web application. - *

- * {@link #exists()} always returns {@code false} in order to avoid exposing the whole - * classpath in a non-servlet environment. - */ - class FilteredReactiveWebContextResource extends AbstractResource { - - private final String path; - - FilteredReactiveWebContextResource(String path) { - this.path = path; - } - - @Override - public boolean exists() { - return false; - } - - @Override - public Resource createRelative(String relativePath) throws IOException { - String pathToUse = StringUtils.applyRelativePath(this.path, relativePath); - return new FilteredReactiveWebContextResource(pathToUse); - } - - @Override - public String getDescription() { - return "ReactiveWebContext resource [" + this.path + "]"; - } - - @Override - public InputStream getInputStream() throws IOException { - throw new FileNotFoundException(this.getDescription() - + " cannot be opened because it does not exist"); - } - - } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java index 57e25c2bec..aff7446dbe 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java @@ -36,11 +36,6 @@ public class ReactiveWebServerApplicationContext private volatile WebServer webServer; public ReactiveWebServerApplicationContext() { - super(); - } - - public ReactiveWebServerApplicationContext(Class... annotatedClasses) { - super(annotatedClasses); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/StandardReactiveWebEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/StandardReactiveWebEnvironment.java new file mode 100644 index 0000000000..dc0a394b43 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/StandardReactiveWebEnvironment.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; + +/** + * {@link Environment} implementation to be used by {@code Reactive}-based web + * applications. All web-related (reactive-based) {@code ApplicationContext} classes + * initialize an instance by default. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public class StandardReactiveWebEnvironment extends StandardEnvironment + implements ConfigurableReactiveWebEnvironment { + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java new file mode 100644 index 0000000000..861cab0ee3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContextTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context; + +import org.junit.Test; + +import org.springframework.boot.web.reactive.context.config.ExampleReactiveWebServerApplicationConfiguration; +import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory; +import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.reactive.HttpHandler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AnnotationConfigReactiveWebServerApplicationContext}. + * + * @author Phillip Webb + */ +public class AnnotationConfigReactiveWebServerApplicationContextTests { + + private AnnotationConfigReactiveWebServerApplicationContext context; + + @Test + public void createFromScan() throws Exception { + this.context = new AnnotationConfigReactiveWebServerApplicationContext( + ExampleReactiveWebServerApplicationConfiguration.class.getPackage() + .getName()); + verifyContext(); + } + + @Test + public void createFromConfigClass() throws Exception { + this.context = new AnnotationConfigReactiveWebServerApplicationContext( + ExampleReactiveWebServerApplicationConfiguration.class); + verifyContext(); + } + + @Test + public void registerAndRefresh() throws Exception { + this.context = new AnnotationConfigReactiveWebServerApplicationContext(); + this.context.register(ExampleReactiveWebServerApplicationConfiguration.class); + this.context.refresh(); + verifyContext(); + } + + @Test + public void scanAndRefresh() throws Exception { + this.context = new AnnotationConfigReactiveWebServerApplicationContext(); + this.context.scan(ExampleReactiveWebServerApplicationConfiguration.class + .getPackage().getName()); + this.context.refresh(); + verifyContext(); + } + + private void verifyContext() { + MockReactiveWebServerFactory factory = this.context + .getBean(MockReactiveWebServerFactory.class); + HttpHandler httpHandler = this.context.getBean(HttpHandler.class); + assertThat(factory.getWebServer().getHttpHandler()).isEqualTo(httpHandler); + } + + @Configuration + public static class WebServerConfiguration { + + @Bean + public ReactiveWebServerFactory webServerFactory() { + return new MockReactiveWebServerFactory(); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/config/ExampleReactiveWebServerApplicationConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/config/ExampleReactiveWebServerApplicationConfiguration.java new file mode 100644 index 0000000000..fd1ee0f454 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/config/ExampleReactiveWebServerApplicationConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.context.config; + +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContextTests; +import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.reactive.HttpHandler; + +import static org.mockito.Mockito.mock; + +/** + * Example {@code @Configuration} for use with + * {@link AnnotationConfigReactiveWebServerApplicationContextTests}. + * + * @author Phillip Webb + */ +@Configuration +public class ExampleReactiveWebServerApplicationConfiguration { + + @Bean + public MockReactiveWebServerFactory webServerFactory() { + return new MockReactiveWebServerFactory(); + } + + @Bean + public HttpHandler httpHandler() { + return mock(HttpHandler.class); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/MockReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/MockReactiveWebServerFactory.java new file mode 100644 index 0000000000..b0c5fa78cd --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/MockReactiveWebServerFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive.server; + +import java.util.Map; + +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.http.server.reactive.HttpHandler; + +import static org.mockito.Mockito.spy; + +/** + * Mock {@link ReactiveWebServerFactory}. + * + * @author Brian Clozel + */ +public class MockReactiveWebServerFactory extends AbstractReactiveWebServerFactory { + + private MockReactiveWebServer webServer; + + @Override + public WebServer getWebServer(HttpHandler httpHandler) { + this.webServer = spy(new MockReactiveWebServer(httpHandler, getPort())); + return this.webServer; + } + + public MockReactiveWebServer getWebServer() { + return this.webServer; + } + + public static class MockReactiveWebServer implements WebServer { + + private final int port; + + private HttpHandler httpHandler; + + private Map httpHandlerMap; + + public MockReactiveWebServer(HttpHandler httpHandler, int port) { + this.httpHandler = httpHandler; + this.port = port; + } + + public MockReactiveWebServer(Map httpHandlerMap, int port) { + this.httpHandlerMap = httpHandlerMap; + this.port = port; + } + + public HttpHandler getHttpHandler() { + return this.httpHandler; + } + + public Map getHttpHandlerMap() { + return this.httpHandlerMap; + } + + @Override + public void start() throws WebServerException { + + } + + @Override + public void stop() throws WebServerException { + + } + + @Override + public int getPort() { + return this.port; + } + + } + +}