天天看点

spring web 容器启动方式

作者:huohuo494

spring web.xml 部署

springboot war包部署

springboot 内嵌 Tomcat

spring web.xml 部署

1.加载 web.xml

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
        classpath:applicationContext.xml
        classpath:spring-shiro-web.xml
    </param-value>
</context-param>
<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
           

2.创建父容器,根据 contextConfigLocation 加载资源文件

// ContextLoaderListener implements ServletContextListener
@Override
public void contextInitialized(ServletContextEvent event) {
   initWebApplicationContext(event.getServletContext());
}
           
<servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/servlet-context.xml</param-value>
        </init-param>
        <!-- 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet -->
        <!-- 当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载 -->        
        <load-on-startup>1</load-on-startup>
</servlet>
           

3.创建 DispatcherServlet,创建子容器,根据 contextConfigLocation 加载资源文件

// FrameworkServlet
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
   if (logger.isInfoEnabled()) {
      logger.info("Initializing Servlet '" + getServletName() + "'");
   }
   long startTime = System.currentTimeMillis();

   try {
      // 初始化容器
      this.webApplicationContext = initWebApplicationContext();
      // 空实现。子类有需要,可以实现该方法,实现自定义逻辑
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (logger.isDebugEnabled()) {
      String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
            "masked to prevent unsafe logging of potentially sensitive data";
      logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
            "': request parameters and headers will be " + value);
   }

   if (logger.isInfoEnabled()) {
      logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
   }
}
           

springboot war包部署

1.Servlet3.0中容器启动会扫描应用里面每一个jar包里面META-INF/services/javax.servlet.ServletContainerInitializer指定的实现类,启动并运行这个是实现的方法

public interface ServletContainerInitializer {

    /**
     * Notifies this <tt>ServletContainerInitializer</tt> of the startup of the application represented by the given
     * <tt>ServletContext</tt>.
     *
     * <p>
     * If this <tt>ServletContainerInitializer</tt> is bundled in a JAR file inside the <tt>WEB-INF/lib</tt> directory
     * of an application, its <tt>onStartup</tt> method will be invoked only once during the startup of the bundling
     * application. If this <tt>ServletContainerInitializer</tt> is bundled inside a JAR file outside of any
     * <tt>WEB-INF/lib</tt> directory, but still discoverable as described above, its <tt>onStartup</tt> method will be
     * invoked every time an application is started.
     *
     * @param c   the Set of application classes that extend, implement, or have been annotated with the class types
     *            specified by the {@link javax.servlet.annotation.HandlesTypes HandlesTypes} annotation, or
     *            <tt>null</tt> if there are no matches, or this <tt>ServletContainerInitializer</tt> has not been
     *            annotated with <tt>HandlesTypes</tt>
     *
     * @param ctx the <tt>ServletContext</tt> of the web application that is being started and in which the classes
     *            contained in <tt>c</tt> were found
     *
     * @throws ServletException if an error has occurred
     */
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
           
  1. SpringServletContainerInitializer 实现 ServletContainerInitializer,执行 WebApplicationInitializer 对应的 onStartup 方法,springboot 在这里创建

    SpringApplication,执行 run 方法

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

}
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
     // Logger initialization is deferred in case an ordered
     // LogServletContextInitializer is being used
     this.logger = LogFactory.getLog(getClass());
     WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
     if (rootApplicationContext != null) {
        servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
     }
     else {
        this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
              + "return an application context");
     }
  }
}
           
  1. 因为是通过 Tomcat main 方法启动。所以项目必须实现 SpringBootServletInitializer,才能将启动类 register,进而扫描到 @SpringBootApplication 注解,实现和 springboot main 方法一样的功能
@SpringBootApplication
public class SampleWebApplication extends SpringBootServletInitializer {

  public static void main(String[] args) {
    SpringApplication.run(SampleWebApplication.class, args);
  }

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(SampleWebApplication.class);
  }
}
           

springboot 内嵌 Tomcat

// ServletWebServerApplicationContext
@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}
private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
      // ServletWebServerFactoryAutoConfiguration
      ServletWebServerFactory factory = getWebServerFactory();
      createWebServer.tag("factory", factory.getClass().toString());
      this.webServer = factory.getWebServer(getSelfInitializer());
      createWebServer.end();
      getBeanFactory().registerSingleton("webServerGracefulShutdown",
            new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop",
            new WebServerStartStopLifecycle(this, this.webServer));
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
   }
   initPropertySources();
}
           
  • war 包和 main 方法都会调用 ServletContextInitializer onStartup方法,其中 RegistrationBean 会注册 servlet、filter 等到 servletContext;
  • DispatcherServletRegistrationBean 中 loadOnStartup 属性 为-1,说明容器启动的时候不初始化,第一次访问servlet时,才会初始化
private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {

   @Override
   public final void onStartup(ServletContext servletContext) throws ServletException {
      String description = getDescription();
      if (!isEnabled()) {
         logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
         return;
      }
      register(description, servletContext);
   }
}