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

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的一个规范

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

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

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

    

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

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

 //该注解标注的接口的实现类会被实例化
@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 的父接口,也即是上面要实例化的就包括我们自定义的这个类。

⑤ 上面说了会运行所有实例化的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的启动流程中,最后服务启动完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值