SpringBoot--启动源码

196 阅读10分钟

前言

本文从spring启动源码开始解读,过程中附带一些额外spring相关的知识点.

1. new SpringApplication()

//1.main方法,spring开始的地方
public static void main(String[] args) {  
    SpringApplication.run(DemoApplication.class, args);  
}

//2.run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { 
    return run(new Class<?>[] { primarySource }, args);  
}

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

我们看一下new SpringApplication()

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 【1】给resourceLoader属性赋值,注意传入的resourceLoader参数为null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 【2】给primarySources属性赋值,传入的primarySources其实就是SpringApplication.run(MainApplication.class, args);中的MainApplication.class
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 【3】给webApplicationType属性赋值,根据classpath中存在哪种类型的类来确定是哪种应用类型,默认WebApplicationType.SERVLET,后续会在prepareEnvironment以及createApplicationContext方法里用到(想想也是,不同的应用类型,创建的环境和context肯定也不会一样)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 【4】★★给initializers属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationContextInitializer接口的实现类并赋值给initializers属性,后续在prepareContext()中调用
    setInitializers((Collection) getSpringFactoriesInstances(
    ApplicationContextInitializer.class));
    // 【5】★★给listeners属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationListener接口的实现类并赋值给listeners属性.ApplicationListener主要是用来实现各种applicationEvent的监听,需配合applicationRunListener发布事件
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 【6】给mainApplicationClass属性赋值,即这里要推断哪个类调用了main函数,然后再赋值给mainApplicationClass属性,用于后面启动流程中打印一些日志。
    this.mainApplicationClass = deduceMainApplicationClass();
}

1.1扩展 Spring通过SPI加载ApplicationContextInitializer和ApplicationListener

重点看下getSpringFactoriesInstances(ApplicationContextInitializer.class)方法

//type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {  
    return getSpringFactoriesInstances(type, new Class<?>[] {});  
}  
  
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { 
    //获取classloader,常规就是appclassloader
    ClassLoader classLoader = getClassLoader();  
    // 重点看下  SpringFactoriesLoader.loadFactoryNames
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));  
    // 用反射生成刚刚加载类的实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);  
    //排个序
    AnnotationAwareOrderComparator.sort(instances);  
    return instances;  
}

关键方法是SpringFactoriesLoader.loadFactoryNames(),就是在这里加载的spring.factories里的配置

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {  
    String factoryTypeName = factoryType.getName();  
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());  
}  
  
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //旁使用路缓存,避免多次加载
    MultiValueMap<String, String> result = cache.get(classLoader);  
    if (result != null) {  
        return result;  
    }  
  
    try {  
        Enumeration<URL> urls = (classLoader != null ?  
            //加载classpath下的META-INF/spring.factories
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :  
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));  
        result = new LinkedMultiValueMap<>();  
        while (urls.hasMoreElements()) {  
            URL url = urls.nextElement();  
            UrlResource resource = new UrlResource(url); 
            //读取到spring.factories内容
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);  
            for (Map.Entry<?, ?> entry : properties.entrySet()) {  
                String factoryTypeName = ((String) entry.getKey()).trim();  
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {  
                    result.add(factoryTypeName, factoryImplementationName.trim());  
                }  
            }  
        }  
        cache.put(classLoader, result);  
        return result;  
    }  
    catch (IOException ex) {  
        throw new IllegalArgumentException("Unable to load factories from location [" +  
                FACTORIES_RESOURCE_LOCATION + "]", ex);  
    }  
}

以下是spring.factories里的内容

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

其中initializer和listeners都是从spring.factories中加载(可能有多个spring.factories)

1.2 SPI机制

SPI(Service Provider Interface),JDK内置的服务发现机制,主要思想是解耦,将装配的控制权转移到程序之外,在模块化设计十分有用.

SPI流程:

  1. 有关组织和公式定义接口标准
  2. 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件
  3. 开发者使用

使用示例:


public interface SPIInterface {  
  
    public void helloWorld();  
  
}
//可以在各自的jar包中定义接口实现类
public class JavaSPIImpl implements SPIInterface {  
    @Override  
    public void helloWorld() {  
        System.out.println("JAVA helloworld");  
    }  
}

public class PHPSPIImpl implements SPIInterface {  
    @Override  
    public void helloWorld() {  
        System.out.println("PHP helloworld");  
    }  
}

在META-INF中新建services文件夹,新建spi接口类名文件(全路径),写入实现类. image.png

1.3 SPI使用场景

在我司一个外部项目中,有部分功能需要甲方实现,可以使用SPI机制让甲方写实现类,然后集成到我司项目中

提问:为啥不用接口调用?

回答:当然可以用接口调用,甲方提供接口,我们的代码中调用,但是此项目只是一个甲方一个外围项目,甲方不希望直接和他们的核心系统对接,所以这部分代码我们乙方基本不参与,也就是说甲方想直接自己二开该项目

提问:还有哪些场景可以用SPI吗

回答:SPI的场景使用确实不多,SPI产生年代久远,可能不太完全适用现在的开发要求,常规的场景是JDBC兼容各类厂家的数据库,还有一些就是项目中如果需要兼容多种中间件,可以使用spi,比如:FileStorageService对象存储,一般需要兼容阿里oss,华为OBS,七牛云.....万一以后又需要再兼容一套对象存储,可以使用类似JDBC的模式

2. run方法

public ConfigurableApplicationContext run(String... args) {  
    StopWatch stopWatch = new StopWatch();  
    stopWatch.start();  
    ConfigurableApplicationContext context = null;  
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();  
    //设置无显示参数,不用关心  
    configureHeadlessProperty();  
    //加载runlistener  
    SpringApplicationRunListeners listeners = getRunListeners(args);  
    //这里会向每个ApplicationListener发送一个ApplicationStartedEvent  
    listeners.starting();  
    try {  
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  
        //准备环境,通过发送environmentprepared的事件通知加载系统环境以及properties/yml文件  
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);  
        //根据配置spring.beaninfo.ignore 忽略一些bean,不重要  
        configureIgnoreBeanInfo(environment);  
        //打印banner  
        Banner printedBanner = printBanner(environment);  
        //创建applicationcontext,主要为beanfactory-DefaultListableBeanFactory  
        context = createApplicationContext();  
        //实例化SpringBootExceptionReporter,用于报告springboot启动的异常  
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,  
        new Class[] { ConfigurableApplicationContext.class }, context);  
        //核心重点1 ,刷新上下文的准备工作, 设置context的属性设置,bean对象的创建(包含Application的启动类创建)  
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);  
        //核心重点2 加载bean以及各种配置  
        refreshContext(context);  
        //刷新后处理,该方法是一个扩展方法,没有提供任何默认的实现,我们自定义的子类可以进行扩展。  
        afterRefresh(context, applicationArguments);  
        stopWatch.stop();  
        if (this.logStartupInfo) {  
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);  
        }  
        //应用SpringApplicationRunListener监听器的started方法  
        listeners.started(context);  
        //执行容器中的ApplicationRunner和CommandLineRunner类型的bean的run方法  
        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;  
}

3. 启动runListener

从spring.factories中加载SpringApplicationRunListener, 内部有一个initialMulticaster, 用于广播event给applicationListeners

// 【1】从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
// EventPublishingRunListener对象主要用来发射SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动SpringApplicationRunListener的监听,表示SpringApplication开始启动。
// 》》》》》发射【ApplicationStartingEvent】事件
listeners.starting();

3 prepareEnvironment

首先,准备环境变量,包含系统变量环境变量命令行参数、默认变量、servlet相关配置、配置文件

其次, 发射 environmentPrepared 事件

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		
		//new 了一个StandardServletEnviroment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 这里为之前创建的environment配置一些命令行参数形式的环境变量以及系统环境变量、JVM环境变量
		configureEnvironment(environment, applicationArguments.getSourceArgs()); 
		// 利用事件监听机制来为environment环境变量配置application.properties中的环境变量或@{}形式的环境变量
                //主要通过ConfigFileApplicationListener事件监听来加载配置properties及yml文件
		listeners.environmentPrepared(environment); 
		bindToSpringApplication(environment); // TODO 将environment绑定在SpringApplication,后续用来干嘛呢?
		if (!this.isCustomEnvironment) {
			// TODO 这里什么情况下需要进行转换?
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

listeners中有一个 ConfigFileApplicationListener ,主要用于配置properties以及yml文件

springboot2.2.9中配置文件的加载顺序为:

 * <ul>
 * <li>file:./config/</li>
 * <li>file:./</li>
 * <li>classpath:config/</li>
 * <li>classpath:</li>
 * </ul>

4. createApplicationContext

根据不同类型创建不同类型的spring applicationcontext容器 因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象 applicationContext使用反射的方式创建

//构造方法
public AnnotationConfigServletWebServerApplicationContext() {  
    //这里会注册一些beanDefinition,主要为注解增强器
    //1.internalConfigurationAnnotationProcessor处理@Component、@CompoenentScan等注解
    //2.internalAutowiredAnnotationProcessor处理@Autowired、@Inject、@Valued等注解
    //3.internalCommonAnnotationProcessor
    //4.internalEventListenerProcessor
    //5.internalEventListenerFactory
    this.reader = new AnnotatedBeanDefinitionReader(this);  
    this.scanner = new ClassPathBeanDefinitionScanner(this);  
}

image.png

AnnotationConfigServletWebServerApplicationContext.png

看下这个类图,其中关键的是AnnotationConfigServletWebServerApplicationContext间接集成BeanDefinitionRegistry、ResourceLoader、EnvironmentCapable以及BeanFactory等接口

  1. BeanDefinitionRegistry: 主要是beandefinition的注册,获取,增删等功能
  2. ResourceLoader: 主要为资源读写
  3. BeanFactory: bean的获取,移除等
  4. EnvironmentCapable: 环境变量读写能力

可以看出ApplicationContext 就是个大杂烩,集合多方功能,因为其采用了门面模式

5.获取SpringBootExceptionReporter

从spring.factories配置文件中加载异常报告期实例,这里加载的是FailureAnalyzers 注意FailureAnalyzers的构造器要传入ConfigurableApplicationContext,因为要从context中获取beanFactory和environment

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

6. ★★ prepareContext

为刚创建的AnnotationConfigServletWebServerApplicationContext容器对象做一些初始化工作,准备一些容器属性值等

  1. 为AnnotationConfigServletWebServerApplicationContext的属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置environgment属性
  2. 根据情况对ApplicationContext应用一些相关的后置处理,比如设置resourceLoader属性等
  3. 在容器刷新前调用各个ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在构建SpringApplication对象时从spring.factories中加载的
  4. 》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好
  5. 从context容器中获取beanFactory,并向beanFactory中注册一些单例bean,比如applicationArguments,printedBanner
  6. TODO 加载bean到application context,注意这里只是加载了部分bean比如mainApplication这个bean
  7. 》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成
	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,  
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {  
        //设置环境变量  
        context.setEnvironment(environment);  
        //增强applicationcontext,就是设置bean名称生成器、资源加载器以及转换器  
        postProcessApplicationContext(context);  
        //应用初始化器,之前有加载的  
        applyInitializers(context);  
        //发射事件 contextPrepared  
        listeners.contextPrepared(context);  
        //是否打印启动相关日志,默认true  
        if (this.logStartupInfo) {  
            logStartupInfo(context.getParent() == null);  
            logStartupProfileInfo(context);  
        }  
        // 获取此上下文内部的beanFactory,即bean工厂  
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();  
        //将applicationArguments作为一个单例对象注册到bean工厂中  
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);  
        if (printedBanner != null) {  
            beanFactory.registerSingleton("springBootBanner", printedBanner);  
        }  
        // 默认就是DefaultListableBeanFactory  
        if (beanFactory instanceof DefaultListableBeanFactory) {  
            //设置是否允许同名bean的覆盖,这里默认false,如果有同名bean就会抛出异常  
            ((DefaultListableBeanFactory) beanFactory)  
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);  
        }  
        //设置是否允许懒加载,即延迟初始化bean,即仅在需要bean时才创建该bean,并注入其依赖项。  
        //默认false,即所有定义的bean及其依赖项都是在创建应用程序上下文时创建的。  
        if (this.lazyInitialization) {  
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());  
        }  
        // 加载启动源,默认就是我们的启动类  
        Set<Object> sources = getAllSources();  
        Assert.notEmpty(sources, "Sources must not be empty");  
        /*  
        * 加载beanDefinition,最终执行的是BeanDefinitionLoader
        * 加载所有的beanDefinition
        */  
        load(context, sources.toArray(new Object[0]));  
        //发射contextloaded事件  
        listeners.contextLoaded(context);  
}

7. ★★★★ refreshContext

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // 1.在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等
      prepareRefresh();

      // 2.让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样,springboot是直接获取beanfactory,因为在springboot启动过程中createApplicationContext已经初始化了beanFactory
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 3.对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等
      prepareBeanFactory(beanFactory);

      try {
         // 4.执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置;在springBoot里设置了一个awareProcessor
         postProcessBeanFactory(beanFactory);
         
         //----------------- 至此beanFactory的所有准备工作均已完成-------

         // 5.执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器; 加载所有的beanDefinition; springBoot中从启动Application类入手循环解析@component,内部类bean,扫描componentScan标识的包,引入@import类(触发registra以及selectImportor),最后处理@bean;
         invokeBeanFactoryPostProcessors(beanFactory);
         
         //----------------- 核心扫描beanDefinination结束-------

         // 6.注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor
         registerBeanPostProcessors(beanFactory);

         // 7. 初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等
         initMessageSource();

         // 8. 初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中
         initApplicationEventMulticaster();

         // 9.AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器
         onRefresh();

         // 10.注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events
         registerListeners();
         
         // 11.完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。
         finishBeanFactoryInitialization(beanFactory);

         // 12.完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件
         finishRefresh();
         
          //----------------- bean的初始化结束-------
      }

      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();
      }
   }
}

8. afterRefresh

后置增强方法,目前为空

9. 发射started事件

发射【ApplicationStartedEvent】事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕

listeners.started(context);

10. 调用Runner

调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西比如加载一些业务数据等

	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}