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

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

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);
	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在刷新上下文的时候创建嵌入式容器。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5693637.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存