Skip to content

Commit 4fee54c

Browse files
committed
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
1 parent 6a189ce commit 4fee54c

File tree

6 files changed

+284
-12
lines changed

6 files changed

+284
-12
lines changed

spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ public MockMvc mockMvc(MockMvcBuilder builder) {
8686
return builder.build();
8787
}
8888

89+
@Bean
90+
@ConditionalOnMissingBean
91+
public DispatcherServlet dispatcherServlet(MockMvc mockMvc) {
92+
return mockMvc.getDispatcherServlet();
93+
}
94+
8995
private static class MockMvcDispatcherServletCustomizer
9096
implements DispatcherServletCustomizer {
9197

spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java

+32-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import org.apache.commons.logging.Log;
2929
import org.apache.commons.logging.LogFactory;
3030

31+
import org.springframework.beans.factory.ListableBeanFactory;
3132
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3233
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
3334
import org.springframework.boot.web.servlet.FilterRegistrationBean;
35+
import org.springframework.boot.web.servlet.RegistrationBean;
3436
import org.springframework.boot.web.servlet.ServletContextInitializer;
3537
import org.springframework.boot.web.servlet.ServletContextInitializerBeans;
3638
import org.springframework.context.ApplicationContext;
@@ -106,8 +108,7 @@ private LinesWriter getLinesWriter() {
106108
}
107109

108110
private void addFilters(ConfigurableMockMvcBuilder<?> builder) {
109-
ServletContextInitializerBeans initializers = new ServletContextInitializerBeans(
110-
this.context);
111+
FilterRegistrationBeans initializers = new FilterRegistrationBeans(this.context);
111112
for (ServletContextInitializer initializer : initializers) {
112113
if (initializer instanceof FilterRegistrationBean) {
113114
addFilter(builder, (FilterRegistrationBean<?>) initializer);
@@ -319,4 +320,33 @@ private PrintStream getPrintStream() {
319320

320321
}
321322

323+
private static class FilterRegistrationBeans extends ServletContextInitializerBeans {
324+
325+
FilterRegistrationBeans(ListableBeanFactory beanFactory) {
326+
super(beanFactory, FilterRegistrationBean.class,
327+
DelegatingFilterProxyRegistrationBean.class);
328+
}
329+
330+
@Override
331+
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
332+
addAsRegistrationBean(beanFactory, Filter.class,
333+
new FilterRegistrationBeanAdapter());
334+
}
335+
336+
private static class FilterRegistrationBeanAdapter
337+
implements RegistrationBeanAdapter<Filter> {
338+
339+
@Override
340+
public RegistrationBean createRegistrationBean(String name, Filter source,
341+
int totalNumberOfSourceBeans) {
342+
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(
343+
source);
344+
bean.setName(name);
345+
return bean;
346+
}
347+
348+
}
349+
350+
}
351+
322352
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.boot.test.autoconfigure.web.servlet;
17+
18+
import org.junit.Test;
19+
20+
import org.springframework.boot.autoconfigure.AutoConfigurations;
21+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
22+
import org.springframework.test.web.servlet.MockMvc;
23+
import org.springframework.web.servlet.DispatcherServlet;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* Tests for {@link MockMvcAutoConfiguration}.
29+
*
30+
* @author Madhura Bhave
31+
*/
32+
public class MockMvcAutoConfigurationTests {
33+
34+
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
35+
.withConfiguration(AutoConfigurations.of(MockMvcAutoConfiguration.class));
36+
37+
@Test
38+
public void registersDispatcherServletFromMockMvc() {
39+
this.contextRunner.run((context) -> {
40+
MockMvc mockMvc = context.getBean(MockMvc.class);
41+
assertThat(context).hasSingleBean(DispatcherServlet.class);
42+
assertThat(context.getBean(DispatcherServlet.class))
43+
.isEqualTo(mockMvc.getDispatcherServlet());
44+
});
45+
}
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.boot.test.autoconfigure.web.servlet;
17+
18+
import java.util.List;
19+
20+
import javax.servlet.Filter;
21+
import javax.servlet.FilterChain;
22+
import javax.servlet.FilterConfig;
23+
import javax.servlet.ServletRequest;
24+
import javax.servlet.ServletResponse;
25+
import javax.servlet.http.HttpServlet;
26+
27+
import org.junit.Test;
28+
29+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.mock.web.MockServletContext;
32+
import org.springframework.test.util.ReflectionTestUtils;
33+
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
34+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
35+
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Tests for {@link SpringBootMockMvcBuilderCustomizer}.
41+
*
42+
* @author Madhura Bhave
43+
*/
44+
public class SpringBootMockMvcBuilderCustomizerTests {
45+
46+
private SpringBootMockMvcBuilderCustomizer customizer;
47+
48+
@Test
49+
@SuppressWarnings("unchecked")
50+
public void customizeShouldAddFilters() {
51+
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
52+
MockServletContext servletContext = new MockServletContext();
53+
context.setServletContext(servletContext);
54+
context.register(ServletConfiguration.class, FilterConfiguration.class);
55+
context.refresh();
56+
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context);
57+
this.customizer = new SpringBootMockMvcBuilderCustomizer(context);
58+
this.customizer.customize(builder);
59+
FilterRegistrationBean registrationBean = (FilterRegistrationBean) context
60+
.getBean("filterRegistrationBean");
61+
Filter testFilter = (Filter) context.getBean("testFilter");
62+
Filter otherTestFilter = registrationBean.getFilter();
63+
List<Filter> filters = (List<Filter>) ReflectionTestUtils.getField(builder,
64+
"filters");
65+
assertThat(filters).containsExactlyInAnyOrder(testFilter, otherTestFilter);
66+
}
67+
68+
static class ServletConfiguration {
69+
70+
@Bean
71+
public TestServlet testServlet() {
72+
return new TestServlet();
73+
}
74+
75+
}
76+
77+
static class FilterConfiguration {
78+
79+
@Bean
80+
public FilterRegistrationBean<OtherTestFilter> filterRegistrationBean() {
81+
return new FilterRegistrationBean<>(new OtherTestFilter());
82+
}
83+
84+
@Bean
85+
public TestFilter testFilter() {
86+
return new TestFilter();
87+
}
88+
89+
}
90+
91+
static class TestServlet extends HttpServlet {
92+
93+
}
94+
95+
static class TestFilter implements Filter {
96+
97+
@Override
98+
public void init(FilterConfig filterConfig) {
99+
100+
}
101+
102+
@Override
103+
public void doFilter(ServletRequest request, ServletResponse response,
104+
FilterChain chain) {
105+
106+
}
107+
108+
@Override
109+
public void destroy() {
110+
111+
}
112+
113+
}
114+
115+
static class OtherTestFilter implements Filter {
116+
117+
@Override
118+
public void init(FilterConfig filterConfig) {
119+
120+
}
121+
122+
@Override
123+
public void doFilter(ServletRequest request, ServletResponse response,
124+
FilterChain chain) {
125+
126+
}
127+
128+
@Override
129+
public void destroy() {
130+
131+
}
132+
133+
}
134+
135+
}

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

+20-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.AbstractCollection;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.Collections;
2223
import java.util.Comparator;
2324
import java.util.EventListener;
@@ -73,10 +74,16 @@ public class ServletContextInitializerBeans
7374

7475
private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
7576

77+
private final List<Class<? extends ServletContextInitializer>> initializerTypes;
78+
7679
private List<ServletContextInitializer> sortedList;
7780

78-
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
81+
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
82+
Class<? extends ServletContextInitializer>... initializerTypes) {
7983
this.initializers = new LinkedMultiValueMap<>();
84+
this.initializerTypes = (initializerTypes.length != 0
85+
? Arrays.asList(initializerTypes)
86+
: Collections.singletonList(ServletContextInitializer.class));
8087
addServletContextInitializerBeans(beanFactory);
8188
addAdaptableBeans(beanFactory);
8289
List<ServletContextInitializer> sortedInitializers = this.initializers.values()
@@ -88,10 +95,12 @@ public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
8895
}
8996

9097
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
91-
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
92-
beanFactory, ServletContextInitializer.class)) {
93-
addServletContextInitializerBean(initializerBean.getKey(),
94-
initializerBean.getValue(), beanFactory);
98+
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
99+
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(
100+
beanFactory, initializerType)) {
101+
addServletContextInitializerBean(initializerBean.getKey(),
102+
initializerBean.getValue(), beanFactory);
103+
}
95104
}
96105
}
97106

@@ -152,7 +161,7 @@ private String getResourceDescription(String beanName,
152161
}
153162

154163
@SuppressWarnings("unchecked")
155-
private void addAdaptableBeans(ListableBeanFactory beanFactory) {
164+
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
156165
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
157166
addAsRegistrationBean(beanFactory, Servlet.class,
158167
new ServletRegistrationBeanAdapter(multipartConfig));
@@ -172,8 +181,8 @@ private MultipartConfigElement getMultipartConfig(ListableBeanFactory beanFactor
172181
return (beans.isEmpty() ? null : beans.get(0).getValue());
173182
}
174183

175-
private <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
176-
RegistrationBeanAdapter<T> adapter) {
184+
protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory,
185+
Class<T> type, RegistrationBeanAdapter<T> adapter) {
177186
addAsRegistrationBean(beanFactory, type, type, adapter);
178187
}
179188

@@ -248,8 +257,10 @@ public int size() {
248257
/**
249258
* Adapter to convert a given Bean type into a {@link RegistrationBean} (and hence a
250259
* {@link ServletContextInitializer}).
260+
*
261+
* @param <T> the type of the Bean to adapt
251262
*/
252-
private interface RegistrationBeanAdapter<T> {
263+
protected interface RegistrationBeanAdapter<T> {
253264

254265
RegistrationBean createRegistrationBean(String name, T source,
255266
int totalNumberOfSourceBeans);

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

+44-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import javax.servlet.FilterChain;
2121
import javax.servlet.FilterConfig;
2222
import javax.servlet.ServletContext;
23+
import javax.servlet.ServletException;
2324
import javax.servlet.ServletRequest;
2425
import javax.servlet.ServletResponse;
2526
import javax.servlet.http.HttpServlet;
@@ -59,7 +60,17 @@ public void filterThatImplementsServletContextInitializerIsOnlyRegisteredOnce()
5960
assertThat(initializerBeans.iterator()).hasOnlyElementsOfType(TestFilter.class);
6061
}
6162

62-
private void load(Class<?> configuration) {
63+
@Test
64+
public void looksForInitializerBeansOfSpecifiedType() {
65+
load(TestConfiguration.class);
66+
ServletContextInitializerBeans initializerBeans = new ServletContextInitializerBeans(
67+
this.context.getBeanFactory(), TestServletContextInitializer.class);
68+
assertThat(initializerBeans.size()).isEqualTo(1);
69+
assertThat(initializerBeans.iterator())
70+
.hasOnlyElementsOfType(TestServletContextInitializer.class);
71+
}
72+
73+
private void load(Class<?>... configuration) {
6374
this.context = new AnnotationConfigApplicationContext(configuration);
6475
}
6576

@@ -81,6 +92,20 @@ public TestFilter testFilter() {
8192

8293
}
8394

95+
static class TestConfiguration {
96+
97+
@Bean
98+
public TestServletContextInitializer testServletContextInitializer() {
99+
return new TestServletContextInitializer();
100+
}
101+
102+
@Bean
103+
public OtherTestServletContextInitializer otherTestServletContextInitializer() {
104+
return new OtherTestServletContextInitializer();
105+
}
106+
107+
}
108+
84109
static class TestServlet extends HttpServlet implements ServletContextInitializer {
85110

86111
@Override
@@ -115,4 +140,22 @@ public void destroy() {
115140

116141
}
117142

143+
static class TestServletContextInitializer implements ServletContextInitializer {
144+
145+
@Override
146+
public void onStartup(ServletContext servletContext) throws ServletException {
147+
148+
}
149+
150+
}
151+
152+
static class OtherTestServletContextInitializer implements ServletContextInitializer {
153+
154+
@Override
155+
public void onStartup(ServletContext servletContext) throws ServletException {
156+
157+
}
158+
159+
}
160+
118161
}

0 commit comments

Comments
 (0)