zoukankan      html  css  js  c++  java
  • SpringBoot启动时自动配置类的加载流程

      自定义过starter的同学应该都知道,自动配置类需要用 EnableAutoConfiguration  注解修饰,并且需要将自动配置类配置在spring.factories中。但自动配置类是如何被SpringBoot加载的呢?

      网上有些文章已经讲述了  EnableAutoConfiguration  注解类由  @Import(AutoConfigurationImportSelector.class)  修饰,已经SpringBoot是从spring.factories文件中读取自动配置类的。我这里结合SpringBoot的启动流程,记述一下自动配置类的加载过程。

      首先,从SpringBoot项目的启动类的SpringBootApplication.run(#,#)方法查看源码,可以看到最终执行的是以下代码:

     1 /**
     2      * Run the Spring application, creating and refreshing a new
     3      * {@link ApplicationContext}.
     4      * @param args the application arguments (usually passed from a Java main method)
     5      * @return a running {@link ApplicationContext}
     6      */
     7     public ConfigurableApplicationContext run(String... args) {
     8         StopWatch stopWatch = new StopWatch();
     9         stopWatch.start();
    10         ConfigurableApplicationContext context = null;
    11         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    12         configureHeadlessProperty();
    13         SpringApplicationRunListeners listeners = getRunListeners(args);
    14         listeners.starting();
    15         try {
    16             ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    17                     args);
    18             ConfigurableEnvironment environment = prepareEnvironment(listeners,
    19                     applicationArguments);
    20             configureIgnoreBeanInfo(environment);
    21             Banner printedBanner = printBanner(environment);
    22             context = createApplicationContext();
    23             exceptionReporters = getSpringFactoriesInstances(
    24                     SpringBootExceptionReporter.class,
    25                     new Class[] { ConfigurableApplicationContext.class }, context);
    26             prepareContext(context, environment, listeners, applicationArguments,
    27                     printedBanner);
    28             refreshContext(context);
    29             afterRefresh(context, applicationArguments);
    30             stopWatch.stop();
    31             if (this.logStartupInfo) {
    32                 new StartupInfoLogger(this.mainApplicationClass)
    33                         .logStarted(getApplicationLog(), stopWatch);
    34             }
    35             listeners.started(context);
    36             callRunners(context, applicationArguments);
    37         }
    38         catch (Throwable ex) {
    39             handleRunFailure(context, ex, exceptionReporters, listeners);
    40             throw new IllegalStateException(ex);
    41         }
    42 
    43         try {
    44             listeners.running(context);
    45         }
    46         catch (Throwable ex) {
    47             handleRunFailure(context, ex, exceptionReporters, null);
    48             throw new IllegalStateException(ex);
    49         }
    50         return context;
    51     }

      可以看到SpringBoot启动时的主要内容是创建Environment、ApplicationContext以及对应事件的发布。代码详情,这里不涉及。我们这关注SpringBoot是在哪一步加载自动配置类的。而这一步就是第28行的  refreshContext(context);  追踪 refreshContext(context); 可以发现,实际执行的代码是 AbstractApplicationContext 的 refresh 方法, 代码如下:

     1 @Override
     2     public void refresh() throws BeansException, IllegalStateException {
     3         synchronized (this.startupShutdownMonitor) {
     4             // Prepare this context for refreshing.
     5             prepareRefresh();
     6 
     7             // Tell the subclass to refresh the internal bean factory.
     8             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
     9 
    10             // Prepare the bean factory for use in this context.
    11             prepareBeanFactory(beanFactory);
    12 
    13             try {
    14                 // Allows post-processing of the bean factory in context subclasses.
    15                 postProcessBeanFactory(beanFactory);
    16 
    17                 // Invoke factory processors registered as beans in the context.
    18                 invokeBeanFactoryPostProcessors(beanFactory);
    19 
    20                 // Register bean processors that intercept bean creation.
    21                 registerBeanPostProcessors(beanFactory);
    22 
    23                 // Initialize message source for this context.
    24                 initMessageSource();
    25 
    26                 // Initialize event multicaster for this context.
    27                 initApplicationEventMulticaster();
    28 
    29                 // Initialize other special beans in specific context subclasses.
    30                 onRefresh();
    31 
    32                 // Check for listener beans and register them.
    33                 registerListeners();
    34 
    35                 // Instantiate all remaining (non-lazy-init) singletons.
    36                 finishBeanFactoryInitialization(beanFactory);
    37 
    38                 // Last step: publish corresponding event.
    39                 finishRefresh();
    40             }
    41 
    42             catch (BeansException ex) {
    43                 if (logger.isWarnEnabled()) {
    44                     logger.warn("Exception encountered during context initialization - " +
    45                             "cancelling refresh attempt: " + ex);
    46                 }
    47 
    48                 // Destroy already created singletons to avoid dangling resources.
    49                 destroyBeans();
    50 
    51                 // Reset 'active' flag.
    52                 cancelRefresh(ex);
    53 
    54                 // Propagate exception to caller.
    55                 throw ex;
    56             }

      关注第18行的  invokeBeanFactoryPostProcessors(beanFactory); ,这一步会执行实现了 BeanFactoryPostProcessor 接口的类;这个接口是Spring初始化bean时对外暴露的入口,它可以修改bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。追踪代码,可以看到实际执行的是 PostProcessorRegistrationDelegate  的  invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)  方法。这个方法比较复杂,分为了好几部分去处理,截取其中我们关心的部分即可(其实还包含了优先级、属性等类似处理过程)。

     1         // Do not initialize FactoryBeans here: We need to leave all regular beans
     2             // uninitialized to let the bean factory post-processors apply to them!
     3             // Separate between BeanDefinitionRegistryPostProcessors that implement
     4             // PriorityOrdered, Ordered, and the rest.
     5             List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
     6 
     7             // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
     8             String[] postProcessorNames =
     9                     beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    10             for (String ppName : postProcessorNames) {
    11                 if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    12                     currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
    13                     processedBeans.add(ppName);
    14                 }
    15             }
    16             sortPostProcessors(currentRegistryProcessors, beanFactory);
    17             registryProcessors.addAll(currentRegistryProcessors);
    18             invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    19             currentRegistryProcessors.clear();

      第18行的代码是我们要关注的,在这一步时,currentRegistryProcessors只有一个元素: ConfigurationClassPostProcessor ; 下一步执行的具体代码就是 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法;然后执行到 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 方法,这个方法内容比较多,也比较复杂,这里也只贴出我们需要关注的几行代码:

     1 // Parse each @Configuration class
     2         ConfigurationClassParser parser = new ConfigurationClassParser(
     3                 this.metadataReaderFactory, this.problemReporter, this.environment,
     4                 this.resourceLoader, this.componentScanBeanNameGenerator, registry);
     5 
     6         Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
     7         Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
     8         do {
     9             parser.parse(candidates);
    10             parser.validate();

      这里new了一个 ConfigurationClassParser 实例,并且执行了它的  parse(Set<BeanDefinitionHolder> configCandidates)  方法, 注意这里传入的参数虽然是一个Set,但实际只有我们的启动类一个元素。这个方法的代码如下:

     

     1 public void parse(Set<BeanDefinitionHolder> configCandidates) {
     2         for (BeanDefinitionHolder holder : configCandidates) {
     3             BeanDefinition bd = holder.getBeanDefinition();
     4             try {
     5                 if (bd instanceof AnnotatedBeanDefinition) {
     6                     parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
     7                 }
     8                 else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
     9                     parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    10                 }
    11                 else {
    12                     parse(bd.getBeanClassName(), holder.getBeanName());
    13                 }
    14             }
    15             catch (BeanDefinitionStoreException ex) {
    16                 throw ex;
    17             }
    18             catch (Throwable ex) {
    19                 throw new BeanDefinitionStoreException(
    20                         "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    21             }
    22         }
    23 
    24         this.deferredImportSelectorHandler.process();
    25     }

      在这个方法里,实际执行了第6行的代码。这里不是我们的目标,先不关注;注意第24行的代码,好像和我们看到了文章开头提到的 AutoConfigurationImportSelector 有点关联了,没错,这里就是实际加载自动配置类的地方。在这个process方法里,可以看到这里的importSelector元素就是  AutoConfigurationImportSelector ,到了这里我们有理由推测,SpringBoot是查找所有由 AutoConfigurationImportSelector 修饰的类来加载自动配置类的。继续往下追踪代码,关注 getImports 方法:

     1 /**
     2          * Return the imports defined by the group.
     3          * @return each import with its associated configuration class
     4          */
     5         public Iterable<Group.Entry> getImports() {
     6             for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
     7                 this.group.process(deferredImport.getConfigurationClass().getMetadata(),
     8                         deferredImport.getImportSelector());
     9             }
    10             return this.group.selectImports();
    11         }

      关注第7行内的process方法,点进去会发现这是 DeferredImportSelector 类的内部接口 Group 的接口方法,它的实现类有两个: AutoConfigurationImportSelector的内部类AutoConfigurationGroup  和  ConfigurationClassParser的内部类DefaultDeferredImportSelectorGroup ;这里执行到的代码是 AutoConfigurationGroup ,代码如下:

     1 @Override
     2         public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
     3             Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
     4                     () -> String.format("Only %s implementations are supported, got %s",
     5                             AutoConfigurationImportSelector.class.getSimpleName(),
     6                             deferredImportSelector.getClass().getName()));
     7             AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
     8                     .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
     9             this.autoConfigurationEntries.add(autoConfigurationEntry);
    10             for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    11                 this.entries.putIfAbsent(importClassName, annotationMetadata);
    12             }
    13         }

      可以看到,这里传入的参数必须是 AutoConfigurationImportSelector 实例,否则会抛出异常,所以EnableAutoConfiguration  注解类必须由  @Import(AutoConfigurationImportSelector.class)  修饰;进入第8行的 getAutoConfigurationEntry 方法:

     1 /**
     2      * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
     3      * of the importing {@link Configuration @Configuration} class.
     4      * @param autoConfigurationMetadata the auto-configuration metadata
     5      * @param annotationMetadata the annotation metadata of the configuration class
     6      * @return the auto-configurations that should be imported
     7      */
     8     protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
     9             AnnotationMetadata annotationMetadata) {
    10         if (!isEnabled(annotationMetadata)) {
    11             return EMPTY_ENTRY;
    12         }
    13         AnnotationAttributes attributes = getAttributes(annotationMetadata);
    14         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    15         configurations = removeDuplicates(configurations);
    16         Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    17         checkExcludedClasses(configurations, exclusions);
    18         configurations.removeAll(exclusions);
    19         configurations = filter(configurations, autoConfigurationMetadata);
    20         fireAutoConfigurationImportEvents(configurations, exclusions);
    21         return new AutoConfigurationEntry(configurations, exclusions);
    22     }

      从方法名可以看到这里有加载配置类候选的逻辑,有过滤掉不需要加载的逻辑。我们这关注加载配置候选的逻辑,关于过滤(即条件加载机制)可以参考这篇文章 https://blog.csdn.net/weixin_33915554/article/details/89702088 ; 进入第14行的 getCandidateConfigurations 方法,如果看过SpringBoot源码的同学,就会有一种熟悉的感觉了:

     1 /**
     2      * Return the auto-configuration class names that should be considered. By default
     3      * this method will load candidates using {@link SpringFactoriesLoader} with
     4      * {@link #getSpringFactoriesLoaderFactoryClass()}.
     5      * @param metadata the source metadata
     6      * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     7      * attributes}
     8      * @return a list of candidate configurations
     9      */
    10     protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    11         List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    12                 getBeanClassLoader());
    13         Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
    14                 + "are using a custom packaging, make sure that file is correct.");
    15         return configurations;
    16     }

      这里的 SpringFactoriesLoader.loadFactoryNames 方法,就是根据类名从spring.factories中查找对应的实现类名。我们还可以看一下 getSpringFactoriesLoaderFactoryClass 的代码:

    1 /**
    2      * Return the class used by {@link SpringFactoriesLoader} to load configuration
    3      * candidates.
    4      * @return the factory class
    5      */
    6     protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    7         return EnableAutoConfiguration.class;
    8     }

      没错,返回的就是 EnableAutoConfiguration.class 。好了,现在我们真实的从源码看到了一些文章说的:EnableAutoConfiguration  注解类由  @Import(AutoConfigurationImportSelector.class)  修饰, SpringBoot从spring.facories中加载自动配置类的说法。

      到这里为止,我们已经知道了SpringBoot获取自动配置类全限定名的过程。回到 ConfigurationClassParser.DeferredImportSelectorGroupingHandler的processGroupImports 方法,继续看拿到所有的自动配置类名后的操作,也就是forEach里面的内容,它解决一些循环依赖的问题,最终将所有的自动配置类信息都放到了 configurationClasses  中。 ConfigurationClassParser 的parse方法执行完毕后,回到 ConfigurationClassPostProcessor的processConfigBeanDefinitions 方法,继续看下面一部分代码。

     1 // Parse each @Configuration class
     2         ConfigurationClassParser parser = new ConfigurationClassParser(
     3                 this.metadataReaderFactory, this.problemReporter, this.environment,
     4                 this.resourceLoader, this.componentScanBeanNameGenerator, registry);
     5 
     6         Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
     7         Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
     8         do {
     9             parser.parse(candidates);
    10             parser.validate();
    11 
    12             Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    13             configClasses.removeAll(alreadyParsed);
    14 
    15             // Read the model and create bean definitions based on its content
    16             if (this.reader == null) {
    17                 this.reader = new ConfigurationClassBeanDefinitionReader(
    18                         registry, this.sourceExtractor, this.resourceLoader, this.environment,
    19                         this.importBeanNameGenerator, parser.getImportRegistry());
    20             }
    21             this.reader.loadBeanDefinitions(configClasses);
    22             alreadyParsed.addAll(configClasses);

      第21行的 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions 方法,将自动配置类解析成beanDefination,注册到 registry 中;看一下代码:

     1 /**
     2      * Read a particular {@link ConfigurationClass}, registering bean definitions
     3      * for the class itself and all of its {@link Bean} methods.
     4      */
     5     private void loadBeanDefinitionsForConfigurationClass(
     6             ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
     7 
     8         if (trackedConditionEvaluator.shouldSkip(configClass)) {
     9             String beanName = configClass.getBeanName();
    10             if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
    11                 this.registry.removeBeanDefinition(beanName);
    12             }
    13             this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
    14             return;
    15         }
    16 
    17         if (configClass.isImported()) {
    18             registerBeanDefinitionForImportedConfigurationClass(configClass);
    19         }
    20         for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    21             loadBeanDefinitionsForBeanMethod(beanMethod);
    22         }
    23 
    24         loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    25         loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    26     }

      自动配置类走的是第18行的逻辑,后续的功能主要是一些装配BeanDefination的工作,这里不再详述。

      

      到这里为止,SpringBoot加载自动配置类的逻辑结束。

  • 相关阅读:
    linux中编写同步文件的脚本
    SSH实现免密登录
    关于ISO 15765-2的解读
    设置Tera Term
    串口通信有极限速度
    三相永磁电机电流采样
    eclipse中F3快捷键无法跳转到定义的解决方法
    电脑和航模杂志和电子开发网站汇总
    MC9S08DZ60经典单片机
    STM32的SWD调试
  • 原文地址:https://www.cnblogs.com/zsxneil/p/14043443.html
Copyright © 2011-2022 走看看