Browse Source

Register @WebListeners in a way that allows them to register components

Previously, @WebListeners were discovered via custom component scanning
and then registered programmatically via the ServletContext. The servlet
spec requires any ServletContextListener registered in this manner to be
prohibited from programatically configuring servlets, filters, and
listeners. This left us not strictly complying with the servlet spec
as a ServletContextListener registered via a @WebListener annotation
should be able to programatically configure other components.

This commit updates WebListenerHandler to register each @WebListener
component directly with Jetty, Tomcat, or Undertow rather than via the
ServletContext API. This ensure that any @WebListener-annoated
ServletContextListener registered via servlet component scanning is
able to programatically register servlets, filters, and listeners.

There is a small chance that this will be a breaking change for some
users:

1. The ServletListenerRegistrationBeans that were previously defined
   for each @WebListener will now be
   WebListenerHandler.WebListenerRegistrars
2. Each @WebListener-annotated class will now be instantiated by
   Jetty, Tomcat, or Undertow. Jetty and Tomcat both require the class
   to be public and have a public default constructor. Previously,
   a package-private class or default constructor could be used as the
   instantiation was performed by Spring Framework. Undertow is not
   affected as it can instantiate a package-private type.

Fixes gh-18303
pull/23914/head
Andy Wilkinson 5 years ago committed by Phillip Webb
parent
commit
fafc0a91e3
  1. 9
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java
  2. 15
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java
  3. 41
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java
  4. 3
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java
  5. 19
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java
  6. 24
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java
  7. 35
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerRegistrar.java
  8. 35
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerRegistry.java
  9. 12
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java
  10. 4
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/ConfigurableServletWebServerFactory.java
  11. 89
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java
  12. 4
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java
  13. 27
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java
  14. 4
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java

9
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.web.servlet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.servlet.DispatcherType;
import javax.servlet.ServletRequest;
@ -24,6 +25,7 @@ import javax.servlet.ServletRequest; @@ -24,6 +25,7 @@ import javax.servlet.ServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
@ -38,6 +40,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -38,6 +40,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.WebListenerRegistrar;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -69,8 +72,10 @@ import org.springframework.web.filter.ForwardedHeaderFilter; @@ -69,8 +72,10 @@ import org.springframework.web.filter.ForwardedHeaderFilter;
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
return new ServletWebServerFactoryCustomizer(serverProperties,
webListenerRegistrars.orderedStream().collect(Collectors.toList()));
}
@Bean

15
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryCustomizer.java

@ -16,9 +16,13 @@ @@ -16,9 +16,13 @@
package org.springframework.boot.autoconfigure.web.servlet;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.WebListenerRegistrar;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.core.Ordered;
@ -37,8 +41,16 @@ public class ServletWebServerFactoryCustomizer @@ -37,8 +41,16 @@ public class ServletWebServerFactoryCustomizer
private final ServerProperties serverProperties;
private final Iterable<WebListenerRegistrar> webListenerRegistrars;
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this(serverProperties, Collections.emptyList());
}
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties,
List<WebListenerRegistrar> webListenerRegistrars) {
this.serverProperties = serverProperties;
this.webListenerRegistrars = webListenerRegistrars;
}
@Override
@ -62,6 +74,9 @@ public class ServletWebServerFactoryCustomizer @@ -62,6 +74,9 @@ public class ServletWebServerFactoryCustomizer
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
registrar.register(factory);
}
}
}

41
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java

@ -27,6 +27,7 @@ import java.time.Duration; @@ -27,6 +27,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EventListener;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -46,8 +47,11 @@ import org.eclipse.jetty.server.session.DefaultSessionCache; @@ -46,8 +47,11 @@ import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.FileSessionDataStore;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
@ -336,6 +340,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor @@ -336,6 +340,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
configurations.add(getServletContextInitializerConfiguration(webAppContext, initializers));
configurations.add(getErrorPageConfiguration());
configurations.add(getMimeTypeConfiguration());
configurations.add(new WebListenersConfiguration(getWebListenerClassNames()));
configurations.addAll(getConfigurations());
return configurations.toArray(new Configuration[0]);
}
@ -604,4 +609,40 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor @@ -604,4 +609,40 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
}
/**
* {@link AbstractConfiguration} to apply {@code @WebListener} classes.
*/
private static class WebListenersConfiguration extends AbstractConfiguration {
private final Set<String> classNames;
WebListenersConfiguration(Set<String> webListenerClassNames) {
this.classNames = webListenerClassNames;
}
@Override
public void configure(WebAppContext context) throws Exception {
ServletHandler servletHandler = context.getServletHandler();
for (String className : this.classNames) {
configure(context, servletHandler, className);
}
}
private void configure(WebAppContext context, ServletHandler servletHandler, String className)
throws ClassNotFoundException {
ListenerHolder holder = servletHandler.newListenerHolder(new Source(Source.Origin.ANNOTATION, className));
holder.setHeldClass(loadClass(context, className));
servletHandler.addListener(holder);
}
@SuppressWarnings("unchecked")
private Class<? extends EventListener> loadClass(WebAppContext context, String className)
throws ClassNotFoundException {
ClassLoader classLoader = context.getClassLoader();
classLoader = (classLoader != null) ? classLoader : getClass().getClassLoader();
return (Class<? extends EventListener>) classLoader.loadClass(className);
}
}
}

3
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java

@ -383,6 +383,9 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto @@ -383,6 +383,9 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (String webListenerClassName : getWebListenerClassNames()) {
context.addApplicationListener(webListenerClassName);
}
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}

19
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java

@ -25,6 +25,7 @@ import java.util.ArrayList; @@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@ -47,6 +48,7 @@ import io.undertow.server.session.SessionManager; @@ -47,6 +48,7 @@ import io.undertow.server.session.SessionManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ListenerInfo;
import io.undertow.servlet.api.MimeMapping;
import io.undertow.servlet.api.ServletContainerInitializerInfo;
import io.undertow.servlet.api.ServletStackTraces;
@ -330,6 +332,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac @@ -330,6 +332,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
deployment.setEagerFilterInit(this.eagerFilterInit);
deployment.setPreservePathOnForward(this.preservePathOnForward);
configureMimeMappings(deployment);
configureWebListeners(deployment);
for (UndertowDeploymentInfoCustomizer customizer : this.deploymentInfoCustomizers) {
customizer.customize(deployment);
}
@ -350,6 +353,22 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac @@ -350,6 +353,22 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
return manager;
}
private void configureWebListeners(DeploymentInfo deployment) {
for (String className : getWebListenerClassNames()) {
try {
deployment.addListener(new ListenerInfo(loadWebListenerClass(className)));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load web listener class '" + className + "'", ex);
}
}
}
@SuppressWarnings("unchecked")
private Class<? extends EventListener> loadWebListenerClass(String className) throws ClassNotFoundException {
return (Class<? extends EventListener>) getServletClassLoader().loadClass(className);
}
private boolean isZeroOrLess(Duration timeoutDuration) {
return timeoutDuration == null || timeoutDuration.isZero() || timeoutDuration.isNegative();
}

24
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -38,9 +38,25 @@ class WebListenerHandler extends ServletComponentHandler { @@ -38,9 +38,25 @@ class WebListenerHandler extends ServletComponentHandler {
@Override
protected void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServletListenerRegistrationBean.class);
builder.addPropertyValue("listener", beanDefinition);
registry.registerBeanDefinition(beanDefinition.getBeanClassName(), builder.getBeanDefinition());
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(ServletComponentWebListenerRegistrar.class);
builder.addConstructorArgValue(beanDefinition.getBeanClassName());
registry.registerBeanDefinition(beanDefinition.getBeanClassName() + "Registrar", builder.getBeanDefinition());
}
static class ServletComponentWebListenerRegistrar implements WebListenerRegistrar {
private final String listenerClassName;
ServletComponentWebListenerRegistrar(String listenerClassName) {
this.listenerClassName = listenerClassName;
}
@Override
public void register(WebListenerRegistry registry) {
registry.addWebListeners(this.listenerClassName);
}
}
}

35
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerRegistrar.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.servlet;
import javax.servlet.annotation.WebListener;
/**
* Interface to be implemented by types that register {@link WebListener @WebListeners}.
*
* @author Andy Wilkinson
* @since 2.4.0
*/
public interface WebListenerRegistrar {
/**
* Register web listeners with the given registry.
* @param registry the web listener registry
*/
void register(WebListenerRegistry registry);
}

35
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/WebListenerRegistry.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.servlet;
import javax.servlet.annotation.WebListener;
/**
* A registry that holds {@link WebListener @WebListeners}.
*
* @author Andy Wilkinson
* @since 2.4.0
*/
public interface WebListenerRegistry {
/**
* Adds web listeners that will be registered with the servlet container.
* @param webListenerClassNames the class names of the web listeners
*/
void addWebListeners(String... webListenerClassNames);
}

12
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactory.java

@ -23,6 +23,7 @@ import java.util.ArrayList; @@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@ -81,6 +82,8 @@ public abstract class AbstractServletWebServerFactory extends AbstractConfigurab @@ -81,6 +82,8 @@ public abstract class AbstractServletWebServerFactory extends AbstractConfigurab
private final StaticResourceJars staticResourceJars = new StaticResourceJars();
private final Set<String> webListenerClassNames = new HashSet<String>();
/**
* Create a new {@link AbstractServletWebServerFactory} instance.
*/
@ -283,6 +286,15 @@ public abstract class AbstractServletWebServerFactory extends AbstractConfigurab @@ -283,6 +286,15 @@ public abstract class AbstractServletWebServerFactory extends AbstractConfigurab
return this.session.getSessionStoreDirectory().getValidDirectory(mkdirs);
}
@Override
public void addWebListeners(String... webListenerClassNames) {
this.webListenerClassNames.addAll(Arrays.asList(webListenerClassNames));
}
protected final Set<String> getWebListenerClassNames() {
return this.webListenerClassNames;
}
/**
* {@link ServletContextInitializer} to apply appropriate parts of the {@link Session}
* configuration.

4
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/server/ConfigurableServletWebServerFactory.java

@ -28,6 +28,7 @@ import org.springframework.boot.web.server.ConfigurableWebServerFactory; @@ -28,6 +28,7 @@ import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.WebListenerRegistry;
/**
* A configurable {@link ServletWebServerFactory}.
@ -41,7 +42,8 @@ import org.springframework.boot.web.servlet.ServletContextInitializer; @@ -41,7 +42,8 @@ import org.springframework.boot.web.servlet.ServletContextInitializer;
* @see ServletWebServerFactory
* @see WebServerFactoryCustomizer
*/
public interface ConfigurableServletWebServerFactory extends ConfigurableWebServerFactory, ServletWebServerFactory {
public interface ConfigurableServletWebServerFactory
extends ConfigurableWebServerFactory, ServletWebServerFactory, WebListenerRegistry {
/**
* Sets the context path for the web server. The context should start with a "/"

89
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletComponentScanIntegrationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -23,6 +23,7 @@ import java.net.URL; @@ -23,6 +23,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;
import javax.servlet.MultipartConfigElement;
import javax.servlet.annotation.WebFilter;
@ -30,12 +31,19 @@ import javax.servlet.annotation.WebListener; @@ -30,12 +31,19 @@ import javax.servlet.annotation.WebListener;
import javax.servlet.annotation.WebServlet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.boot.web.servlet.testcomponents.TestMultipartServlet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -52,6 +60,9 @@ class ServletComponentScanIntegrationTests { @@ -52,6 +60,9 @@ class ServletComponentScanIntegrationTests {
private AnnotationConfigServletWebServerApplicationContext context;
@TempDir
File temp;
@AfterEach
void cleanUp() {
if (this.context != null) {
@ -59,37 +70,40 @@ class ServletComponentScanIntegrationTests { @@ -59,37 +70,40 @@ class ServletComponentScanIntegrationTests {
}
}
@Test
void componentsAreRegistered() {
@ParameterizedTest(name = "{0}")
@MethodSource("testConfiguration")
void componentsAreRegistered(String serverName, Class<?> configuration) {
this.context = new AnnotationConfigServletWebServerApplicationContext();
this.context.register(TestConfiguration.class);
this.context.register(configuration);
new ServerPortInfoApplicationContextInitializer().initialize(this.context);
this.context.refresh();
String port = this.context.getEnvironment().getProperty("local.server.port");
String response = new RestTemplate().getForObject("http://localhost:" + port + "/test", String.class);
assertThat(response).isEqualTo("alpha bravo");
assertThat(response).isEqualTo("alpha bravo charlie");
}
@Test
void indexedComponentsAreRegistered(@TempDir File temp) throws IOException {
writeIndex(temp);
@ParameterizedTest(name = "{0}")
@MethodSource("testConfiguration")
void indexedComponentsAreRegistered(String serverName, Class<?> configuration) throws IOException {
writeIndex(this.temp);
this.context = new AnnotationConfigServletWebServerApplicationContext();
try (URLClassLoader classLoader = new URLClassLoader(new URL[] { temp.toURI().toURL() },
try (URLClassLoader classLoader = new URLClassLoader(new URL[] { this.temp.toURI().toURL() },
getClass().getClassLoader())) {
this.context.setClassLoader(classLoader);
this.context.register(TestConfiguration.class);
this.context.register(configuration);
new ServerPortInfoApplicationContextInitializer().initialize(this.context);
this.context.refresh();
String port = this.context.getEnvironment().getProperty("local.server.port");
String response = new RestTemplate().getForObject("http://localhost:" + port + "/test", String.class);
assertThat(response).isEqualTo("alpha bravo");
assertThat(response).isEqualTo("alpha bravo charlie");
}
}
@Test
void multipartConfigIsHonoured() {
@ParameterizedTest(name = "{0}")
@MethodSource("testConfiguration")
void multipartConfigIsHonoured(String serverName, Class<?> configuration) {
this.context = new AnnotationConfigServletWebServerApplicationContext();
this.context.register(TestConfiguration.class);
this.context.register(configuration);
new ServerPortInfoApplicationContextInitializer().initialize(this.context);
this.context.refresh();
@SuppressWarnings("rawtypes")
@ -118,15 +132,54 @@ class ServletComponentScanIntegrationTests { @@ -118,15 +132,54 @@ class ServletComponentScanIntegrationTests {
}
}
@Configuration(proxyBeanMethods = false)
static Stream<Arguments> testConfiguration() {
return Stream.of(Arguments.of("Jetty", JettyTestConfiguration.class),
Arguments.of("Tomcat", TomcatTestConfiguration.class),
Arguments.of("Undertow", UndertowTestConfiguration.class));
}
@ServletComponentScan(basePackages = "org.springframework.boot.web.servlet.testcomponents")
static class TestConfiguration {
abstract static class AbstractTestConfiguration {
@Bean
TomcatServletWebServerFactory webServerFactory() {
protected ServletWebServerFactory webServerFactory(ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
ConfigurableServletWebServerFactory factory = createWebServerFactory();
webListenerRegistrars.orderedStream().forEach((registrar) -> registrar.register(factory));
return factory;
}
abstract ConfigurableServletWebServerFactory createWebServerFactory();
}
@Configuration(proxyBeanMethods = false)
static class JettyTestConfiguration extends AbstractTestConfiguration {
@Override
ConfigurableServletWebServerFactory createWebServerFactory() {
return new JettyServletWebServerFactory(0);
}
}
@Configuration(proxyBeanMethods = false)
static class TomcatTestConfiguration extends AbstractTestConfiguration {
@Override
ConfigurableServletWebServerFactory createWebServerFactory() {
return new TomcatServletWebServerFactory(0);
}
}
@Configuration(proxyBeanMethods = false)
static class UndertowTestConfiguration extends AbstractTestConfiguration {
@Override
ConfigurableServletWebServerFactory createWebServerFactory() {
return new UndertowServletWebServerFactory(0);
}
}
}

4
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/WebListenerHandlerTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -49,7 +49,7 @@ class WebListenerHandlerTests { @@ -49,7 +49,7 @@ class WebListenerHandlerTests {
given(definition.getMetadata()).willReturn(new SimpleMetadataReaderFactory()
.getMetadataReader(TestListener.class.getName()).getAnnotationMetadata());
this.handler.handle(definition, this.registry);
this.registry.getBeanDefinition(TestListener.class.getName());
this.registry.getBeanDefinition(TestListener.class.getName() + "Registrar");
}
@WebListener

27
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestListener.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,17 +16,27 @@ @@ -16,17 +16,27 @@
package org.springframework.boot.web.servlet.testcomponents;
import java.io.IOException;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
@WebListener
class TestListener implements ServletContextListener {
public class TestListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().addFilter("listenerAddedFilter", new ListenerAddedFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
sce.getServletContext().setAttribute("listenerAttribute", "alpha");
}
@Override
@ -34,4 +44,15 @@ class TestListener implements ServletContextListener { @@ -34,4 +44,15 @@ class TestListener implements ServletContextListener {
}
static class ListenerAddedFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setAttribute("listenerAddedFilterAttribute", "charlie");
chain.doFilter(request, response);
}
}
}

4
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/testcomponents/TestServlet.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -30,7 +30,7 @@ public class TestServlet extends HttpServlet { @@ -30,7 +30,7 @@ public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print(((String) req.getServletContext().getAttribute("listenerAttribute")) + " "
+ req.getAttribute("filterAttribute"));
+ req.getAttribute("filterAttribute") + " " + req.getAttribute("listenerAddedFilterAttribute"));
resp.getWriter().flush();
}

Loading…
Cancel
Save