springboot学习总结(十)WebApplicationInitializer简介
随着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;
}
}
}
浙公网安备 33010602011771号