From c9e67b2ef25ff1f5de32e4fc75a36f076fd14f35 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Mon, 30 May 2011 12:53:32 +0000 Subject: [PATCH] Introduce Servlet 3.0 WebApplicationInitializer WebApplicationInitializer provides a programmatic alternative to the traditional WEB-INF/web.xml servlet container deployment descriptor for Servlet API 3.0+ environments. This is done by building atop the new ServletContainerInitializer support in Servlet 3.0. See SpringServletContainerInitializer for complete details. And see WebApplicationInitializer Javadoc for typical usage examples. Issue: SPR-7672 --- .../SpringServletContainerInitializer.java | 165 ++++++++++++++++ .../web/WebApplicationInitializer.java | 183 ++++++++++++++++++ ...AnnotationConfigWebApplicationContext.java | 9 +- .../javax.servlet.ServletContainerInitializer | 1 + 4 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 org.springframework.web/src/main/java/org/springframework/web/SpringServletContainerInitializer.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/WebApplicationInitializer.java create mode 100644 org.springframework.web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer diff --git a/org.springframework.web/src/main/java/org/springframework/web/SpringServletContainerInitializer.java b/org.springframework.web/src/main/java/org/springframework/web/SpringServletContainerInitializer.java new file mode 100644 index 00000000000..5ea1ebfb1e3 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/SpringServletContainerInitializer.java @@ -0,0 +1,165 @@ +/* + * Copyright 2002-2011 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; + +import static org.springframework.beans.BeanUtils.instantiateClass; + +import java.lang.reflect.Modifier; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TreeSet; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.HandlesTypes; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based + * configuration of the servlet container using Spring's {@link WebApplicationInitializer} + * SPI as opposed to (or possibly in combination with) the traditional + * {@code web.xml}-based approach. + * + *

Mechanism of Operation

+ * This class will be loaded and instantiated and have its {@link #onStartup} method + * invoked by any Servlet 3.0-compliant container during container startup assuming that + * the {@code spring-web} module JAR is present on the classpath. This occurs through the + * JAR Services API {@link ServiceLoader#load(Class)} method detecting the + * {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer} + * service provider configuration file. See the + * + * JAR Services API documentation as well as section 8.2.4 of the Servlet 3.0 + * Final Draft specification for complete details. + * + *

when in combination with {@code web.xml}

+ *

If a web application does include a {@code WEB-INF/web.xml} file, it is important to + * understand that neither this nor any other {@code ServletContextInitializer} will be + * processed unless the {@code } element's {@code version} attribute is >= "3.0" + * and the {@code xsi:schemaLocation} for "http://java.sun.com/xml/ns/javaee" is set to + * "http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd". + * + *

Relationship to Spring's {@code WebApplicationInitializer}

+ * Spring's {@code WebApplicationInitializer} SPI consists of just one method: + * {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally + * quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}: + * simply put, {@code SpringServletContainerInitializer} is responsible for instantiating + * and delegating the {@code ServletContext} to any user-defined + * {@code WebApplicationInitializer} implementations. It is then the responsibility of + * each {@code WebApplicationInitializer} to do the actual work of initializing the + * {@code ServletContext}. The exact process of delegation is described in detail in the + * {@link #onStartup} documentation below. + * + *

General Notes

+ * In general, this class should be viewed as supporting infrastructure for + * the more important and user-facing {@code WebApplicationInitializer} SPI. Taking + * advantage of this container initializer is also completely optional: while + * it is true that this initializer will be loaded and invoked under all Servlet 3.0+ + * runtimes, it remains the user's choice whether to make any + * {@code WebApplicationInitializer} implementations available on the classpath. If no + * {@code WebApplicationInitializer} types are detected, this container initializer will + * have no effect. + * + *

Note that use of this container initializer and of {@code WebApplicationInitializer} + * is not in any way "tied" to Spring MVC other than the fact that the types are shipped + * in the {@code spring-web} module JAR. Rather, they can be considered general-purpose + * in their ability to facilitate convenient code-based configuration of the + * {@code ServletContext}. Said another way, any servlet, listener, or filter may be + * registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific + * components. + * + *

This class is not designed for nor intended to be extended. It should be considered + * an internal type, with {@code WebApplicationInitializer} being the public-facing SPI. + * + *

See Also

+ * See {@link WebApplicationInitializer} Javadoc for examples and detailed usage + * recommendations.

+ * + * @author Chris Beams + * @since 3.1 + * @see #onStartup(Set, ServletContext) + * @see WebApplicationInitializer + */ +@HandlesTypes(WebApplicationInitializer.class) +public class SpringServletContainerInitializer implements ServletContainerInitializer { + + private static final Log logger = LogFactory.getLog(SpringServletContainerInitializer.class); + + /** + * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} + * implementations present on the application classpath. + * + *

Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, + * Servlet 3.0+ containers will automatically scan the classpath for implementations + * of Spring's {@code WebApplicationInitializer} interface and provide the set of all + * such types to the {@code webAppInitializerClasses} parameter of this method. + * + *

If no {@code WebApplicationInitializer} implementations are found on the + * classpath, this method is effectively a no-op. An INFO-level log message will be + * issued notifying the user that the {@code ServletContainerInitializer} has indeed + * been invoked, but that no {@code WebApplicationInitializer} implementations were + * found. + * + *

Assuming that one or more {@code WebApplicationInitializer} types are detected, + * they will be instantiated (and sorted if the @{@link + * org.springframework.core.annotation.Order Order} annotation is present or + * the {@link org.springframework.core.Ordered Ordered} interface has been + * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} + * method will be invoked on each instance, delegating the {@code ServletContext} such + * that each instance may register and configure servlets such as Spring's + * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener} + * or any other Servlet API componentry such as filters. + * + * @param webAppInitializerClasses all implementations of + * {@link WebApplicationInitializer} found on the application classpath. + * @param servletContext the servlet context to be initialized + * @see WebApplicationInitializer#onStartup(ServletContext) + * @see AnnotationAwareOrderComparator + */ + public void onStartup(Set> webAppInitializerClasses, + ServletContext servletContext) throws ServletException { + + Set initializers = + new TreeSet(new AnnotationAwareOrderComparator()); + + for (Class waiClass : webAppInitializerClasses) { + if (Modifier.isAbstract(waiClass.getModifiers())) { + // the class is not instantiable (i.e. abstract or an interface) -> skip it + continue; + } + + initializers.add(instantiateClass(waiClass, WebApplicationInitializer.class)); + } + + if (initializers.isEmpty()) { + logger.info("Detected no WebApplicationInitializer types on the classpath: exiting."); + return; + } + + logger.info("Delegating ServletContext to the following " + + "WebApplicationInitializer instances: " + initializers); + + for (WebApplicationInitializer initializer : initializers) { + initializer.onStartup(servletContext); + } + + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/WebApplicationInitializer.java b/org.springframework.web/src/main/java/org/springframework/web/WebApplicationInitializer.java new file mode 100644 index 00000000000..2db7cde143f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/WebApplicationInitializer.java @@ -0,0 +1,183 @@ +/* + * Copyright 2002-2011 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; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +/** + * Interface to be implemented in Servlet 3.0+ environments in order to configure the + * {@link ServletContext} programmatically -- as opposed to (or possibly in conjunction + * with) the traditional {@code web.xml}-based approach. + * + *

Implementations of this SPI will be detected automatically by the + * {@link SpringServletContainerInitializer}, which itself is automatically bootstrapped + * by Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its Javadoc} + * for details on this bootstrapping mechanism. + * + *

Example

+ *

The traditional, XML-based approach

+ * Most Spring users building a web application will need to register Spring's {@code + * DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as + * follows: + *
+ * {@code
+ * 
+ *   dispatcher
+ *   
+ *     org.springframework.web.servlet.DispatcherServlet
+ *   
+ *   
+ *     contextConfigLocation
+ *     /WEB-INF/spring/dispatcher-config.xml
+ *   
+ *   1
+ * 
+ *
+ * 
+ *   dispatcher
+ *   /main
+ * }
+ * + *

The code-based approach with {@code WebApplicationInitializer}

+ * Here is the equivalent {@code DispatcherServlet} registration logic, + * {@code WebApplicationInitializer}-style: + *
+ * public class MyWebAppInitializer implements WebApplicationInitializer {
+ *
+ *    @Override
+ *    public void onStartup(ServletContext container) {
+ *      XmlWebApplicationContext appContext = new XmlWebApplicationContext()
+ *      appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
+ *
+ *      ServletRegistration.Dynamic dispatcher =
+ *        container.addServlet("dispatcher", new DispatcherServlet(appContext));
+ *      dispatcher.setLoadOnStartup(1);
+ *      dispatcher.addMapping("/main");
+ *    }
+ *
+ * }
+ * + * As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method + * we're actually registering an instance of the {@code DispatcherServlet}, and + * this means that the {@code DispatcherServlet} can now be treated like any other object + * -- receiving constructor injection of its application context in this case. + * + *

This style is both simpler and more concise. There is no concern for dealing with + * init-params, etc, just normal JavaBean-style properties and constructor arguments. You + * are free to create and work with your Spring application contexts as necessary before + * injecting them into the {@code DispatcherServlet}. + * + *

Most major Spring Web componentry has been updated to support this style of + * registration. You'll find that {@code DispatcherServlet}, {@code FrameworkServlet}, + * {@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support + * constructor arguments. Even if a component (e.g. non-Spring, other third party) has not + * been specifically updated for use within {@code WebApplicationInitializers}, they still + * may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting + * init-params, context-params, etc programmatically. + * + *

A 100% code-based approach to configuration

+ * In the example above, {@code WEB-INF/web.xml} was successfully replaced with code in + * the form of a {@code WebApplicationInitializer}, but the actual + * {@code dispatcher-config.xml} Spring configuration remained XML-based. + * {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based + * {@code @Configuration} classes. See @{@link + * org.springframework.context.annotation.Configuration Configuration} Javadoc for + * complete details, but the following example demonstrates refactoring to use Spring's + * {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext + * AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and + * user-defined {@code @Configuration} classes {@code AppConfig} and + * {@code DispatcherConfig} instead of Spring XML files. This example also goes a bit + * beyond those above to demonstrate typical configuration of the 'root' application + * context and registration of the {@code ContextLoaderListener}: + *
+ * public class MyWebAppInitializer implements WebApplicationInitializer {
+ *
+ *    @Override
+ *    public void onStartup(ServletContext container) {
+ *      // Create the 'root' Spring application context
+ *      AnnotationConfigWebApplicationContext rootContext =
+ *        new AnnotationConfigWebApplicationContext();
+ *      rootContext.register(AppConfig.class);
+ *
+ *      // Manage the lifecycle of the root application context
+ *      container.addListener(new ContextLoaderListener(rootContext));
+ *
+ *      // Create the dispatcher servlet's Spring application context
+ *      AnnotationConfigWebApplicationContext dispatcherContext =
+ *        new AnnotationConfigWebApplicationContext();
+ *      dispatcherContext.register(DispatcherConfig.class);
+ *
+ *      // Register and map the dispatcher servlet
+ *      ServletRegistration.Dynamic dispatcher =
+ *        container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
+ *      dispatcher.setLoadOnStartup(1);
+ *      dispatcher.addMapping("/main");
+ *    }
+ *
+ * }
+ * + * Remember that {@code WebApplicationInitializer} implementations are detected + * automatically -- so you are free to package them within your application as you + * see fit. + * + *

Ordering {@code WebApplicationInitializer} execution

+ * {@code WebApplicationInitializer} implementations may optionally be annotated at the + * class level with Spring's @{@link org.springframework.core.annotation.Order Order} + * annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered} + * interface. If so, the initializers will be ordered prior to invocation. This provides + * a mechanism for users to ensure the order in which servlet container initialization + * occurs. Use of this feature is expected to be rare, as typical applications will likely + * centralize all container initialization within a single {@code WebApplicationInitializer}. + * + *

Caveats

+ * + *

web.xml versioning

+ *

{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually + * exclusive; for example, web.xml can register one servlet, and a {@code + * WebApplicationInitializer} can register another. An initializer can even + * modify registrations performed in {@code web.xml} through the + * {@link ServletContext#getServletRegistration(String)} method and its similiars. + * However, if {@code WEB-INF/web.xml} is present in the application, its + * {@code version} attribute must be set to "3.0" or greater, otherwise {@code + * ServletContainerInitializer} bootstrapping will be ignored by the servlet container. + * + * + *

Mapping to '/' under Tomcat

+ *

Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions + * <= 7.0.14, this servlet mapping cannot be overridden programmatically. This + * is a known issue + * and scheduled to be addressed in the next revision of Tomcat. Overriding the "/" + * servlet mapping has been tested successfully on GlassFish 3.1.

+ * + * @author Chris Beams + * @since 3.1 + * @see SpringServletContainerInitializer + */ +public interface WebApplicationInitializer { + + /** + * Configure the given {@link ServletContext} with any servlets, filters, listeners + * context-params and attributes necessary for initializing this web application. See + * examples {@linkplain WebApplicationInitializer above}. + * @param servletContext the {@code ServletContext} to initialize + * @throws ServletException if any call against the given {@code ServletContext} + * throws a {@code ServletException} + */ + void onStartup(ServletContext servletContext) throws ServletException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 2b0a61d5150..c8ecaafbd46 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -37,14 +37,19 @@ import org.springframework.web.context.ContextLoader; * as for classpath scanning (specifying base packages as config location). * *

This is essentially the equivalent of - * {@link org.springframework.context.annotation.AnnotationConfigApplicationContext} - * for a web environment. + * {@link org.springframework.context.annotation.AnnotationConfigApplicationContext + * AnnotationConfigApplicationContext} for a web environment. * *

To make use of this application context, the * {@linkplain ContextLoader#CONTEXT_CLASS_PARAM "contextClass"} context-param for * ContextLoader and/or "contextClass" init-param for FrameworkServlet must be set to * the fully-qualified name of this class. * + *

As of Spring 3.1, this class may also be directly instantiated and injected into + * Spring's {@code DispatcherServlet} or {@code ContextLoaderListener} when using the + * new {@link org.springframework.web.WebApplicationInitializer WebApplicationInitializer} + * code-based alternative to {@code web.xml}. See its Javadoc for details and usage examples. + * *

Unlike {@link XmlWebApplicationContext}, no default configuration class locations * are assumed. Rather, it is a requirement to set the * {@linkplain ContextLoader#CONFIG_LOCATION_PARAM "contextConfigLocation"} diff --git a/org.springframework.web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/org.springframework.web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..b0ab3c3f006 --- /dev/null +++ b/org.springframework.web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +org.springframework.web.SpringServletContainerInitializer \ No newline at end of file