springboot启动流程源码分析(一)

springboot启动流程源码分析(一)

前言:springboot相信基本上所有的人都使用过,但是对于一些初学者可能只是知道如何使用,但是对于它实现的原理不太熟悉,今天跟大家一起去分析下它的启动源码。其实也是比较简单,相信通过这篇文章能对一些初学者有一些帮助,在学习这篇文章之前最好有spring的基础知识。

一、引入问题

今天我们是以web应用为例来分析springboot的启动原理,首先我们看如下的代码

@SpringBootApplication
public class HellobootApplication {
   

   public static void main(String[] args) {
   
      SpringApplication.run(HellobootApplication.class, args);
   }

}

我们知道当我们run这个main方法时,web应用程序就能启动。那么首先请大家思考如下问题

1、springboot如何和spring容器关联上
2、我们没有看到有tomcat容器,为什么能够支撑起web应用
3、当我们引用第三方的starter时,为什么会自动实例化一些类,我们并没有扫描到第三方的包,甚至我们对第三方的包的路径都不知道

带着上面三个问题,我们一起来看下springboot的启动的原理吧

二、源码分析(1)

首先分析上面第一个问题(springboot如何和spring容器关联上)

SpringApplication#run()

public static ConfigurableApplicationContext run(Class<?> primarySource,
      String... args) {
   
    //看这里
   return run(new Class<?>[] {
    primarySource }, args);
}

run()

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
   
    //看这个run方法
   return new SpringApplication(primarySources).run(args);
}

继续记者往下看

run()

public ConfigurableApplicationContext run(String... args) {
   
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
   
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
       //从这个方法名字看,大概就能猜测出来意思了,这里就是创建spring的context
      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);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
   
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
   
      listeners.running(context);
   }
   catch (Throwable ex) {
   
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

这个方法,我们重点看两个地方

(1)createApplicationContext()

(2)refreshContext(context)

如果对spring源码有了解的话,我们大概能猜到这两个方法的意思,第一个时创建spring上下文,第二个就是刷新上下文,这两个地方其实就实现了springboot–>spring容器

比如我们单元测试的时候,经常编写如下代码进行启动spring容器

ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("xxx.xml")

1、我们先看createApplicationContext()方法

protected ConfigurableApplicationContext createApplicationContext() {
   
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
   
      try {
   
         switch (this.webApplicationType) {
   
         case SERVLET:
            //重点看加载的这个class的路径
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
   
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, "
                     + "please specify an ApplicationContextClass",
               ex);
      }
   }
   //后面就是通过反射创建ApplicationContext上下文了,后面不再继续跟了
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

这里我们重点看一下加载的是哪个ApplicationContext的上下文,因为ApplicationContext有很多子类,我们看下常量(DEFAULT_SERVLET_WEB_CONTEXT_CLASS)对应的class是哪个

/** * The class name of application context that will be used by default for web * environments. */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
      + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

所以这里创建的ApplicationContext的上下文是AnnotationConfigServletWebServerApplicationContext,这里很重要,大家先记一下(不同的ApplicationContext有些方法实现的细节是不一样的)

2、接着看上面的refreshContext(context)

private void refreshContext(ConfigurableApplicationContext context) {
   
   //继续看该方法
    refresh(context);
   if (this.registerShutdownHook) {
   
      try {
   
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
   
         // Not allowed in some environments.
      }
   }
}

refresh()

protected void refresh(ApplicationContext applicationContext) {
   
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   //这里就调用了spring的context包中的容器刷新了
   ((AbstractApplicationContext) applicationContext).refresh();
}

分析到这里,相信大家都知道springboot如何和spring容器相关联的了

三、源码分析(2)

上面介绍了springboot如何与spring容器进行关联的,接着我们看web应用时,我们没有将应用放入tomcat中时,为何能实现web应用

上面有一个地方,我让大家重点记住创建ApplicationContext时,到底是创建了哪个ApplicationContext的子类,其实是创建了AnnotationConfigServletWebServerApplicationContext那么我们来看下ApplicationContext.refresh()方法

AbstractApplicationContext.refresh()

public void refresh() throws BeansException, IllegalStateException {
   
   synchronized (this.startupShutdownMonitor) {
   
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
   
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         //咱们重点看这里,上面的英文注释写的也很详细,可以在子类中实例化其他bean
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
   
         if (logger.isWarnEnabled()) {
   
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
   
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

这个方法我相信对spring源码有了解的人都非常熟悉这个方法,这个方法就是spring代码的入口,非常重要。这里其实是实现了模板方法设计模式,这里是定义了基本的骨架,有些方法可以在不同的子类有不同的实现,我们上面看到创建的springboot的web应用程序默认创建的是AnnotationConfigServletWebServerApplicationContext,我们来看下它的onRefresh()方法(在他的父类ServletWebServerApplicationContext中)

ServletWebServerApplicationContext.onRefresh()

@Override
protected void onRefresh() {
   
   super.onRefresh();
   try {
   
       //重点看这里
      createWebServer();
   }
   catch (Throwable ex) {
   
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

咱们重点看createWebServer(),这里从方法名就能看出创建web服务

private void createWebServer() {
   
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
   
      ServletWebServerFactory factory = getWebServerFactory();
       //创建web服务在这里,这里是工厂方法
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if (servletContext != null) {
   
      try {
   
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
   
         throw new ApplicationContextException("Cannot initialize servlet context",
               ex);
      }
   }
   initPropertySources();
}

在这里插入图片描述

上面是工厂方法,有Jetty和tomcat,我们这里点tomcat进去看看

TomcatServletWebServerFactory.getWebServer()

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory
         : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   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);
   return getTomcatWebServer(tomcat);
}

这里相信大家就很清晰了,这里自己创建了一个Tomcat,最后会调用tomcat.start()这里我就不继续往下跟了,感兴趣的可以继续往下看看。另外如果感兴趣的同学可以看看这里的Tomcat的设置和我们下载的tomcat中的server.xml文件进行对比一下,看看有什么相似的地方

这篇文章就先分析到这里吧,还留了一个最后一个问题(如何自动加载第三方的starter),我们到下一篇文章再详细的分析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111213.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 学生选课管理系统的数据库设计_选课系统数据库设计

    学生选课管理系统的数据库设计_选课系统数据库设计学生选课管理系统的设计为选课及成绩管理提供了一个良好的工具,此系统基本实现了设计的要求,即登录系统(通过用户名和密码),管理(录入、查询、修改和删除)学生、课程基本资料,管理(录入、查询、修改和删除)学生所选课程成绩,统计每个学生的总分、平均分以及排名,修改用户密码等功能。在此基础上,我还对系统进行了改进,特别是加强了查询的功能,使我们能更直观、有效地查询到我们想要的数据。

    2022年10月15日
  • 工作流引擎Activiti系列(一)——初识[通俗易懂]

    工作流引擎Activiti系列(一)——初识[通俗易懂]1、介绍  几乎任何一个公司的软件开发都会涉及到流程,以往我们可能是这么实现的:业务表添加标志位标识流程的节点状态,关联批注表实现审核意见,根据一些业务数据分析处理逻辑,分配任务到用户,节点的调度,审批等…..这其实是很繁琐的,且不说开发起来比较混乱,维护起来更是难上加难:     Activiti刚好就能解决几乎所有的这些问题,当流程开发变得简单有趣。  官网:

    2022年10月27日
  • 服务器网卡配置_如何设置在不同的网段访问服务器

    服务器网卡配置_如何设置在不同的网段访问服务器配置详解配置DNS修改对应网卡的DNS的配置文件# vi /etc/resolv.conf修改以下内容,可以设置多个:nameserver 202.106.0.

  • matlab 矩阵除法

    matlab 矩阵除法Matlab提供了两种除法运算:左除(/)和右除(/)。一般情况下,x=a/b是方程a*x=b的解,而x=b/a是方程x*a=b的解。例:a=[1  2  3;4  2  6;7  4  9]b=[4;1;2];x=a/b则显示:x=      -1.5000        2.0000        0.5000如果a为非奇异矩阵,则a/b和b/a可通过a的逆矩阵与

  • redis客户端连接工具连接docker里面redis_gbase客户端连接工具

    redis客户端连接工具连接docker里面redis_gbase客户端连接工具Redis客户端连接工具AnotherRedisDesktopManagermac想用到brew的话,地址:https://www.jianshu.com/p/b7b789a2ed2cAnotherRedisDesktopManager为redis可视化工具,真的巨好用呀!!!原文地址:https://blog.csdn.net/huizhou_achao/article/details/108467792下载及安装教程地址:https://github.com/qishibo/An

  • pycharm 使用docker 配置环境「建议收藏」

    pycharm 使用docker 配置环境「建议收藏」前提:windowspycharm使用docker中的python3搭建运行环境配置环境:pycharmversion:2018.1.2docker18.03.1-ce-win65windows10专业版启动dockerdockerpullpython3##downloadpython3容器默认latest版本无需运行该容器,若需要安装…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号