SpringBoot war 启动原理
一、配置war包启动
1、 将pom.xml文件的packaging属性改为war,然后排除tomcat的依赖 。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
2、 在主配置类中继承SpringBootServletInitializer,然后重写configure方法 。
/**
* @author lixm
*/
@SpringBootApplication
public class App extends SpringBootServletInitializer{
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
// 注意下面App.class 配置自己的启动类
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(new Class[] {App.class});
}
}
3、打war包
略过。
二、源码分析
1、jar启动构建tomcat容器源码分析
涉计到SpringBoot源码,下面只会分析中间件的先关代码,不再分析和本次内容无关的代码,如果需要后续可以单独出一遍boot的源码。
// 1、spingboot 启动类
SpringApplication.run(App.class, args);
// 2、org.springframework.boot.SpringApplication.run(String...)
// 1、由于我们是Servlet项目,所以返回的是AnnotationConfigServletWebServerApplicationContext
static class Factory implements ApplicationContextFactory {
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
return (webApplicationType != WebApplicationType.SERVLET) ? null
: new AnnotationConfigServletWebServerApplicationContext();
}
}
// 2、刷新refreshcontext上下文
//通过堆栈代码执行到
1、org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh()
-> 2、org.springframework.context.support.AbstractApplicationContext.refresh()
// 上面的AbstractApplicationContext.refresh 函数里面会执行到onRefresh方法。
-> 3、org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh()
-> 4、createWebServer();
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// 初始化条件
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
/**
注意上述webServer的初始化条件是 webServer == null && servletContext == null,这个很关键。下面war源码会在这个地方已经初始化了 servletContext 环境。(很重要)
*/
总结:通过代码的分析,可以发现webServer的构建是依赖IOC容器启动的过程中。
2、war启动源码分析
分析:war启动配置里友谊和核心的操作。主类是需要继承 SpringBootServletInitializer。该类是实现 WebApplicationInitializer。 利用SPI机制的SpringServletContainerInitializer类 可以找到这个WebApplicationInitializer。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
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 {
initializers.add((WebApplicationInitializer) 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) {
initializer.onStartup(servletContext);
}
}
}
ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架.
我们以SpringMVC举例, servlet3.0之前我们需要在web.xml中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置。 (这里主要是描述servlet的一个扩展特性,利用这个特性实现了IOC初始化工作)
从上述总结我们可以理解就是外部中间件在启动的时候会执行 SpringBootServletInitializer
里的 onStartup 方法。
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
}
else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
+ "return an application context");
}
}
大家注意这个 函数参数 ServletContext servletContext 。这个是中间件调用的时候传进来的。这个很关键!!!
接着分析这个函数代码。
WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
这里面两处关键代码。
1、ServletContext 环境绑定到 initializers 里。
2、执行了 SpringApplication 的run 方法。
你会发现这块代码和上面jar执行的代码开始重叠了,但是在执行prepareContext 函数时候,由于上述 ServletContext 绑定到initializers 里,这个prepareContext 就做了一件事
这行代码主要就是把servlet环境绑定到IOC上的servletContext属性上了。回顾我们jar启动的时候,我们初始化webServer时候说的是他有一个条件。(webServer == null && servletContext == null),看到这里我们或许就明白了原理。
总结: 和jar启动原理不同是 IOC容器启动是依赖webServer初始化。