非嵌入式方式启动spring boot应用需要继承SpringBootServletInitializer并重写configure方法,执行SpringApplication.sources方法,参数为SpringBoot应用的主程序。后面分析为啥需要这一步。
spring boot非嵌入式容器启动原理从大的方面只有一个:
Servlet3.0提供的规范:javax.servlet.ServletContainerInitializer
具体实现如下:
Spring在spring-web jar包:/meta-INF/services/javax.servlet.ServletContainerInitializer文件中,配置了spring对ServletContainerInitializer接口的实现类org.springframework.web.SpringServletContainerInitializer。
在实现了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容器)。
spring boot提供了ServletContainerInitializer的实现SpringBootServletInitializer。注意SpringBootServletInitializer是抽象类
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {所以需要自己继承该类。
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在刷新上下文的时候创建嵌入式容器。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)