欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot使用外置Servlet容器和原理分析

程序员文章站 2022-05-23 17:35:48
...

1、使用外部servlet容器的步骤

1)、maven项目打包方式为war

<packaging>war</packaging>

2)、将嵌入式Servlet容器的打包方式指定为provided

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

3)、必须编写一个SpringBootServletInitializer的子类,并重写configure方法,如下,其中ExtTomcatApplication是SpringBoot的主程序类

package com.dxy.springboot;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

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

}

4)、启动外置Servlet容器即可

2、原理分析

1)、在SpringBoot打包成jar包的情况下是如何启动的?启动main方法,初始化IOC容器,创建嵌入式Servlet容器并启动,可参考另外一篇博文SpringBoot启动流程分析;当打包成war的时候是启动外置Servlet容器的,然后服务器启动SpringBoot应用【SpringBootServletInitializer】,最后再启动ioc容器。

2)、为何会有上面描述的启动流程,这里就需要了解一下servlet3.0的一个规范

SpringBoot使用外置Servlet容器和原理分析

 ① 服务器启动web应用的时候会去该应用下的所有的jar包中找ServletContainerInitializer的实例

② 如何找到这些实例呢?这些实例的全类名都存到jar包的META-INF/services目录下名为

     javax.servlet.ServletContainerInitializer文件中,如下图,在spring-web-5.2.2.RELEASE.jar包下就有该文件

    SpringBoot使用外置Servlet容器和原理分析

 文件内容是org.springframework.web.SpringServletContainerInitializer

SpringBoot使用外置Servlet容器和原理分析

③ 打开该类,我们一探究竟

 //该注解标注的接口的实现类会被实例化
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

     //将所有的@HandlesTypes标注的接口的实现类的class对象保存到Set集合中
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

         //创建了一个@HandlesTypes注解标注类型的List用于保存所有的实例
		List<WebApplicationInitializer> initializers = new LinkedList<>();

		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 {
                        //判断如果不是接口或者抽象类,则进行实例化并添加到List中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(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) {
             //运行上面实例化的所有对象的onStartup方法
			initializer.onStartup(servletContext);
		}
	}

}

  根据上面的servlet3.0规范中的第二段结合上面的源代码可知,在该类的onStartup方法中会将所有@HandlesTypes(WebApplicationInitializer.class)注解标注的接口(这里是WebApplicationInitializer)的实现类进行实例化,并运行他们的onStartup方法,请结合上面代码中的注解进行理解。

④ 上面类中@HandlesTypes标注的WebApplicationInitializer又是何方神圣,它其实就是我们在最开头讲述的SpringBootServletInitializer 的父接口,也即是上面要实例化的就包括我们自定义的这个类。

SpringBoot使用外置Servlet容器和原理分析

⑤ 上面说了会运行所有实例化的WebApplicationInitializer子类的onStartup方法,那我们就需要看看这个方法做了些什么,我们自动的类中没有该方法,那就应该在它的父类SpringBootServletInitializer中

    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        //调用下面的createRootApplicationContext方法
        WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
          //创建一个SpringApplication的构建器
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        //运行我们自定义类覆盖的方法,从而便可以获得该SpringBoot的主程序类
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
         //通过上面的builder构建出一个SpringApplication对象
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }

         //运行SpringApplication的run方法启动整个SpringBoot应用,就回到了SpringBoot的启动流程
        return this.run(application);
    }

    protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext)application.run(new String[0]);
    }

⑥ 通过调用SpringApplication的run方法就启动了整个SpringBoot应用,也就回到了SpringBoot的启动流程中,最后服务启动完成。