diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 10a8325c76c..c0eb4390886 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -28,17 +28,18 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.i18n.LocaleContext; import org.springframework.core.OrderComparator; import org.springframework.core.io.ClassPathResource; @@ -46,6 +47,7 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.ui.context.ThemeSource; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -118,9 +120,15 @@ import org.springframework.web.util.WebUtils; * namespace, loading its own application context with mappings, handlers, etc. Only the root application context as * loaded by {@link org.springframework.web.context.ContextLoaderListener}, if any, will be shared. * + *

As of Spring 3.1, {@code DispatcherServlet} may now be injected with a web + * application context, rather than creating its own internally. This is useful in Servlet + * 3.0+ environments, which support programmatic registration of servlet instances. See + * {@link #DispatcherServlet(WebApplicationContext)} Javadoc for details. + * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop + * @author Chris Beams * @see org.springframework.web.HttpRequestHandler * @see org.springframework.web.servlet.mvc.Controller * @see org.springframework.web.context.ContextLoaderListener @@ -265,6 +273,70 @@ public class DispatcherServlet extends FrameworkServlet { private List viewResolvers; + /** + * Create a new {@code DispatcherServlet} that will create its own internal web + * application context based on defaults and values provided through servlet + * init-params. Typically used in Servlet 2.5 or earlier environments, where the only + * option for servlet registration is through {@code web.xml} which requires the use + * of a no-arg constructor. + *

Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation') + * will dictate which XML files will be loaded by the + * {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext} + *

Calling {@link #setContextClass} (init-param 'contextClass') overrides the + * default {@code XmlWebApplicationContext} and allows for specifying an alternative class, + * such as {@code AnnotationConfigWebApplicationContext}. + *

Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses') + * indicates which {@code ApplicationContextInitializer} classes should be used to + * further configure the internal application context prior to refresh(). + * @see #DispatcherServlet(WebApplicationContext) + */ + public DispatcherServlet() { + super(); + } + + /** + * Create a new {@code DispatcherServlet} with the given web application context. This + * constructor is useful in Servlet 3.0+ environments where instance-based registration + * of servlets is possible through the {@link ServletContext#addServlet} API. + *

Using this constructor indicates that the following properties / init-params + * will be ignored: + *

+ *

The given web application context may or may not yet be {@linkplain + * ConfigurableApplicationContext#refresh() refreshed}. If it has not + * already been refreshed (the recommended approach), then the following will occur: + *

+ * If the context has already been refreshed, none of the above will occur, under the + * assumption that the user has performed these actions (or not) per their specific + * needs. + *

See {@link org.springframework.web.WebApplicationInitializer} for usage examples. + * @param webApplicationContext the context to use + * @see #initWebApplicationContext + * @see #configureAndRefreshWebApplicationContext + * @see org.springframework.web.WebApplicationInitializer + */ + public DispatcherServlet(WebApplicationContext webApplicationContext) { + super(webApplicationContext); + } + /** * Set whether to detect all HandlerMapping beans in this servlet's context. Otherwise, * just a single bean with name "handlerMapping" will be expected. diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index 7e4b5d58223..d119b7c9188 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet; import java.io.IOException; import java.security.Principal; +import java.util.TreeSet; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -35,7 +36,9 @@ import org.springframework.context.event.SourceFilteringListener; import org.springframework.context.i18n.LocaleContext; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.SimpleLocaleContext; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; @@ -100,6 +103,11 @@ import org.springframework.web.util.WebUtils; * with XmlWebApplicationContext). The namespace can also be set explicitly via * the "namespace" servlet init-param. * + *

As of Spring 3.1, {@code FrameworkServlet} may now be injected with a web + * application context, rather than creating its own internally. This is useful in Servlet + * 3.0+ environments, which support programmatic registration of servlet instances. See + * {@link #FrameworkServlet(WebApplicationContext)} Javadoc for details. + * * @author Rod Johnson * @author Juergen Hoeller * @author Sam Brannen @@ -172,8 +180,77 @@ public abstract class FrameworkServlet extends HttpServletBean { /** Flag used to detect whether onRefresh has already been called */ private boolean refreshEventReceived = false; + /** Comma-delimited ApplicationContextInitializer classnames set through init param */ private String contextInitializerClasses; + /** Actual ApplicationContextInitializer instances to apply to the context */ + private TreeSet> contextInitializers = + new TreeSet>(new AnnotationAwareOrderComparator()); + + + /** + * Create a new {@code FrameworkServlet} that will create its own internal web + * application context based on defaults and values provided through servlet + * init-params. Typically used in Servlet 2.5 or earlier environments, where the only + * option for servlet registration is through {@code web.xml} which requires the use + * of a no-arg constructor. + *

Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation') + * will dictate which XML files will be loaded by the + * {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext} + *

Calling {@link #setContextClass} (init-param 'contextClass') overrides the + * default {@code XmlWebApplicationContext} and allows for specifying an alternative class, + * such as {@code AnnotationConfigWebApplicationContext}. + *

Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses') + * indicates which {@link ApplicationContextInitializer} classes should be used to + * further configure the internal application context prior to refresh(). + * @see #FrameworkServlet(WebApplicationContext) + */ + public FrameworkServlet() { + } + + /** + * Create a new {@code FrameworkServlet} with the given web application context. This + * constructor is useful in Servlet 3.0+ environments where instance-based registration + * of servlets is possible through the {@link ServletContext#addServlet} API. + *

Using this constructor indicates that the following properties / init-params + * will be ignored: + *

+ *

The given web application context may or may not yet be {@linkplain + * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation + * of {@link ConfigurableWebApplicationContext} and (b) has not + * already been refreshed (the recommended approach), then the following will occur: + *

+ * If the context has already been refreshed or does not implement + * {@code ConfigurableWebApplicationContext}, none of the above will occur under the + * assumption that the user has performed these actions (or not) per his or her + * specific needs. + *

See {@link org.springframework.web.WebApplicationInitializer} for usage examples. + * @param webApplicationContext the context to use + * @see #initWebApplicationContext + * @see #configureAndRefreshWebApplicationContext + * @see org.springframework.web.WebApplicationInitializer + */ + public FrameworkServlet(WebApplicationContext webApplicationContext) { + this.webApplicationContext = webApplicationContext; + } /** * Set the name of the ServletContext attribute which should be used to retrieve the @@ -230,12 +307,25 @@ public abstract class FrameworkServlet extends HttpServletBean { /** * Specify the set of fully-qualified {@link ApplicationContextInitializer} class * names, per the optional "contextInitializerClasses" servlet init-param. - * @see #createWebApplicationContext + * @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext) + * @see #applyInitializers(ConfigurableWebApplicationContext) */ public void setContextInitializerClasses(String contextInitializerClasses) { this.contextInitializerClasses = contextInitializerClasses; } + /** + * Specify which {@link ApplicationContextInitializer} instances should be used + * to initialize the application context used by this {@code FrameworkServlet}. + * @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext) + * @see #applyInitializers(ConfigurableWebApplicationContext) + */ + public void setContextInitializers(ApplicationContextInitializer... contextInitializers) { + for (ApplicationContextInitializer initializer : contextInitializers) { + this.contextInitializers.add(initializer); + } + } + /** * Set the context config location explicitly, instead of relying on the default * location built from the namespace. This location string can consist of @@ -362,21 +452,48 @@ public abstract class FrameworkServlet extends HttpServletBean { *

Delegates to {@link #createWebApplicationContext} for actual creation * of the context. Can be overridden in subclasses. * @return the WebApplicationContext instance + * @see #FrameworkServlet(WebApplicationContext) * @see #setContextClass * @see #setContextConfigLocation */ protected WebApplicationContext initWebApplicationContext() { - WebApplicationContext wac = findWebApplicationContext(); + WebApplicationContext rootContext = + WebApplicationContextUtils.getWebApplicationContext(getServletContext()); + WebApplicationContext wac = null; + + if (this.webApplicationContext != null) { + // A context instance was injected at construction time -> use it + wac = this.webApplicationContext; + if (wac instanceof ConfigurableWebApplicationContext) { + ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; + if (!cwac.isActive()) { + // The context has not yet been refreshed -> provide services such as + // setting the parent context, setting the application context id, etc + if (cwac.getParent() == null) { + // The context instance was injected without an explicit parent -> set + // the root application context (if any; may be null) as the parent + cwac.setParent(rootContext); + } + configureAndRefreshWebApplicationContext(cwac); + } + } + } + if (wac == null) { + // No context instance was injected at construction time -> see if one + // has been registered in the servlet context. If one exists, it is assumed + // that the parent context (if any) has already been set and that the + // user has performed any initialization such as setting the context id + wac = findWebApplicationContext(); + } if (wac == null) { - // No fixed context defined for this servlet - create a local one. - WebApplicationContext parent = - WebApplicationContextUtils.getWebApplicationContext(getServletContext()); - wac = createWebApplicationContext(parent); + // No context instance is defined for this servlet -> create a local one + wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { - // Apparently not a ConfigurableApplicationContext with refresh support: - // triggering initial onRefresh manually here. + // Either the context is not a ConfigurableApplicationContext with refresh + // support or the context injected at construction time had already been + // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } @@ -447,39 +564,48 @@ public abstract class FrameworkServlet extends HttpServletBean { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); - // Assign the best possible id value. - ServletContext sc = getServletContext(); - if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { - // Servlet <= 2.4: resort to name specified in web.xml, if any. - String servletContextName = sc.getServletContextName(); - if (servletContextName != null) { - wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName + - "." + getServletName()); + wac.setParent(parent); + wac.setConfigLocation(getContextConfigLocation()); + + configureAndRefreshWebApplicationContext(wac); + + return wac; + } + + protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { + + if (ObjectUtils.identityToString(wac).equals(wac.getId())) { + // The application context id is still set to its original default value + // -> assign a more useful id based on available information + ServletContext sc = getServletContext(); + if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { + // Servlet <= 2.4: resort to name specified in web.xml, if any. + String servletContextName = sc.getServletContextName(); + if (servletContextName != null) { + wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName + + "." + getServletName()); + } + else { + wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName()); + } } else { - wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName()); + // Servlet 2.5's getContextPath available! + wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() + + "/" + getServletName()); } } - else { - // Servlet 2.5's getContextPath available! - wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() + - "/" + getServletName()); - } - wac.setParent(parent); wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); - wac.setConfigLocation(getContextConfigLocation()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); postProcessWebApplicationContext(wac); - initializeWebApplicationContext(wac); + applyInitializers(wac); wac.refresh(); - - return wac; } /** @@ -506,10 +632,10 @@ public abstract class FrameworkServlet extends HttpServletBean { * @param wac the configured WebApplicationContext (not refreshed yet) * @see #createWebApplicationContext * @see #postProcessWebApplicationContext - * @see ConfigurableWebApplicationContext#refresh() + * @see ConfigurableApplicationContext#refresh() */ @SuppressWarnings("unchecked") - protected void initializeWebApplicationContext(ConfigurableWebApplicationContext wac) { + protected void applyInitializers(ConfigurableApplicationContext wac) { if (this.contextInitializerClasses != null) { String[] initializerClassNames = StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS); for(String initializerClassName : initializerClassNames) { @@ -522,9 +648,13 @@ public abstract class FrameworkServlet extends HttpServletBean { String.format("Could not instantiate class [%s] specified via " + "'contextInitializerClasses' init-param", initializerClassName), ex); } - initializer.initialize(wac); + this.contextInitializers.add(initializer); } } + + for (ApplicationContextInitializer initializer : this.contextInitializers) { + initializer.initialize(wac); + } } /**