美文网首页
Spring 应用的多种启动方式

Spring 应用的多种启动方式

作者: AlanSun2 | 来源:发表于2019-06-25 13:24 被阅读0次

    本文基于:
    SpringBoot-2.1.4

    方式1. Servlet3.0 之前使用 web.xml 配置监听器,Servlet,Filter 就可以了,如下:

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
          ...
    </servlet>
    <filter>
          ...
    </filter>
    

    ContextLoaderListener实现了 ServletContextListener,当 Servlet 容器启动时,contextInitialized(ServletContextEvent event)方法会被回调,Spring 会做相应的 ApplicationContext 初始化。

    方式2. Servlet3.0 之后,可以使用编程方式启动 Spring(不使用 Springboot)

    该方式得益于 Servlet3.0 的 ServletContainerInitializer 接口,该接口使用 SPI 加载实现类,Spring 的SpringServletContainerInitializer 实现了该接口,源码如下:

    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        @Override
        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
                throws ServletException {
      ...
      }
    }
    

    HandlesTypes: HandlesTypes的作用是其配置的类会被注入到onStartup方法的webAppInitializerClasses参数。

    我们也可以自己实现 ServletContainerInitializer 来做我们想做的事情。但是一般我们只要实现了 AbstractAnnotationConfigDispatcherServletInitializer 就可以使用 Spring(非 SpringBoog)。

    方式3. SpringBoot war包启动

    如果你是使用的 SpringBoot war包启动的话,你的启动类可能是这样的:

    @SpringBootApplication
    public class SpringTestApplication extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(SpringTestApplication.class);
        }
    }
    

    SpringBootServletInitializer 实现了 WebApplicationInitializer,就像上面提到的,我们的启动类会被注入到 SpringServletContainerInitializeronStartup 方法。在 onStartup 方法中 Spring 会做相应的ApplicationContext 初始化。看 SpringBootServletInitializer 的源码你就会发现,其实 war 包启动的方式最后还是会使用到 SpringApplication#run。

    方式4. SpringBoot jar方式启动

    该方式,Spring 会通过 main 方法中的 SpringApplication.run(SpringTestApplication.class, args); 来做相应的 ApplicationContext 初始化,并且它使用嵌入式的 Tomcat 来加载 Servlet,但是它没有完全遵守 servlet3.0 的规范,你可以尝试在 SpringServletContainerInitializer 中打个断点,你会发现它并没有被运行。它其实会进入到TomcatStarterTomcatStarterSpringServletContainerInitializer 一样也实现了 ServletContainerInitializer 接口,监听器,Servlet,Filter 的注册依靠的是 ServletContextInitializer。具体请看 spring-boot中tomcat的启动过程

    对于为什么不使用 SpringServletContainerInitializer? SpringBoot 在 github 的 issues 中也做了回答。Springboot 考虑到了如下的问题,我们在使用 Springboot 时,开发阶段一般都是使用内嵌 tomcat 容器,但部署时却存在两种选择:一种是打成 jar 包,使用 java -jar 的方式运行;另一种是打成 war 包,交给外置容器去运行。前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 servlet3.0 的策略去加载 ServletContainerInitializer!最后作者还提供了一个替代选项:ServletContextInitializer。该类被使用在了 TomcatServer 中。其实 war 包启动也会使用到 ServletContextInitializer,只要是 SpringBoot 启动都会使用 ServletContextInitializer 而不是 ServletContainerInitializer

    3.1 自定义 Servler,Filter,Listener 的一种注册方式

    基于以上内容,我们可以自定义 ServletContextInitializer 的实现类来实现 Servler,Filter,Listener 的注册(无论你是war包启动还是jar包启动)。其实 Spirng 已经帮我们做好了,当你去看它的实现类时,你会看到:

    • ServletRegistrationBean:用来注册 Servlet
    • FilterRegistrationBean:用来注册 Filter
    • ServletListenerRegistrationBean:用来注册 Listener
    • ...

    Spring 会通过以下方法为你注册:

    ServletWebServerApplicationContext.java

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) { //jar包启动入口
            ServletWebServerFactory factory = getWebServerFactory();
            // 这一步 getSelfInitializer() 方法返回的是一个 ServletContextInitializer,
            // 它负责处理所有的 Servler,Filter,Listener 的注册
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {//war包启动入口
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
        initPropertySources();
    }
    
    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        return this::selfInitialize;
    }
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        // getServletContextInitializerBeans()  会获取所有有注册的 ServletContextInitializer 实例
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    题外话

    1. 注解的方式注册Servlet。 3.0 以后,@WebServlet,@WebFilter,@WebListener + @ServletComponentScan
    2. Filter 的做种注册方式,详情

    总结

    1. Servlet 3.0 后使整个应用更加轻量了,出去繁琐的 xml 配置,甚至可以动态配置 Servlet,Filter,Listener 等。
    2. 无论 SpringBoot 是 war 包还是 jar 启动,它最后的都是调用的 SpringApplication#run 来启动的。

    相关文章

      网友评论

          本文标题:Spring 应用的多种启动方式

          本文链接:https://www.haomeiwen.com/subject/cdwyqctx.html