springboot源码分析-装配过程

153 阅读6分钟

一、项目启动方法做了什么?

@SpringBootApplication
public class DemoymlApplication {

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

}

项目启动类中,可以看到DemoymlApplication类中只有两个需要注意的点

  1. main方法,在main方法中调用了SpringApplication.run(DemoymlApplication.class, args)。
  2. @SpringBootApplication注解。 根据这两点,一个一个进行分析。首先看看main方法中做了些什么东西。

1.1、SpringApplication.run(DemoymlApplication.class, args);做了什么?

1.1.1、main方法调用SpringApplication的静态run方法

main方法调用SpringApplication的静态run方法传递的参数是:当前启动类、main方法中的args。

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

静态run方法中,创建了类数组和args为参数,以此参数调用了另一个静态run方法,方便区分我就叫run2吧

1.1.2、run2方法创建了SpringApplication类并执行SpringApplication的run方法

源码
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

run2方法根据接收到的类数组(数组中只有启动类一个类),调用了SpringApplication构造方法,创建出SpringApplication类。接下来我们看看这个构造方法做了什么?

1.1.2.1、new SpringApplication(primarySources)构造方法

源码

public SpringApplication(Class<?>... primarySources) {
    this((ResourceLoader)null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

根据源码,看到这个构造方法需要两个参数:ResourceLoader(加载资源的策略接口)、类数组。ResourceLoader这个参数传的是null,类数组传的是只保护启动类的数组。可以看出这个构造方法给一些参数进行赋值。其中有两个需要注意一下。

  1. this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));获取初始化spring的回调接口实现类,并将这个回调接口实现类集合赋值给了initializers属性。
  2. this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));获取事件监听接口实现类,并把些类赋值给listeners属性。
  3. 初始化spring的回调接口实现类和事件监听接口实现类是从哪里来的呢?根据源码看到通过类加载器从各个包配置文件中获取
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
   try {
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

1.1.2.2、在创建 SpringApplication类后调用run方法

看看源码在这个run方法中做了什么

public ConfigurableApplicationContext run(String... args) {
    //创建秒表,记录run方法运行时间
   StopWatch stopWatch = new StopWatch();
   //开始设置时间
   stopWatch.start();
   //初始化应用上下文
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   设置java.awt.headless的值,默认为true
   configureHeadlessProperty();
   //获取并启动监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
   //初始化默认参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //项目运行环境的预配置
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      //创建spring容器
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      //spring容器前置处理
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      //刷新spring容器,接下来重点跟踪一下这个方法
      refreshContext(context);
      //spring容器后置处理
      afterRefresh(context, applicationArguments);
      //停止秒表,统计时长
      stopWatch.stop();
      //打印启动日志
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      //发布应用上下文信息
      listeners.started(context);
       //执行所有 Runner 运行器
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }

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

1.1.2.3、 refreshContext(context)方法做了什么?

  1. 注册一个关机钩子
context.registerShutdownHook();
@Override
public void registerShutdownHook() {
   if (this.shutdownHook == null) {
      // No shutdown hook registered yet.
      //创建一个以SpringContextShutdownHook为名的线程
      this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
         @Override
         public void run() {
            synchronized (startupShutdownMonitor) {
            //实际执行上下文关闭:发布一个ContextClosedEvent和销毁该应用程序上下文bean工厂中的单例。
               doClose();
            }
         }
      };
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}
  1. refresh((ApplicationContext) context)
@Deprecated
protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
   refresh((ConfigurableApplicationContext) applicationContext);
}
  1. refresh((ConfigurableApplicationContext) applicationContext);
protected void refresh(ConfigurableApplicationContext applicationContext) {
   applicationContext.refresh();
}
  1. applicationContext.refresh();刷新底层
@Override
public final void refresh() throws BeansException, IllegalStateException {
   try {
      super.refresh();
   }
   catch (RuntimeException ex) {
      WebServer webServer = this.webServer;
      if (webServer != null) {
         webServer.stop();
      }
      throw ex;
   }
}
  1. super.refresh();
@Override
public void refresh() throws BeansException, IllegalStateException {
    //获得锁
   synchronized (this.startupShutdownMonitor) {
       //创建DefaultStartupStep类
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

      // 准备用于刷新的上下文.
      prepareRefresh();

      // 告诉子类刷新内部bean工厂
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 配置工厂的标准上下文特征,比如上下文的ClassLoader和post-processor
      prepareBeanFactory(beanFactory);

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

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

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

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

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

1.1.3、 方法调用顺序图(部分)

程序执行的太多,就不一一跟踪了,这样太浪费时间,下面贴出调用方法的顺序

image.png image.png 根据这些方法的调用顺序,进行翻找,发现在ConfigurationClassParser.processImports方法中发现了自己写的类,由于自己写的类都加了@Controller、@Service、@RestController等spring的注解,因此被扫描到了,进行了bean注册。现在我们看看这些自己写的使用注解的类是在什么时候被扫描到的?一直往前翻找,发现这些类在run方法的context.branFactory中就已经出现,接下来我们看看是怎么出现在这里的。

image.png 根据这个图片可以看到,程序执行到这里时,自己写的类还没有被扫描到只有5个spring的类,根据断点调试,发现在执行完prepareContext方法后,项目的启动类被扫瞄到了,那么就到prepareContext方法中看看。 image.png 一步步分析启动类是如何加载到beanFactory中的。

  1. prepareContext方法中的这部分代码 image.png
  2. getAllSources如何获取到启动类的

image.png 根据源码发现是从字段中获取的primarySources,那么就要看看这个字段什么时候赋值的。发现在执行main方法时就带着这个类了,在创建SpringApplication类时就将启动类放到了primarySources字段中

  1. 如何获取启动类找到了,那么如何将启动类放入beanFactory中,这中间调用了很多方法,就不一一跟踪了,下面贴出方法调用顺序 image.png 在DefaultListableBeanFactory.registerBeanDefinition方法中将启动类放到了beanFactory中。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }

   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   if (existingDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
         }
      }
      else {
         // 还在启动注册阶段,在这里将启动类放入beanFactory中
         this.beanDefinitionMap.put(beanName, beanDefinition);
         this.beanDefinitionNames.add(beanName);
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

现在启动类加载解决了,加下来看看自己写的其他类是什么时候加载到beanFactory中的

1.1.4 获取其他类的过程

  1. 从beanFactory中获取启动类 这次获取的类是使用了@Component注解的类

image.png

image.png

image.png

image.png

image.png

image.png

image.png 上图获取的基础包就是启动类所在的包 image.png 扫描基础包

image.png 基础包下的文件都会获取到,如下图 image.png

image.png

image.png 断点执行过程如下图 image.png 将扫描到的类放入到beanFactory中 image.png 到这里,自己写的类已经放入到beanFactory中了,后面还有一些spring的其他类陆续放入到beanFactory中,就不继续追踪了。

1.1.5 配置文件什么时候获取的

image.png