外置容器创建及启动ApplicationContext过程
Posted 恒奇恒毅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了外置容器创建及启动ApplicationContext过程相关的知识,希望对你有一定的参考价值。
一、外置容器使用步骤
SpringBoot Web在外置容器中也可以运行,使用步骤是
- 须创建一个war项目;
- 将嵌入式的Tomcat指定为provided;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 编写一个
SpringBootServletInitializer
的子类,并调用configure方法,传入主配置类(标注@SpringBootApplication
)
public class ServletInitializer extends SpringBootServletInitializer
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
//传入SpringBoot应用的主程序
return builder.sources(SpringBootApplicationMain.class);
当然我们还可以利用builder对应用做更多的配置
4. 将应用打成war包部署到web容器,比如tomcat
二、Servlet3.0规范ServletContainerInitializer
在说明启动原理之前我们先回顾一下Servlet3.0规范关于Shared libraries / runtimes pluggability
的说明。
- 服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面
ServletContainerInitializer
实例 ServletContainerInitializer
的实现放在jar包的META-INF/services
文件夹下,有一个名为javax.servlet.ServletContainerInitializer
的文件,内容就是ServletContainerInitializer
的实现类的全类名ServletContainerInitializer
实现类上使用@HandlesTypes
,在应用启动的时候传入我们感兴趣的类;
/**
* ServletContainerInitializers (SCIs) are registered via an entry in the
* file META-INF/services/javax.servlet.ServletContainerInitializer that must be
* included in the JAR file that contains the SCI implementation.
* <p>
* SCI processing is performed regardless of the setting of metadata-complete.
* SCI processing can be controlled per JAR file via fragment ordering. If an
* absolute ordering is defined, the only those JARs included in the ordering
* will be processed for SCIs. To disable SCI processing completely, an empty
* absolute ordering may be defined.
* <p>
* SCIs register an interest in annotations (class, method or field) and/or
* types via the @link javax.servlet.annotation.HandlesTypes annotation which
* is added to the class.
*
* @since Servlet 3.0
*/
public interface ServletContainerInitializer
/**
* Receives notification during startup of a web application of the classes
* within the web application that matched the criteria defined via the
* @link javax.servlet.annotation.HandlesTypes annotation.
*
* @param c The (possibly null) set of classes that met the specified
* criteria
* @param ctx The ServletContext of the web application in which the
* classes were discovered
*
* @throws ServletException If an error occurs
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
标注在该类实现类上的类,会通过onStartup的第一个参数传递进来。
三、启动原理
- 我们发现在spring-web模块下,jar包的META-INF/services下的确存在一个文件
javax.servlet.ServletContainerInitializer
,其内容就是org.springframework.web.SpringServletContainerInitializer
SpringServletContainerInitializer
的类定义如下,类上标注了@HandlesTypes(WebApplicationInitializer.class)
,传入了WebApplicationInitializer
,于是在加载的时候onStartup
方法的第一个参数会传递所有类路径下的WebApplicationInitializer
的子类。在onStartup
方法中,实例化了所有WebApplicationInitializer
的可实例化子类,排序后分别调用了其onStartup
方法,传入ServletContext
。
/**
* 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.
*
* <h2>Mechanism of Operation</h2>
* 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
* <a href="http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">
* JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0
* Final Draft specification for complete details.
*
* <h3>In combination with @code web.xml</h3>
* A web application can choose to limit the amount of classpath scanning the Servlet
* container does at startup either through the @code metadata-complete attribute in
* @code web.xml, which controls scanning for Servlet annotations or through an
* @code <absolute-ordering> element also in @code web.xml, which controls which
* web fragments (i.e. jars) are allowed to perform a @code ServletContainerInitializer
* scan. When using this feature, the @link SpringServletContainerInitializer
* can be enabled by adding "spring_web" to the list of named web fragments in
* @code web.xml as follows:
*
* <pre class="code">
* @code
* <absolute-ordering>
* <name>some_web_fragment</name>
* <name>spring_web</name>
* </absolute-ordering>
* </pre>
*
* <h2>Relationship to Spring's @code WebApplicationInitializer</h2>
* 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 onStartup documentation below.
*
* <h2>General Notes</h2>
* In general, this class should be viewed as <em>supporting infrastructure</em> for
* the more important and user-facing @code WebApplicationInitializer SPI. Taking
* advantage of this container initializer is also completely <em>optional</em>: 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.
*
* <p>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. In other words, any servlet, listener, or filter may be
* registered within a @code WebApplicationInitializer, not just Spring MVC-specific
* components.
*
* <p>This class is neither designed for extension nor intended to be extended.
* It should be considered an internal type, with @code WebApplicationInitializer
* being the public-facing SPI.
*
* <h2>See Also</h2>
* See @link WebApplicationInitializer Javadoc for examples and detailed usage
* recommendations.<p>
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.1
* @see #onStartup(Set, ServletContext)
* @see WebApplicationInitializer
*/
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer
/**
* Delegate the @code ServletContext to any @link WebApplicationInitializer
* implementations present on the application classpath.
* <p>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.
* <p>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.
* <p>Assuming that one or more @code WebApplicationInitializer types are detected,
* they will be instantiated (and <em>sorted</em> 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
*/
@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);
WebApplicationInitializer
类定义如下:
/**
* 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
就会被实例化并调用onStartup
。一般我们不会直接实现WebApplicationInitializer
,而是继承SpringBootServletInitializer
,它完成了容器的创建。我们看到除开SpringBootServletInitializer
还有其他类也实现了WebApplicationInitializer
,后面有机会再表。
SpringBootServletInitializer
,该类完成了容器的创建。
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.listeners(new ServletContextApplicationListener(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
builder = configure(builder);
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;
在createRootApplicationContext
方法中,首先创建一个SpringApplicationBuilder
,然后配置一系列的属性(最关键的设置了ApplicationContext为AnnotationConfigEmbeddedWebApplicationContext
和配置注解源),再据此创建了SpringApplication
,最后调用其run
方法完成容器启动。run方法后面再表。
6. 提供了一个我们可以定制builder属性的方法configure
。利用该方法,我们可以设置注解源、环境配置等。
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
return builder;
以上是关于外置容器创建及启动ApplicationContext过程的主要内容,如果未能解决你的问题,请参考以下文章
『中级篇』docker之java容器运行外置springboot-jar(番外篇)(79)
SpringBoot配置外部Tomcat项目启动流程源码分析(上)