spring boot war包(非嵌入式容器)启动原理

非嵌入式方式启动spring boot应用需要继承SpringBootServletInitializer并重写configure方法,执行SpringApplication.sources方法,参数为SpringBoot应用的主程序。后面分析为啥需要这一步。

spring boot非嵌入式容器启动原理从大的方面只有一个:

Servlet3.0提供的规范:javax.servlet.ServletContainerInitializer

具体实现如下:

  1. Spring在spring-web jar包:/META-INF/services/javax.servlet.ServletContainerInitializer文件中,配置了spring对ServletContainerInitializer接口的实现类org.springframework.web.SpringServletContainerInitializer

  2. 在实现了Servlet3.0规范的Servlet 容器在启动阶段会扫描jar包中:META-INF/services/javax.servlet.ServletContainerInitializer文件,获取ServletContainerInitializer实现类并实例化,解析出ServletContainerInitializer类上的@HandlesTypes注解,根据@HandlesTypes限定的类型集合,作为ServletContainerInitializer.onStartup方法处理的第一个参数c调用onStartup方法(第二个参数为ServletContext,这个ServletContext也是关键的,spring boot也是基于spring 上下文中是否有ServletContext来是否创建嵌入式Servlet容器)。

  3. spring boot提供了ServletContainerInitializer的实现SpringBootServletInitializer。注意SpringBootServletInitializer是抽象类

    public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

    所以需要自己继承该类。

  4. SpringBootServletInitializer.onStartup方法调用SpringBootServletInitializer.createRootApplicationContext方法,在createRootApplicationContext方法中构建SpringApplication对象并执行SpringApplication.run方法以启动spring boot项目。

不管是嵌入式还是非嵌入式,都是通过SpringApplication.run来启动spring boot应用的。

那么spring boot是如何判断是否需要创建嵌入式容器呢??

源头就在于SpringBootServletInitializer,下面源码分析一波:入口:onStartup方法:

 

1、

builder.initializers(new ServletContextApplicationContextInitializer(servletContext));

ServletContextApplicationContextInitializer实现了org.springframework.context.ApplicationContextInitializer,而且ApplicationContextInitializer在spring boot的执行时机先于application的refresh方法的,先看下org.springframework.boot.SpringApplication#run方法:

prepareContext方法:

 在applyInitializers里面执行了所有的ApplicationContextInitializer的initialize方法,而spring boot添加的ServletContextApplicationContextInitializer的initialize方法如下:

设置applicationContext的servletContext。关键关键!!!

 继续回到上面的org.springframework.boot.web.servlet.support.SpringBootServletInitializer#createRootApplicationContext方法:

builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());

这里不是关键地方:这里直接通过lambda创建一个返回AnnotationConfigServletWebServerApplicationContext的ApplicationContextFactory,也就是非嵌入式的spring  IOC容器类型为:AnnotationConfigServletWebServerApplicationContext

builder = configure(builder);
/**
	 * 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;
	}

注释的意思:添加配置类即可。

那么我们为什么必须重写这个方法呢?

@SpringBootApplication注解一个是自动配置的开启,还有一个就是我们定义扫描我们的配置类包路径,不然,IOC是无法找到我们的配置类的,因为我们并没有向IOC容器中注入我们的配置类。所以将我们的启动类添加到SpringApplication的source中即可。

可以不重写吗?

是可以的。看下org.springframework.boot.web.servlet.support.SpringBootServletInitializer#createRootApplicationContext的逻辑:

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.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().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.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		application.setRegisterShutdownHook(false);
		return run(application);
	}

有段代码:

 如果application.getAllSources().isEmpty(),默认返回true

并且MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)),用于判断当前类是否有@Configuration,有的话,就会执行:

application.addPrimarySources(Collections.singleton(getClass()));

所以,我们可以这样写:

加上@SpringBootApplication就不需要重写configure方法了。 

下面分析IOC容器的刷新:

不管是嵌入式servlet容器还是非嵌入式servlet容器启动spring boot应用,创建的IOC容器类型都为:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#AnnotationConfigServletWebServerApplicationContext()

嵌入式方式:

 入口在org.springframework.boot.SpringApplication#run(java.lang.String...)方法:

 执行到org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh

super.refresh(),这个就到spring IOC容器刷新的内容了,内部有一个函数:

 ServletWebServerApplicationContext重写了该onRefresh方法:

 

 判断了servletContext是否为null,前面已经通过ServletContextApplicationContextInitializer的initialize绑定了,所以这里不会去创建WebServer 了。

总结

非嵌入式容器启动spring boot应用的实现关键有2点:

  • spring boot应用如何启动:基于Servlet3.0规范
           非嵌入式spring boot应用启动基于Servlet3.0规范,然后继承SpringBootServletInitializer来启动spring boot应用,可以通过重写其configure方法来添加我们的启动类,也可以在SpringBootServletInitializer的实现类上(比如本文的WarSpringBootApplication)添加@SpringBootApplication注解。
  • 如何判断当前应用基于外部容器启动还是通过创建嵌入式容器启动
         通过ServletContextApplicationContextInitializer来阻止IOC在刷新上下文的时候创建嵌入式容器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值