Align ReactiveWebApplicationContext with framework

Refactor `ReactiveWebApplicationContext` implementations to align closer
with the `WebApplicationContext` implementations defined in
Spring Framework.

The following classes are now provided:

  - `AnnotationConfigReactiveWebApplicationContext` -- A refreshable
    reactive web context with support for `@Configuration` classes.

  - `GenericReactiveWebApplicationContext` -- A non-refreshable reactive
    GenericApplicationContext.

  - `ReactiveWebServerApplicationContext` --  A non-refreshable reactive
    GenericApplicationContext with support for server discovery.

  - `AnnotationConfigReactiveWebServerApplicationContext` -- A
    non-refreshable reactive `GenericApplicationContext` with support
    for `@Configuration` classes and server discovery.

These classes roughly align to the following Servlet equivalents:

  - `AnnotationConfigWebApplicationContext` (Spring Framework)
  - `GenericWebApplicationContext` (Spring Framework)
  - `ServletWebServerApplicationContext` (Spring Boot)
  - `AnnotationConfigServletWebServerApplicationContext` (Spring Boot)

An additional `ConfigurableReactiveWebEnvironment` interface as also
been introduced, primarily for `@ConditionalOnWebApplication` to use.

Fixes gh-10852
pull/10906/merge
Phillip Webb 7 years ago
parent b6166dc12a
commit fa191d8c73

@ -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) {

@ -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<WebTestClient> clientConsumer) {
BiConsumer<ApplicationContext, WebTestClient> 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."

@ -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

@ -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;
}

@ -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<Class<?>> classes) {
ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext();
AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext();
classes.add(WebFluxEndpointConfiguration.class);
context.register(classes.toArray(new Class<?>[classes.size()]));
context.refresh();

@ -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"));

@ -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();
}

@ -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"));
}

@ -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) {

@ -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() {

@ -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);

@ -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);

@ -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<Class<?>> configClasses = new ArrayList<>();
if (config != null) {

@ -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);
}
/**

@ -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).
* <p>
* 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<Class<?>> annotatedClasses = new LinkedHashSet<>();
private final Set<String> 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}.
* <p>
* 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}.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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...)}.
* <p>
* 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.
* <p>
* Enables the default set of annotation configuration post processors, such that
* {@code @Autowired}, {@code @Required}, and associated annotations can be used.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

@ -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).
* <p>
* 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}
* <p>
* 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.
* <p>
* Default is
* {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
* <p>
* 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.
* <p>
* The default is an {@link AnnotationScopeMetadataResolver}.
* <p>
* 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.
* <p>
* 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);
}
}
}

@ -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 {
}

@ -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.
* <p>
* {@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");
}
}

@ -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.
* <p>
* {@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");
}
}
}

@ -36,11 +36,6 @@ public class ReactiveWebServerApplicationContext
private volatile WebServer webServer;
public ReactiveWebServerApplicationContext() {
super();
}
public ReactiveWebServerApplicationContext(Class<?>... annotatedClasses) {
super(annotatedClasses);
}
@Override

@ -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 {
}

@ -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();
}
}
}

@ -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);
}
}

@ -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<String, HttpHandler> httpHandlerMap;
public MockReactiveWebServer(HttpHandler httpHandler, int port) {
this.httpHandler = httpHandler;
this.port = port;
}
public MockReactiveWebServer(Map<String, HttpHandler> httpHandlerMap, int port) {
this.httpHandlerMap = httpHandlerMap;
this.port = port;
}
public HttpHandler getHttpHandler() {
return this.httpHandler;
}
public Map<String, HttpHandler> getHttpHandlerMap() {
return this.httpHandlerMap;
}
@Override
public void start() throws WebServerException {
}
@Override
public void stop() throws WebServerException {
}
@Override
public int getPort() {
return this.port;
}
}
}
Loading…
Cancel
Save