随着springboot框架的兴起,xml配置已经渐行渐远,基本已经被JavaConfig配置所取代。今天所说的WebApplicationInitializer的作用就是用来替代web开发中最重要的web.xml文件。
关于WebApplicationInitializer的介绍,最好的莫过于她自身的注释了。
/* * 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; 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. * * <p>Implementations of this SPI will be detected automatically by {@link * SpringServletContainerInitializer}, which itself is bootstrapped automatically * by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its * Javadoc} for details on this bootstrapping mechanism. * * <h2>Example</h2> * <h3>The traditional, XML-based approach</h3> * 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: * <pre class="code"> * {@code * <servlet> * <servlet-name>dispatcher</servlet-name> * <servlet-class> * org.springframework.web.servlet.DispatcherServlet * </servlet-class> * <init-param> * <param-name>contextConfigLocation</param-name> * <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value> * </init-param> * <load-on-startup>1</load-on-startup> * </servlet> * * <servlet-mapping> * <servlet-name>dispatcher</servlet-name> * <url-pattern>/</url-pattern> * </servlet-mapping>}</pre> * * <h3>The code-based approach with {@code WebApplicationInitializer}</h3> * Here is the equivalent {@code DispatcherServlet} registration logic, * {@code WebApplicationInitializer}-style: * <pre class="code"> * 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("/"); * } * * }</pre> * * As an alternative to the above, you can also extend from {@link * org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}. * * As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method * we're actually registering an <em>instance</em> 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. * * <p>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}. * * <p>Most major Spring Web components have 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. * * <h2>A 100% code-based approach to configuration</h2> * 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}: * <pre class="code"> * 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("/"); * } * * }</pre> * * As an alternative to the above, you can also extend from {@link * org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}. * * Remember that {@code WebApplicationInitializer} implementations are <em>detected * automatically</em> -- so you are free to package them within your application as you * see fit. * * <h2>Ordering {@code WebApplicationInitializer} execution</h2> * {@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}. * * <h2>Caveats</h2> * * <h3>web.xml versioning</h3> * <p>{@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 * <em>modify</em> registrations performed in {@code web.xml} through methods such as * {@link ServletContext#getServletRegistration(String)}. <strong>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.</strong> * * <h3>Mapping to '/' under Tomcat</h3> * <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions * <= 7.0.14, this servlet mapping <em>cannot be overridden programmatically</em>. * 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested * successfully under GlassFish 3.1.<p> * * @author Chris Beams * @since 3.1 * @see SpringServletContainerInitializer * @see org.springframework.web.context.AbstractContextLoaderInitializer * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer * @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer */ 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; }
我这边挑出重点指出,回答下面几个问题
(一)实现了WebApplicationInitializer的类是如何发现的?
答:利用SPI机制的SpringServletContainerInitializer类。
在与WebApplicationInitializer类同路径下有个SpringServletContainerInitializer类。这个类的作用就是发现所有实现了WebApplicationInitializer的类。
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
(二)springboot项目debug启动时为何没有进入到SpringServletContainerInitializer的onStartup方法?
答:WebApplicationInitializer目前只对war启动的项目有效,对jar启动的项目无效。
关于这个答案,在SpringBootServletInitializer类的注释中有写
* <p> * Note that a WebApplicationInitializer is only needed if you are building a war file and * deploying it. If you prefer to run an embedded container then you won't need this at * all.
SpringBootServletInitializer的源码如下
/* * 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.web.support; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.Assert; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; /** * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and * {@link ServletContextInitializer} beans from the application context to the servlet * container. * <p> * To configure the application either override the * {@link #configure(SpringApplicationBuilder)} method (calling * {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you * might also want to add an {@code @Ordered} annotation to configure a specific startup * order. * <p> * Note that a WebApplicationInitializer is only needed if you are building a war file and * deploying it. If you prefer to run an embedded container then you won't need this at * all. * * @author Dave Syer * @author Phillip Webb * @author Andy Wilkinson * @since 1.4.0 * @see #configure(SpringApplicationBuilder) */ public abstract class SpringBootServletInitializer implements WebApplicationInitializer { protected Log logger; // Don't initialize early private boolean registerErrorPageFilter = true; /** * Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if * error page mappings should be handled via the Servlet container and not Spring * Boot. * @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered. */ protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) { this.registerErrorPageFilter = registerErrorPageFilter; } @Override public void onStartup(ServletContext servletContext) throws ServletException { // Logger initialization is deferred in case a ordered // LogServletContextInitializer is being used this.logger = LogFactory.getLog(getClass()); WebApplicationContext rootAppContext = createRootApplicationContext( servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { @Override public void contextInitialized(ServletContextEvent event) { // no-op because the application context is already initialized } }); } else { this.logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not " + "return an application context"); } } protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); if (application.getSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.getSources().add(getClass()); } Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } return run(application); } /** * Returns the {@code SpringApplicationBuilder} that is used to configure and create * the {@link SpringApplication}. The default implementation returns a new * {@code SpringApplicationBuilder} in its default state. * @return the {@code SpringApplicationBuilder}. * @since 1.3.0 */ protected SpringApplicationBuilder createSpringApplicationBuilder() { return new SpringApplicationBuilder(); } /** * Called to run a fully configured {@link SpringApplication}. * @param application the application to run * @return the {@link WebApplicationContext} */ protected WebApplicationContext run(SpringApplication application) { return (WebApplicationContext) application.run(); } private ApplicationContext getExistingRootWebApplicationContext( ServletContext servletContext) { Object context = servletContext.getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (context instanceof ApplicationContext) { return (ApplicationContext) context; } return null; } /** * Configure the application. Normally all you would need to do is to add sources * (e.g. config classes) because other settings have sensible defaults. You might * choose (for instance) to add default command line arguments, or set an active * Spring profile. * @param builder a builder for the application context * @return the application builder * @see SpringApplicationBuilder */ protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder; } private static final class WebEnvironmentPropertySourceInitializer implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered { private final ServletContext servletContext; private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) { this.servletContext = servletContext; } @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); if (environment instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) environment) .initPropertySources(this.servletContext, null); } } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } } }