SpringBoot深度探究(九)源码探究启动流程之三

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

前言

上一篇【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环境绑定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值