From 4fee54cf24c4b8b481314a231a6e7cbe31e16d87 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 16 Jul 2018 11:45:06 -0700 Subject: [PATCH] Expose a TestDispatcherServlet bean in the MockMvcAutoConfiguration This commit also contains changes to `ServletContextInitializerBeans`. `ServletContextInitializerBeans` can now be configured to only look for specific ServletContextInitializer subclasses, defaulting to ServletContextIntializer.class. `SpringBootMockMvcBuilderCustomizer` only cares about filters so it was unnecessary to look for all `ServletContextInitializer`s. Additionally, adapting `Servlet` beans caused a cycle once the `DispatcherServlet` bean was added and the customizer only needs to adapt `Filter` beans. Closes gh-13241 --- .../web/servlet/MockMvcAutoConfiguration.java | 6 + .../SpringBootMockMvcBuilderCustomizer.java | 34 ++++- .../MockMvcAutoConfigurationTests.java | 47 ++++++ ...ringBootMockMvcBuilderCustomizerTests.java | 135 ++++++++++++++++++ .../ServletContextInitializerBeans.java | 29 ++-- .../ServletContextInitializerBeansTests.java | 45 +++++- 6 files changed, 284 insertions(+), 12 deletions(-) create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java index 28922d6a1b..b3713e0581 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java @@ -86,6 +86,12 @@ public class MockMvcAutoConfiguration { return builder.build(); } + @Bean + @ConditionalOnMissingBean + public DispatcherServlet dispatcherServlet(MockMvc mockMvc) { + return mockMvc.getDispatcherServlet(); + } + private static class MockMvcDispatcherServletCustomizer implements DispatcherServletCustomizer { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java index 2b137a2296..a0b18038b7 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java @@ -28,9 +28,11 @@ import javax.servlet.Filter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.RegistrationBean; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializerBeans; import org.springframework.context.ApplicationContext; @@ -106,8 +108,7 @@ public class SpringBootMockMvcBuilderCustomizer implements MockMvcBuilderCustomi } private void addFilters(ConfigurableMockMvcBuilder builder) { - ServletContextInitializerBeans initializers = new ServletContextInitializerBeans( - this.context); + FilterRegistrationBeans initializers = new FilterRegistrationBeans(this.context); for (ServletContextInitializer initializer : initializers) { if (initializer instanceof FilterRegistrationBean) { addFilter(builder, (FilterRegistrationBean) initializer); @@ -319,4 +320,33 @@ public class SpringBootMockMvcBuilderCustomizer implements MockMvcBuilderCustomi } + private static class FilterRegistrationBeans extends ServletContextInitializerBeans { + + FilterRegistrationBeans(ListableBeanFactory beanFactory) { + super(beanFactory, FilterRegistrationBean.class, + DelegatingFilterProxyRegistrationBean.class); + } + + @Override + protected void addAdaptableBeans(ListableBeanFactory beanFactory) { + addAsRegistrationBean(beanFactory, Filter.class, + new FilterRegistrationBeanAdapter()); + } + + private static class FilterRegistrationBeanAdapter + implements RegistrationBeanAdapter { + + @Override + public RegistrationBean createRegistrationBean(String name, Filter source, + int totalNumberOfSourceBeans) { + FilterRegistrationBean bean = new FilterRegistrationBean<>( + source); + bean.setName(name); + return bean; + } + + } + + } + } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java new file mode 100644 index 0000000000..27fcb7e551 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2018 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.test.autoconfigure.web.servlet; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.servlet.DispatcherServlet; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MockMvcAutoConfiguration}. + * + * @author Madhura Bhave + */ +public class MockMvcAutoConfigurationTests { + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MockMvcAutoConfiguration.class)); + + @Test + public void registersDispatcherServletFromMockMvc() { + this.contextRunner.run((context) -> { + MockMvc mockMvc = context.getBean(MockMvc.class); + assertThat(context).hasSingleBean(DispatcherServlet.class); + assertThat(context.getBean(DispatcherServlet.class)) + .isEqualTo(mockMvc.getDispatcherServlet()); + }); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java new file mode 100644 index 0000000000..e281e3f02c --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizerTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2018 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.test.autoconfigure.web.servlet; + +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; + +import org.junit.Test; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringBootMockMvcBuilderCustomizer}. + * + * @author Madhura Bhave + */ +public class SpringBootMockMvcBuilderCustomizerTests { + + private SpringBootMockMvcBuilderCustomizer customizer; + + @Test + @SuppressWarnings("unchecked") + public void customizeShouldAddFilters() { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + MockServletContext servletContext = new MockServletContext(); + context.setServletContext(servletContext); + context.register(ServletConfiguration.class, FilterConfiguration.class); + context.refresh(); + DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context); + this.customizer = new SpringBootMockMvcBuilderCustomizer(context); + this.customizer.customize(builder); + FilterRegistrationBean registrationBean = (FilterRegistrationBean) context + .getBean("filterRegistrationBean"); + Filter testFilter = (Filter) context.getBean("testFilter"); + Filter otherTestFilter = registrationBean.getFilter(); + List filters = (List) ReflectionTestUtils.getField(builder, + "filters"); + assertThat(filters).containsExactlyInAnyOrder(testFilter, otherTestFilter); + } + + static class ServletConfiguration { + + @Bean + public TestServlet testServlet() { + return new TestServlet(); + } + + } + + static class FilterConfiguration { + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + return new FilterRegistrationBean<>(new OtherTestFilter()); + } + + @Bean + public TestFilter testFilter() { + return new TestFilter(); + } + + } + + static class TestServlet extends HttpServlet { + + } + + static class TestFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) { + + } + + @Override + public void destroy() { + + } + + } + + static class OtherTestFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) { + + } + + @Override + public void destroy() { + + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java index c3663de2ef..ef032ed2ac 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java @@ -18,6 +18,7 @@ package org.springframework.boot.web.servlet; import java.util.AbstractCollection; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EventListener; @@ -73,10 +74,16 @@ public class ServletContextInitializerBeans private final MultiValueMap, ServletContextInitializer> initializers; + private final List> initializerTypes; + private List sortedList; - public ServletContextInitializerBeans(ListableBeanFactory beanFactory) { + public ServletContextInitializerBeans(ListableBeanFactory beanFactory, + Class... initializerTypes) { this.initializers = new LinkedMultiValueMap<>(); + this.initializerTypes = (initializerTypes.length != 0 + ? Arrays.asList(initializerTypes) + : Collections.singletonList(ServletContextInitializer.class)); addServletContextInitializerBeans(beanFactory); addAdaptableBeans(beanFactory); List sortedInitializers = this.initializers.values() @@ -88,10 +95,12 @@ public class ServletContextInitializerBeans } private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { - for (Entry initializerBean : getOrderedBeansOfType( - beanFactory, ServletContextInitializer.class)) { - addServletContextInitializerBean(initializerBean.getKey(), - initializerBean.getValue(), beanFactory); + for (Class initializerType : this.initializerTypes) { + for (Entry initializerBean : getOrderedBeansOfType( + beanFactory, initializerType)) { + addServletContextInitializerBean(initializerBean.getKey(), + initializerBean.getValue(), beanFactory); + } } } @@ -152,7 +161,7 @@ public class ServletContextInitializerBeans } @SuppressWarnings("unchecked") - private void addAdaptableBeans(ListableBeanFactory beanFactory) { + protected void addAdaptableBeans(ListableBeanFactory beanFactory) { MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory); addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig)); @@ -172,8 +181,8 @@ public class ServletContextInitializerBeans return (beans.isEmpty() ? null : beans.get(0).getValue()); } - private void addAsRegistrationBean(ListableBeanFactory beanFactory, Class type, - RegistrationBeanAdapter adapter) { + protected void addAsRegistrationBean(ListableBeanFactory beanFactory, + Class type, RegistrationBeanAdapter adapter) { addAsRegistrationBean(beanFactory, type, type, adapter); } @@ -248,8 +257,10 @@ public class ServletContextInitializerBeans /** * Adapter to convert a given Bean type into a {@link RegistrationBean} (and hence a * {@link ServletContextInitializer}). + * + * @param the type of the Bean to adapt */ - private interface RegistrationBeanAdapter { + protected interface RegistrationBeanAdapter { RegistrationBean createRegistrationBean(String name, T source, int totalNumberOfSourceBeans); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java index 9447150633..6e2b9113b4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java @@ -20,6 +20,7 @@ import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; +import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; @@ -59,7 +60,17 @@ public class ServletContextInitializerBeansTests { assertThat(initializerBeans.iterator()).hasOnlyElementsOfType(TestFilter.class); } - private void load(Class configuration) { + @Test + public void looksForInitializerBeansOfSpecifiedType() { + load(TestConfiguration.class); + ServletContextInitializerBeans initializerBeans = new ServletContextInitializerBeans( + this.context.getBeanFactory(), TestServletContextInitializer.class); + assertThat(initializerBeans.size()).isEqualTo(1); + assertThat(initializerBeans.iterator()) + .hasOnlyElementsOfType(TestServletContextInitializer.class); + } + + private void load(Class... configuration) { this.context = new AnnotationConfigApplicationContext(configuration); } @@ -81,6 +92,20 @@ public class ServletContextInitializerBeansTests { } + static class TestConfiguration { + + @Bean + public TestServletContextInitializer testServletContextInitializer() { + return new TestServletContextInitializer(); + } + + @Bean + public OtherTestServletContextInitializer otherTestServletContextInitializer() { + return new OtherTestServletContextInitializer(); + } + + } + static class TestServlet extends HttpServlet implements ServletContextInitializer { @Override @@ -115,4 +140,22 @@ public class ServletContextInitializerBeansTests { } + static class TestServletContextInitializer implements ServletContextInitializer { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + + } + + } + + static class OtherTestServletContextInitializer implements ServletContextInitializer { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + + } + + } + }