在前面几章的讲解中,我们知道,当我们执行以下代码时,springboot会启动一个内置的tomcat,并且加载对应的starter.那么如果我们不采用java -jar的方式启动springboot的应用,他也就没有去执行run方法,那么他又是如何做到自动装配的呢?
在说这些之前,我们先要了解一个东西,SPI。关于SPI可以去了解我的另一篇文章
我们看spring-web这个项目的spi文件javax.servlet.ServletContainerInitializer
文件内容如下
org.springframework.web.SpringServletContainerInitializer
SpringServletContainerInitializer
代码如下
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}
可以看到这个类继承了一个叫ServletContainerInitializer
的接口
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
这个接口是干嘛的呢?ServletContainerInitializer
是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter
, Servlet
以及Listener
,以取代通过web.xml
配置注册。这样就利于开发内聚的web应用框架。容器启动阶段依据java spi
获取到所有ServletContainerInitializer
的实现类,然后执行其onStartup
方法.
也就是说,我们把 ServletContainerInitializer
的实现类写在 META-INF / services / javax.servlet.ServletContainerInitializer
文件中,那么Tomcat等容器启动的时候就会去调用所有实现类的onStartup
方法。
@HandlesTypes注解是干嘛的呢?
SpringServletContainerInitializer
就是ServletContainerInitializer
的实现类,可以看到SpringServletContainerInitializer
加上了一个@HandlesTypes(WebApplicationInitializer.class)
的注解,这个注解的作用就是容器启动的时候调用实现类的onStartup
方法的时候,会把注解中标注的接口的实现类当做参数传递进去。
我们看SpringServletContainerInitializer
的onStartup
方法,在容器启动的时候会调用这个方法,同时set
集合参数webAppInitializerClasses
即为@HandlesTypes
中标注的WebApplicationInitializer
的实现类
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
//省略其余代码
for (WebApplicationInitializer initializer : initializers) {
//依次回调参数实现类的onStartup方法
initializer.onStartup(servletContext);
}
}
最后一段代码initializer.onStartup(servletContext);
就是把所有的WebApplicationInitializer
的实现类的onStartup
方法调用一遍。我们看看这个类的所有实现类
看到实现类中有一个SpringBootServletInitializer
,这个类是我们要重点关注的对象,先来看看这个类的注释
也就是说这个类是当我们以war包的方式让外部tomcat运行时才需要关注的类。
我们接着看这个类的onStartup
方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
}
createRootApplicationContext
方法
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//其余代码略
return run(application);
}
run
方法
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
可以看到最终上诉会调用到SpringApplication的无参run方法,那么到了这一步,就跟我们通过main方法启动是一个道理了
public static void main(String[] args) {
SpringApplication.run(HppaApplication.class, args);
}
SpringBoot通过打成war包的方式运行,其本质上是利用了Servlet3.0规范中的Tomcat启动时会去调用ServletContainerInitializer
接口的onStartup
方法,同时把使用类注解@HandlesTypes
中标注的接口的实现类作为参数传入到onStartup
中,并依次调用其实现类的onStartup
方法。而SpringServletContainerInitializer
实现了ServletContainerInitializer
,同时标注了@HandlesTypes(WebApplicationInitializer.class)
,
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}
那么也就是说,Tomcat启动时,最终会去调用WebApplicationInitializer
的实现类的onStartup
方法,而SpringBootServletInitializer
实现了WebApplicationInitializer
public abstract class SpringBootServletInitializer implements WebApplicationInitializer
那也就是最终会调用SpringBootServletInitializer
的onStartup
方法,而这个onStartup
方法最终其实是调用了application.run()
,也就类似于我们通过Main方法启动了。为了方便理解,我画了下图
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。