diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java
index 5f661d5b652..3db56743daf 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java
@@ -16,11 +16,19 @@
package org.springframework.web.servlet.support;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
+import org.springframework.core.Conventions;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
@@ -44,6 +52,7 @@ import org.springframework.web.servlet.DispatcherServlet;
*
* @author Arjen Poutsma
* @author Chris Beams
+ * @author Rossen Stoyanchev
* @since 3.2
*/
public abstract class AbstractDispatcherServletInitializer
@@ -87,6 +96,14 @@ public abstract class AbstractDispatcherServletInitializer
servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
+ registration.setAsyncSupported(isAsyncSupported());
+
+ Filter[] filters = getServletFilters();
+ if (!ObjectUtils.isEmpty(filters)) {
+ for (Filter filter : filters) {
+ registerServletFilter(servletContext, filter);
+ }
+ }
this.customizeRegistration(registration);
}
@@ -111,12 +128,63 @@ public abstract class AbstractDispatcherServletInitializer
protected abstract WebApplicationContext createServletApplicationContext();
/**
- * Specify the servlet mapping(s) for the {@code DispatcherServlet}, e.g. '/', '/app',
- * etc.
+ * Specify the servlet mapping(s) for the {@code DispatcherServlet}, e.g. '/', '/app', etc.
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract String[] getServletMappings();
+ /**
+ * Specify filters to add and also map to the {@code DispatcherServlet}.
+ *
+ * @return an array of filters or {@code null}
+ * @see #registerServletFilters(ServletContext, String, Filter...)
+ */
+ protected Filter[] getServletFilters() {
+ return null;
+ }
+
+ /**
+ * Add the given filter to the ServletContext and map it to the
+ * {@code DispatcherServlet} as follows:
+ *
+ *
a default filter name is chosen based on its concrete type
+ *
the {@code asyncSupported} flag is set depending on the
+ * return value of {@link #isAsyncSupported() asyncSupported}
+ *
a filter mapping is created with dispatcher types {@code REQUEST},
+ * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
+ * on the return value of {@link #isAsyncSupported() asyncSupported}
+ *
+ *
If the above defaults are not suitable or insufficient, register
+ * filters directly with the {@code ServletContext}.
+ *
+ * @param servletContext the servlet context to register filters with
+ * @param servletName the name of the servlet to map the filters to
+ * @param filters the filters to be registered
+ * @return the filter registration
+ */
+ protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
+ String filterName = Conventions.getVariableName(filter);
+ Dynamic registration = servletContext.addFilter(filterName, filter);
+ registration.setAsyncSupported(isAsyncSupported());
+ registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
+ return registration;
+ }
+
+ private EnumSet getDispatcherTypes() {
+ return isAsyncSupported() ?
+ EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
+ EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE);
+ }
+
+ /**
+ * A single place to control the {@code asyncSupported} flag for the
+ * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
+ *
The default value is "true".
+ */
+ protected boolean isAsyncSupported() {
+ return true;
+ }
+
/**
* Optionally perform further registration customization once
* {@link #registerDispatcherServlet(ServletContext)} has completed.
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java
index a6cf3bdbfc7..0b02b59ecc0 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java
@@ -16,26 +16,33 @@
package org.springframework.web.servlet.support;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import java.util.Collections;
+import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.Map;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.DispatcherServlet;
-import static org.junit.Assert.*;
-
/**
* Test case for {@link AbstractAnnotationConfigDispatcherServletInitializer}.
*
@@ -45,6 +52,8 @@ public class AnnotationConfigDispatcherServletInitializerTests {
private static final String SERVLET_NAME = "myservlet";
+ private static final String FILTER_NAME = "hiddenHttpMethodFilter";
+
private static final String ROLE_NAME = "role";
private static final String SERVLET_MAPPING = "/myservlet";
@@ -55,14 +64,21 @@ public class AnnotationConfigDispatcherServletInitializerTests {
private Map servlets;
- private Map registrations;
+ private Map servletRegistrations;
+
+ private Map filters;
+
+ private Map filterRegistrations;
+
@Before
public void setUp() throws Exception {
servletContext = new MyMockServletContext();
initializer = new MyAnnotationConfigDispatcherServletInitializer();
- servlets = new LinkedHashMap(2);
- registrations = new LinkedHashMap(2);
+ servlets = new LinkedHashMap(1);
+ servletRegistrations = new LinkedHashMap(1);
+ filters = new LinkedHashMap(1);
+ filterRegistrations = new LinkedHashMap();
}
@Test
@@ -73,29 +89,82 @@ public class AnnotationConfigDispatcherServletInitializerTests {
assertNotNull(servlets.get(SERVLET_NAME));
DispatcherServlet servlet = (DispatcherServlet) servlets.get(SERVLET_NAME);
- WebApplicationContext servletContext = servlet.getWebApplicationContext();
- ((AnnotationConfigWebApplicationContext) servletContext).refresh();
+ WebApplicationContext dispatcherServletContext = servlet.getWebApplicationContext();
+ ((AnnotationConfigWebApplicationContext) dispatcherServletContext).refresh();
- assertTrue(servletContext.containsBean("bean"));
- assertTrue(servletContext.getBean("bean") instanceof MyBean);
+ assertTrue(dispatcherServletContext.containsBean("bean"));
+ assertTrue(dispatcherServletContext.getBean("bean") instanceof MyBean);
- assertEquals(1, registrations.size());
- assertNotNull(registrations.get(SERVLET_NAME));
+ assertEquals(1, servletRegistrations.size());
+ assertNotNull(servletRegistrations.get(SERVLET_NAME));
- MockDynamic registration = registrations.get(SERVLET_NAME);
- assertEquals(Collections.singleton(SERVLET_MAPPING), registration.getMappings());
- assertEquals(1, registration.getLoadOnStartup());
- assertEquals(ROLE_NAME, registration.getRunAsRole());
+ MockServletRegistration servletRegistration = servletRegistrations.get(SERVLET_NAME);
+
+ assertEquals(Collections.singleton(SERVLET_MAPPING), servletRegistration.getMappings());
+ assertEquals(1, servletRegistration.getLoadOnStartup());
+ assertEquals(ROLE_NAME, servletRegistration.getRunAsRole());
+ assertTrue(servletRegistration.isAsyncSupported());
+
+ assertEquals(1, filterRegistrations.size());
+ assertNotNull(filterRegistrations.get(FILTER_NAME));
+
+ MockFilterRegistration filterRegistration = filterRegistrations.get(FILTER_NAME);
+
+ assertTrue(filterRegistration.isAsyncSupported());
+ assertEquals(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC),
+ filterRegistration.getMappings().get(SERVLET_NAME));
}
+ @Test
+ public void asyncSupportedFalse() throws ServletException {
+ initializer = new MyAnnotationConfigDispatcherServletInitializer() {
+ @Override
+ protected boolean isAsyncSupported() {
+ return false;
+ }
+ };
+
+ initializer.onStartup(servletContext);
+
+ MockServletRegistration servletRegistration = servletRegistrations.get(SERVLET_NAME);
+ assertFalse(servletRegistration.isAsyncSupported());
+
+ MockFilterRegistration filterRegistration = filterRegistrations.get(FILTER_NAME);
+ assertFalse(filterRegistration.isAsyncSupported());
+ assertEquals(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE),
+ filterRegistration.getMappings().get(SERVLET_NAME));
+ }
+
+ @Test
+ public void noFilters() throws ServletException {
+ initializer = new MyAnnotationConfigDispatcherServletInitializer() {
+ @Override
+ protected Filter[] getServletFilters() {
+ return null;
+ }
+ };
+
+ initializer.onStartup(servletContext);
+
+ assertEquals(0, filterRegistrations.size());
+ }
+
+
private class MyMockServletContext extends MockServletContext {
@Override
- public ServletRegistration.Dynamic addServlet(String servletName,
- Servlet servlet) {
+ public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
servlets.put(servletName, servlet);
- MockDynamic registration = new MockDynamic();
- registrations.put(servletName, registration);
+ MockServletRegistration registration = new MockServletRegistration();
+ servletRegistrations.put(servletName, registration);
+ return registration;
+ }
+
+ @Override
+ public Dynamic addFilter(String filterName, Filter filter) {
+ filters.put(filterName, filter);
+ MockFilterRegistration registration = new MockFilterRegistration();
+ filterRegistrations.put(filterName, registration);
return registration;
}
}
@@ -118,6 +187,11 @@ public class AnnotationConfigDispatcherServletInitializerTests {
return new String[]{"/myservlet"};
}
+ @Override
+ protected Filter[] getServletFilters() {
+ return new Filter[] { new HiddenHttpMethodFilter() };
+ }
+
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setRunAsRole("role");
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java
index 9fcb14d5244..473c09b97ec 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java
@@ -51,14 +51,14 @@ public class DispatcherServletInitializerTests {
private Map servlets;
- private Map registrations;
+ private Map registrations;
@Before
public void setUp() throws Exception {
servletContext = new MyMockServletContext();
initializer = new MyDispatcherServletInitializer();
servlets = new LinkedHashMap(2);
- registrations = new LinkedHashMap(2);
+ registrations = new LinkedHashMap(2);
}
@Test
@@ -77,7 +77,7 @@ public class DispatcherServletInitializerTests {
assertEquals(1, registrations.size());
assertNotNull(registrations.get(SERVLET_NAME));
- MockDynamic registration = registrations.get(SERVLET_NAME);
+ MockServletRegistration registration = registrations.get(SERVLET_NAME);
assertEquals(Collections.singleton(SERVLET_MAPPING), registration.getMappings());
assertEquals(1, registration.getLoadOnStartup());
assertEquals(ROLE_NAME, registration.getRunAsRole());
@@ -89,7 +89,7 @@ public class DispatcherServletInitializerTests {
public ServletRegistration.Dynamic addServlet(String servletName,
Servlet servlet) {
servlets.put(servletName, servlet);
- MockDynamic registration = new MockDynamic();
+ MockServletRegistration registration = new MockServletRegistration();
registrations.put(servletName, registration);
return registration;
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockFilterRegistration.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockFilterRegistration.java
new file mode 100644
index 00000000000..92b5d65487a
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockFilterRegistration.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2012 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.web.servlet.support;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration.Dynamic;
+
+class MockFilterRegistration implements Dynamic {
+
+ private boolean asyncSupported = false;
+
+ private Map> mappings = new HashMap>();
+
+
+ public Map> getMappings() {
+ return this.mappings;
+ }
+
+ public boolean isAsyncSupported() {
+ return this.asyncSupported;
+ }
+
+ public void setAsyncSupported(boolean isAsyncSupported) {
+ this.asyncSupported = isAsyncSupported;
+ }
+
+ public void addMappingForServletNames(EnumSet dispatcherTypes,
+ boolean isMatchAfter, String... servletNames) {
+
+ for (String servletName : servletNames) {
+ this.mappings.put(servletName, dispatcherTypes);
+ }
+ }
+
+ // Not implemented
+
+ public String getName() {
+ return null;
+ }
+
+ public Collection getServletNameMappings() {
+ return null;
+ }
+
+ public void addMappingForUrlPatterns(EnumSet dispatcherTypes,
+ boolean isMatchAfter, String... urlPatterns) {
+ }
+
+ public Collection getUrlPatternMappings() {
+ return null;
+ }
+
+ public String getClassName() {
+ return null;
+ }
+
+ public boolean setInitParameter(String name, String value) {
+ return false;
+ }
+
+ public String getInitParameter(String name) {
+ return null;
+ }
+
+ public Set setInitParameters(Map initParameters) {
+ return null;
+ }
+
+ public Map getInitParameters() {
+ return null;
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockServletRegistration.java
similarity index 90%
rename from spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java
rename to spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockServletRegistration.java
index cb43a1228b8..2b0e15a4db5 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockDynamic.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/MockServletRegistration.java
@@ -26,7 +26,7 @@ import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletSecurityElement;
-class MockDynamic implements ServletRegistration.Dynamic {
+class MockServletRegistration implements ServletRegistration.Dynamic {
private int loadOnStartup;
@@ -34,6 +34,8 @@ class MockDynamic implements ServletRegistration.Dynamic {
private String roleName;
+ private boolean asyncSupported = false;
+
public int getLoadOnStartup() {
return loadOnStartup;
}
@@ -59,10 +61,16 @@ class MockDynamic implements ServletRegistration.Dynamic {
return roleName;
}
- // not implemented
public void setAsyncSupported(boolean isAsyncSupported) {
+ this.asyncSupported = isAsyncSupported;
}
+ public boolean isAsyncSupported() {
+ return this.asyncSupported;
+ }
+
+ // not implemented
+
public String getName() {
return null;
}
diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml
index 56b2e1671ce..98d681cc5b8 100644
--- a/src/reference/docbook/mvc.xml
+++ b/src/reference/docbook/mvc.xml
@@ -272,7 +272,33 @@
In the preceding example, all requests starting with
/example will be handled by the
DispatcherServlet instance named
- example. This is only the first step in setting up
+ example.
+ In a Servlet 3.0+ environment, you also have the
+ option of configuring the Servlet container programmatically. Below is the code
+ based equivalent of the above web.xml example:
+
+ public class MyWebApplicationInitializer implements WebApplicationInitializer {
+
+ @Override
+ public void onStartup(ServletContext container) {
+ ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
+ registration.setLoadOnStartup(1);
+ registration.addMapping("/example/*");
+ }
+
+}
+
+ WebApplicationInitializer is an interface
+ provided by Spring MVC that ensures your code-based configuration is detected and
+ automatically used to initialize any Servlet 3 container. An abstract base class
+ implementation of this interace named
+ AbstractDispatcherServletInitializer makes it even easier
+ to register the DispatcherServlet by simply specifying
+ its servlet mapping. See
+ Code-based Servlet container initialization
+ for more details.
+
+ The above is only the first step in setting up
Spring Web MVC. You
now need to configure the various beans used by the Spring Web MVC
framework (over and above the DispatcherServlet
@@ -4394,6 +4420,108 @@ public class ErrorController {
</filter-mapping>
+
+ Code-based Servlet container initialization
+
+ In a Servlet 3.0+ environment, you have the option of configuring the
+ Servlet container programmatically as an alternative or in combination with
+ a web.xml file.
+ Below is an example of registering a DispatcherServlet:
+
+ import org.springframework.web.WebApplicationInitializer;
+
+public class MyWebApplicationInitializer implements WebApplicationInitializer {
+
+ @Override
+ public void onStartup(ServletContext container) {
+ XmlWebApplicationContext appContext = new XmlWebApplicationContext();
+ appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
+
+ ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
+ registration.setLoadOnStartup(1);
+ registration.addMapping("/");
+ }
+
+}
+
+ WebApplicationInitializer is an interface
+ provided by Spring MVC that ensures your implementation is detected and
+ automatically used to initialize any Servlet 3 container. An abstract base
+ class implementation of WebApplicationInitializer named
+ AbstractDispatcherServletInitializer makes it even
+ easier to register the DispatcherServlet by simply overriding
+ methods to specify the servlet mapping and the location of the
+ DispatcherServlet configuration:
+
+ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
+
+ @Override
+ protected Class<?>[] getRootConfigClasses() {
+ return null;
+ }
+
+ @Override
+ protected Class<?>[] getServletConfigClasses() {
+ return new Class[] { MyWebConfig.class };
+ }
+
+ @Override
+ protected String[] getServletMappings() {
+ return new String[] { "/" };
+ }
+
+}
+
+ The above example is for an application that uses Java-based Spring
+ configuration. If using XML-based Spring configuration, extend directly from
+ AbstractDispatcherServletInitializer:
+
+ public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
+
+ @Override
+ protected WebApplicationContext createRootApplicationContext() {
+ return null;
+ }
+
+ @Override
+ protected WebApplicationContext createServletApplicationContext() {
+ XmlWebApplicationContext cxt = new XmlWebApplicationContext();
+ cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
+ return cxt;
+ }
+
+ @Override
+ protected String[] getServletMappings() {
+ return new String[] { "/" };
+ }
+
+}
+
+ AbstractDispatcherServletInitializer also provides
+ a convenient way to add Filter instances
+ and have them automatically mapped to the DispatcherServlet:
+
+ public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
+
+ // ...
+
+ @Override
+ protected Filter[] getServletFilters() {
+ return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
+ }
+
+}
+
+ Each filter is added with a default name based on its concrete type and
+ automatically mapped to the DispatcherServlet.
+
+ The isAsyncSupported protected method of
+ AbstractDispatcherServletInitializer provides
+ a single place to enable async support on the DispatcherServlet
+ and all filters mapped to it. By default this flag is set to true.
+
+
+
Configuring Spring MVC
diff --git a/src/reference/docbook/new-in-3.2.xml b/src/reference/docbook/new-in-3.2.xml
index 98bce34e744..e90ed8595a4 100644
--- a/src/reference/docbook/new-in-3.2.xml
+++ b/src/reference/docbook/new-in-3.2.xml
@@ -66,7 +66,7 @@
- New @ControllerAdvice annotation
+ @ControllerAdvice annotationClasses annotated with @ControllerAdvice
can contain @ExceptionHandler,
@@ -88,8 +88,21 @@
For more details see .
+
+ Abstract base class for code-based Servlet 3+ container initialization
+
+ An abstract base class implementation of the
+ WebApplicationInitializer interface is provided to
+ simplify code-based registration of a DispatcherServlet and filters
+ mapped to it. The new class is named
+ AbstractDispatcherServletInitializer and its
+ sub-class AbstractAnnotationConfigDispatcherServletInitializer
+ can be used with Java-based Spring configuration.
+ For more details see .
+
+
- New ResponseEntityExceptionHandler class
+ ResponseEntityExceptionHandler classA convenient base class with an
@ExceptionHandler