写在前面

三年前,我在第一家公司的时候,还是用的原生SSM框架,用的是Double + zookeeper去实现“微服务”治理。 那时候发现市面上已经有很多开始用起了SpringBoot来搭建服务。第一印象,便是快捷、方便、不用外置tomcat,甩原生开发几条街(不由得想起了现在低代码时代)。

后来到现在的第二家公司,有幸用SpringBoot + SpringCloud治理微服务(所以后面我还会出SpringCloud相关的文章)。一用就是三年,有相当大的微服务治理心得和实战经验,扯的有点远了,话说回来,今天的主角SpringBoot

知其然,知其所以然,用了这么久,总得知道一些原理,前两天面了两个人,都答的不太好,今天我们来学习下。关于介绍SpringBoot的文章太多了,今天这里不多啰嗦了,应该没有人还不知道这是什么东东。我们今天主要是通过源码去了解启动背后的秘密

启动流程

为了故事的发展,我们先建一个SpringBoot服务

@SpringBootApplication
public class DemoApiApplication {

    public static void main(String[] args) {
        //run为启动入口
        ApplicationContext applicationContext = SpringApplication.
        run(DemoApiApplication.class, args);
    }
}    
复制代码

这行代码大家再了解不过了,更知道的是,这是启动入口,还有上面的SpringBootApplication注解,这个放到第二步讲,今天主要讲启动流程。

运行main函数,调用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
      
    //分为两步
    // 1、new SpringApplication(primarySources)
    // 2、run(args)
   return new SpringApplication(primarySources).run(args);
}
复制代码

先看SpringApplication的构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   
   //将启动类放入primarySources
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   
   //根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
   //SERVLET : 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。
   //REACTIVE : 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器
   this.webApplicationType = deduceWebApplicationType();
   
   // 去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
         
         
   //就是去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener      
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   
   // 根据main方法推算出mainApplicationClass
   this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

上面两个set太抽象,什么意思呢,其实是利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。

扩展:什么是SPI,全称为 Service Provider Interface(服务提供者接口),是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件 夹查找文件,自动加载文件里所定义的类。

  • ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
  • ApplicationListener 当springboot启动时事件change后都会触发

有没有人疑问扫描收集这两个接口,目的是什么,看到第二步run的时候 大家就会明白,靠这些监听去处理事情

new SpringApplication总结

上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,利用SPI机制主要加载了META-INF/spring.factories 下面定义的事件***接口实现类

也可以分为几个步骤讲:

  • 获取启动类
  • 获取web应用类型
  • 读取了对外扩展的ApplicationContextInitializer ,ApplicationListener
  • 根据main推算出所在的类

核心run方法

上面第一步看构造方法发现并没有什么,那肯定核心的在第二步,我们看启动springboot最核心的逻辑run方法

public ConfigurableApplicationContext run(String... args) {

   // 用来记录当前springboot启动耗时
   StopWatch stopWatch = new StopWatch();
   
   //记录了启动开始时间
   stopWatch.start();
  
   //关键类,它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   
   configureHeadlessProperty();
   
   // 去spring.factroies中读取了SpringApplicationRunListener 的组件, 就是用来发布事件或者运行***
   SpringApplicationRunListeners listeners = getRunListeners(args);
   
   // 发布1.ApplicationStartingEvent事件,在运行开始时发送
   listeners.starting();
   try {
   
      // 根据命令行参数 实例化一个ApplicationArguments
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
            
      // 很重要:::预初始化环境: 读取环境变量,读取配置文件信息(基于***)      
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      //忽略beaninfo的bean      
      configureIgnoreBeanInfo(environment);
      
      // 打印Banner 横幅,就是我们的启动logo
      Banner printedBanner = printBanner(environment);
      
      //根据webApplicationType创建Spring上下文
      //这个一会儿在下面再讲,主要就是内置tomcat
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
            
            
            
      //预初始化spring上下文      
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
            
      //加载spring ioc 容器相当重要 由于是使用AnnotationConfigServletWebServerApplicationContext 启动的sring容器所以springboot对它做了扩展:
      // 加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh      
      refreshContext(context);
      
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   //部分代码省略
   return context;
}
复制代码

上面的每行代码,每行解释,都要细细品一下,接下来我们把上面几个重要的点再深入下。预准备环境prepareEnvironment

预初始化环境prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(
      SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   
   //根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   
   // 将命令行参数读取环境变量中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   
   // 发布了ApplicationEnvironmentPreparedEvent 的*** 读取了全局配置文件
   listeners.environmentPrepared(environment);
   
   //将所有spring.main 开头的配置信息绑定SpringApplication
   bindToSpringApplication(environment);
   
   //判断web容器,装载,说白了 就是tomcat类型,还记上上面讲的web类型吧
   if (this.webApplicationType == WebApplicationType.NONE) {
      environment = new EnvironmentConverter(getClassLoader())
            .convertToStandardEnvironmentIfNecessary(environment);
   }
   
   // 更新PropertySources
   ConfigurationPropertySources.attach(environment);
   return environment;
}
复制代码

上面我们看到发布了很多监听事件去处理很多事情,第一步的时候初始化监听的意义就是所在

创建上下文 createApplicationContext

这段代码主要是根据项目类型创建上下文,并且会注入几个核心组件类

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            contextClass = Class.forName(DEFAULT_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);
      }
   }
   //注入几个核心上下文
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
复制代码

预初始化上下文prepareContext

private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   // 给上下文对象设置环境对象,给AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置环境对象
	context.setEnvironment(environment);
	// 1. 上下文后置处理,包括向BeanFactory中注册BeanNameGenerator和ConversionService
	postProcessApplicationContext(context);
	// 2. SpringApplication构造器中初始化了各种ApplicationContextInitializer,循环调用他们的initialize方法
	applyInitializers(context);
	// 3. 发送ApplicationContextInitializedEvent事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
	    // 打印启动信息,包括pid,用户等
		logStartupInfo(context.getParent() == null);
		// 答应profile信息
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	// 将ApplicationArguments注册到容器中
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
	    // 将Banner注册到容器中
	    beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
	    // 设置不允许定义同名的BeanDefinition,重复注册时抛出异常
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
	    // 如果懒加载,添加懒加载后置处理器
	    context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	// 发送ApplicationPreparedEvent事件
	listeners.contextLoaded(context);
}
复制代码

最后一步 最最重要的就是调用Spring的启动代码加载了bean

refreshContext

@Override
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.
         onRefresh();

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

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

         // Last step: publish corresponding event.
         finishRefresh();
      }
      //省略
复制代码

上面这个有没有很熟悉,就是Spring的初始化bean(启动容器),这里就不展开细讲。

说到这里 还有个小细节,那就是内置tomcat在哪,我们一起看下。

内置tomcat

上面说的Spring启动容器,这里主要看一下onRefresh() 这个方法。转到定义发现这个方法里面啥都没有,这明显是一个钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()

protected void onRefresh() throws BeansException {
    //这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,
    //所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()
    // For subclasses: do nothing by default.
}
复制代码

因为我们是web容器context,所以创建context的时候,createApplicationContext方法里有个ConfigurableApplicationContext接口 那么实现类即是ServletWebServerApplicationContext (上面有讲)


@Override
protected void onRefresh() {
   super.onRefresh();
   try {
   
      //看到web容器了,我们进去看下
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}
复制代码

上图中可以看到有jetty容器,tomcat容器,原来如此

@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);
}
复制代码

OK,今天的学习就到这里,最后我们先进行下简单总结

总结:

1. 初始化SpringApplication 从spring.factories 读取 listener ApplicationContextInitializer 。

2.运行run方法

3.读取 环境变量 配置信息.....

4. 创建springApplication上下文:ServletWebServerApplicationContext

5. 预初始化上下文 : 读取启动类

6.调用refresh 加载ioc容器

 01 加载所有的自动配置类
 02 创建servlet容器 
复制代码

7.在这个过程中springboot会调用很多***对外进行



原文链接:https://juejin.cn/post/7091646469001707557