前言
上一篇【SpringBoot深度探究(八)源码探究启动流程之二】主要说了SpringBoot中如何应用的观察者模式,如何使用广播引起相关监听器兴趣的以及监听器的反应。本篇将会探究SpringBoot是在何时启动的Tomcat以及相关Web环境初始化的。更多Spring内容进入【Spring解读系列目录】。
SpringApplication.run
接着上篇的run()方法里面的内容,从try-catch块讲起。
public ConfigurableApplicationContext run(String... args) {
/**上篇已经说过**/
listeners.starting();
try {
//准备参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//这里就会发布一个prepareEnvironment事件进行广播
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印标签
Banner printedBanner = printBanner(environment);
//new了一个ConfigurableApplicationContext对象,
context = createApplicationContext();
//错误报告
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 启动准备
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新, 非常重要
refreshContext(context);
//这里什么都没有是空方法
afterRefresh(context, applicationArguments);
stopWatch.stop(); //计时器停止
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布ApplicationStartedEvent事件
listeners.started(context);
//异步启动一些其他流程,不重要
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//发布ApplicationReadyEvent事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
接着listeners.starting()往下走,首先是准备参数ApplicationArguments,这里的参数是main()方法中传递进来的参数。然后发布一个prepareEnvironment事件进行广播,这里所有对这个事件感兴趣的Listener都会执行onApplicationEvent()的逻辑,怎么挑选Listener上一篇已经说过了。再接着就是打印标签,SpringBoot启动时输出的那个大字头就是这里打印的。然后到了一个比较重要的点:context = createApplicationContext()。相当于我们在SpringMVC中些的AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(),但是具体要是用哪个context对象还需要去里面根据之前的推断去做选择,第一篇源码讲解已经说过SpringBoot对项目类型做了推断。
创建上下文Context
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try { //开始根据推断去判断要使用哪个Context类进行实例化
switch (this.webApplicationType) {
case SERVLET: //AnnotationConfigServletWebServerApplicationContext这个就是AnnotationConfigWebApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE: //AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default: //AnnotationConfigApplicationContext 这个就是java的Context
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
//根据上面取得的类名进行反射,返回上下文应用
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
可以看到这里就是在SpringApplication构造方法里面推断的类型,是Web项目就必然是DEFAULT_SERVLET_WEB_CONTEXT_CLASS这个推断,于是就会走到这个分支使用AnnotationConfigWebApplicationContext进行初始化。确定好以后根据类名反射生成相应的类对象出去。
返回SpringApplication.run
继续run()方法,exceptionReporters是生成错误报告的。接着prepareContext()准备上下文,发布事件,准备环境,是否要加参数之类的,同时这里又发布了一个ApplicationContextInitializedEvent事件。在下一行refreshContext(context)就是一个非常重要的方法,这里面就是SpringMVC里调用的refresh()方法,此时Spring可以说就已经启动了,同时Tomcat也是在这里面启动的。
SpringApplication.refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
//直接到refresh()方法
refresh(context);
}
几乎没有什么重要的内容,接着到refresh(context)里面。
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//强转为父类方法执行
((AbstractApplicationContext) applicationContext).refresh();
}
这里强转为父类执行refresh()方法,因为applicationContext此时已经是非常具体的Context类。如果直接执行必然会执行到子类里面去,但是它的所有子类都没有重写refresh()方法,因此这里只能看做一个安全保证。在其中会调用onRefresh()方法,由于AbstractApplicationContext父类是空实现,因此又会调用到子类的方法里。因为使用的就是WebServlet类型的项目,所以就以ServletWebServerApplicationContext.onRefresh()为例,跳转过去看里面的内容。
AbstractApplicationContext.refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//准备工作,包括设置启动时间,是否激活标识位,初始化属性源配置(property source)
prepareRefresh();
// 这里获取的是一个DefaultListableBeanFactory的实例也就是初始化一个bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备bean工厂
prepareBeanFactory(beanFactory);
try {
//这里面也是什么都没有,会由相关子类实现,不重要
postProcessBeanFactory(beanFactory);
//在Spring的环境中去执行已经被注册的factory bean post processors后置处理器
// 设置执行自定义的ProcessorBeanFactory
invokeBeanFactoryPostProcessors(beanFactory);
//注册bean后置处理器。
registerBeanPostProcessors(beanFactory);
// 国际化,不重要
initMessageSource();
// spring的event事件,初始化应用事件广播器
initApplicationEventMulticaster();
//空方法,用来子类实现
onRefresh();
// 监听器的注册
registerListeners();
//在此之前,我们的类并没有进行实例化,上面的内容是把所有的bean变成BeanDefinition然后放到到BeanDefinitionMap中,
// 其中有些BeanPostProcessor被提前拿出来进行初始化为了进一步操作。其余的内容仍然没有初始化。
// 这些剩下的内容一般特指我们自己创建的service,controller,dao等等这些bean
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
/**后续无关,略**/
}
笔者在这个方法里,做了一些简单的注释。这个refresh()方法基本上就已经涵盖了整个SpringBean的生命周期的进程,包括怎么生成BeanDefinition,怎么实例化Bean,怎么处理后置处理器等等。多数方法拿出来就能写好几个博客,以后有时间单开博客说,不能偏离主题。直接看onRefresh(),既然说的是一个WebSelvet项目,实现就是ServletWebServerApplicationContext类里面的方法,直接进去。
ServletWebServerApplicationContext#onRefresh
protected void onRefresh() {
super.onRefresh();
try {
//在此创建Tomcat,并且启动。
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
里面只有一个方法createWebServer()直接进入。
private void createWebServer() {
//这里开始创建WebServer,
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//构造一个web工厂
ServletWebServerFactory factory = getWebServerFactory();
//进入这里,调用工厂方法,getWebServer()
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try { //如果发现不是null就使用SPI技术
//直接调用onStartup()方法
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
进入方法,由于是初次创建,这里都是null,所以会先进入if (webServer == null && servletContext == null)条件块里。首先构建一个Web工厂,然后使用工厂对象调用getWebServer()去进行Tomcat的初始化和启动等等步骤。如果发现if (servletContext != null),那么直接调用onStartup()方法,使用了Service Provider Interface(简称SPI)技术。这部分以及下面Tomcat内容在【SpringBoot模拟探究(一)如何启动Tomcat】中有详细讲解。到这里是由TomcatServletWebServerFactory实现的具体方法,所以需要进入TomcatServletWebServerFactory.getWebServer()里面。
配置Tomcat
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}//new 一个tomcat对象
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//在设置完各种属性以后,在getTomcatWebServer(tomcat)里面启动tomcat
return getTomcatWebServer(tomcat);
}
可以看出getWebServer()方法主要对Tomcat的各种配置,比如connector,配置host,port等等,启动就是在getTomcatWebServer(tomcat)里面了。
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
//继续进入new TomcatWebServer构造方法
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
//调用initialize()
initialize();
}
启动Tomcat
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
// 调用tomcat.start()启动tomcat
this.tomcat.start();
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// 在这里使用另一个线程开启服务等待
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
一路点进去,没什么可说的。唯一要提醒的地方就是startDaemonAwaitThread()方法,我们知道Tomcat开启以后需要一直挂着等待服务端接入。所以tomcat.getServer().await()的方法就写在startDaemonAwaitThread()里面,只不过调用了另一个线程执行了。
private void startDaemonAwaitThread() {
//使用线程开启tomcat.getServer().await()等待请求接入
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
TomcatWebServer.this.tomcat.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
总结
本篇基本上已经解决了SpringBoot从初始化ServletWebServerApplicationContext到启动Tomcat的流程。相当于我们已经完成了AnnotationConfigWebApplicationContext初始化以及刷新功能的解析,也完成了Tomcat启动的解析。但是还有一点没有说清楚,那就是DispatcherServlet是如何于Spring绑定的,在SpringMVC中我们可以直接使用new DispatcherServlet(context)就可以做到绑定,但是SpringBoot是怎么做到呢?我们下一篇就会简述一下DispatcherServlet是如何在SpringBoot开始运行的时候于Spring环境绑定的。

本文深入探讨了SpringBoot的启动流程,从`SpringApplication.run`方法开始,详细阐述了参数准备、环境广播、上下文创建、错误报告、上下文刷新等步骤。特别指出,`refreshContext`方法包含了Spring Bean的完整生命周期,包括BeanDefinition的生成、Bean的实例化和后置处理器的处理。在`createWebServer`方法中,初始化并启动了Tomcat,通过`TomcatServletWebServerFactory`创建WebServer,完成端口配置、Connector设置等。最后,`initialize`方法调用`tomcat.start()`启动Tomcat。文章末尾预告了下篇将讨论DispatcherServlet如何与Spring环境绑定。
484

被折叠的 条评论
为什么被折叠?



