SpringBoot的自动装配机制(最新SpringBoot 2.6.3)

​SpringBoot3.0都要出了,据说JDK最低要求17???我滴乖乖,JDK8还没整明白呢,先学习一下SpringBoot压压惊。

一、什么是SpringBoot

官方描述:

翻译:

通过Spring Boot,可以轻松地创建独立的,基于生产级别的基于Spring的应用程序,并且可以“运行”它们其实Spring Boot的设计是为了让你尽可能快的跑起来Spring应用程序并且尽可能减少你的配置文件。

二、SpringBoot的特征

①SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中。

②使编码变得简单,SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,极大地提高了工作效率。

③自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的bean并自动化配置他们。

④使部署变得简单,SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow。我们只需要一个Java的运行环境就可以跑SpringBoot的项目,SpringBoot的项目可以打成一个jar包。

三、SpringBoot的项目搭建

学习SpringBoot的自动装配必然从Spring的主入口类开始进行。建立一个SpringBoot的测试项目SpringBootTest,并导入SpringMVC的起步依赖,观察如何进行的自动装配。

@RestControllerpublic class TestController {    @RequestMapping("/test")    public String testMethod() {        return "测试SpringBoot";    }}

四、SpringBoot的自动装配机制

    想要分析SpringBoot的自动装配机制,必然要从SpringBoot的主启动类入手,主启动类只有一个run方法和一个@SpringBootApplication注解,我们先分析一下@SpringBootApplication注解,方便后续源码的解读。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { 
  
  /**
  * 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
  */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};
​
  /**
  * 根据classname来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
  */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};
​
  /**
  * 指定扫描包,参数是包名的字符串数组。
  */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
  
  /**
  * 扫描特定的包,参数类似是Class类型数组。
  */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  Class<?>[] scanBasePackageClasses() default {};
}

其中@Target、@Retention、@Documented、@Inherited是自定义注解的四个元注解。

@Target:表示注解的适用范围

@Retention:表示注解的生命周期

@Document:表示注解可以记录在javadoc中

@Inherited:表示子类可以继承该注解

@SpringBootConfiguration:表示该类为一个配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration  //实际上就是一个配置类
@Indexed
public @interface SpringBootConfiguration {
  ...
}

@EnableAutoConfigration:启动自动装配功能

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  ...
}
​
​
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
  ...
}

在EnableAutoConfiguration中有两个注解分别使用了@Import注解导入相应的类。@Import注解是Spring提供的注解,用来导入配置类或者一些需要前置加载的类。

@ComponentSacn:扫描路径,默认扫描该类的包及其子包
五、run方法的执行流程

简单介绍完@SpringBootApplication注解后,我们开始介绍SpringBoot的run方法。

Tips:本文采用SpringBoot2.6.3。小伙伴可以自身调整,但总体流程变化不大。可以边看边跟着一步一步Debug。

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

我们可以看到执行run方法有两个步骤:第一步实例化SpringApplication对象,第二步执行run方法。

(1)实例化SpringApplication:

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}
​
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  //设置资源加载器为null
  this.resourceLoader = resourceLoader;
  //断言资源类不可以为null 此时使我们传递的SpringbootTestApplication.class
  Assert.notNull(primarySources, "PrimarySources must not be null");
  //封装到一个linkedHashSet中
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  //推断当前的应用类型 一般都是servlet
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  //初始化classpath下 META-INF/spring.factories中已配置的
  //key是BootstrapRegistryInitializer的类
  this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  //初始化classpath下 META-INF/spring.factories中已配置的
  //key是ApplicationContextInitializer的类
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  //初始化classpath下 META-INF/spring.factories中已配置的
  //key是ApplicationListener的类
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //根据调用栈,推断出 main 方法的类名
  this.mainApplicationClass = deduceMainApplicationClass();
}

在SpringApplication的构造方法中,有一个最重要的方法就是:getSpringFactoriesInstances,最终会调用loadSpringFactories拿到所有的key和value封装到一个Map<String, List<String>>容器中,通过想要的key获取所有的value,然后创建Spring的工厂实例排序后返回。

查看一下META-INF下的spring.factories文件,就是key和value组成的键值对。

我们可以Debug看一下loadSpringFactories执行结果

(2)执行run方法

/**
 * 运行spring应用,并刷新一个新的ApplicationContext(Spring的上下文)
 * ConfigurableApplicationContext是ApplicationContext接口的子接口。
 * 在ApplicationContext基础上增加了配置上下文的工具。
 */
public ConfigurableApplicationContext run(String... args) {
  //记录程序运行时间
  long startTime = System.nanoTime();
  //创建一个默认的上下文
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  //创建spring的上下文
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  //从META-INF/spring.factories中获取监听器
  //key为 SpringApplicationRunListener.class
  SpringApplicationRunListeners listeners = getRunListeners(args);
  //启动监听器
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    //构造应用上下文环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    //处理需要忽略的bean
    configureIgnoreBeanInfo(environment);
    //打印banner 就是SpringBoot的启动图标,可以自定义
    Banner printedBanner = printBanner(environment);
    //初始化应用上下文
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    //刷新应用上下文前的准备阶段
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    //刷新应用上下文
    refreshContext(context);
    //刷新应用上下文后的扩展接口 模板模式的体现,此处是空实现
    afterRefresh(context, applicationArguments);
    Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
    }
    //发布容器启动完成事件
    listeners.started(context, timeTakenToStartup);
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
    listeners.ready(context, timeTakenToReady);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, null);
    throw new IllegalStateException(ex);
  }
  return context;
}

在run方法中,大致分为六步:获取并启动监听器、构造应用上下文环境、初始化应用上下文环境、刷新应用上下文文前的准备工作、刷新应用上下文、刷新应用上下文后的扩展方法。下面我们分别进行分析:

①获取并启动监听器getRunListeners。

事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。

private SpringApplicationRunListeners getRunListeners(String[] args) {
  Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  return new SpringApplicationRunListeners(logger,
      getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
      this.applicationStartup);
}

观察发现还是调用getSpringFactoriesInstances方法,对META-INF的spring.factories文件进行读取。

②构建应用上下文环境prepareEnvironment。

包括计算机的环境,Java环境,Spring的运行环境,还有咱们自己的application.yml(application.properties)配置文件

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
  // Create and configure the environment
  //创建并配置相应的环境
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  //根据用户配置,配置environment系统环境 就是封装一下我们传入的args  configureEnvironment(environment, applicationArguments.getSourceArgs());
  ConfigurationPropertySources.attach(environment);
  //启动相应的监听器,其中最重要的
  //EnvironmentPostProcessorApplicationListener加载配置文件
  listeners.environmentPrepared(bootstrapContext, environment);
  DefaultPropertiesPropertySource.moveToEnd(environment);
  Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
      "Environment prefix cannot be set via properties.");
  bindToSpringApplication(environment);
  if (!this.isCustomEnvironment) {
    environment = convertEnvironment(environment);
  }
  ConfigurationPropertySources.attach(environment);
  return environment;
}

我们Debug来看一下效果。

没有执行listeners.environmentPrepared

执行完之后,多了我们的配置文件中的属性。

在启动监听器时,

SpringBoot2.4.0之后废弃了ConfigFileApplicationListener,3.0就会被移除。

/**
 * @deprecated since 2.4.0 for removal in 3.0.0 in favor of
 * {@link ConfigDataEnvironmentPostProcessor}
 */
@Deprecated
public class ConfigFileApplicationListener

然后改用了EnvironmentPostProcessorApplicationListener来加载我们的配置文件。小伙伴可以在此处进行深入Debug探究,此处就主线来说已经结束了。

③初始化应用上下文createApplicationContext。

在SpringBoot工程中,应用类型分为三种,如下代码所示。

public enum WebApplicationType {
​
  /**
   * 应用程序不是web应用,也不应该用web服务器去启动
   * The application should not run as a web application and should not start an
   * embedded web server.
   */
  NONE,
​
  /**
   * 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web(tomcat)服务器。
   * The application should run as a servlet-based web application and should start an
   * embedded servlet web server.
   */
  SERVLET,
​
  /**
   * 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服务器。
   * The application should run as a reactive web application and should start an
   * embedded reactive web server.
   */
  REACTIVE;
}

对应三种应用类型,SpringBoot项目有三种对应的应用上下文,我们以web工程为例,即其应用上下文为:

AnnotationConfigServletWebServerApplicationContext。

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
  try {
    switch (webApplicationType) {
    case SERVLET:
      return new AnnotationConfigServletWebServerApplicationContext();
    case REACTIVE:
      return new AnnotationConfigReactiveWebServerApplicationContext();
    default:
      return new AnnotationConfigApplicationContext();
    }
  }
  catch (Exception ex) {
    throw new IllegalStateException("Unable create a default ApplicationContext instance, "
        + "you may need a custom ApplicationContextFactory", ex);
  }
};

应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一些高级功能。应用上下文对IoC容器是持有的关系。属性beanFactory就是IoC容器DefaultListableBeanFactory。所以他们之间是持有和扩展的关系。

类AnnotationConfigServletWebServerApplicationContext继承了类GenericApplicationContext,GenericApplicationContext的构造方法中实例化了beanFactory(DefaultListableBeanFactory)。所以在此处也创建了IoC容器。我们Debug观察执行createApplicationContext()方法。

执行前:

执行后:

④刷新应用上下文前的准备工作prepareContext()

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments, Banner printedBanner) {
  //设置上下文环境
  context.setEnvironment(environment);
  //执行后置处理器
  postProcessApplicationContext(context);
  //执行上下文中的ApplicationContextInitializer
  applyInitializers(context);
  //向各个监听器发送上下文已经准备好的事件
  listeners.contextPrepared(context);
  bootstrapContext.close(context);
  if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
  }
  // Add boot specific singleton beans
  //将main函数中的args参数封装成单例Bean,注册进容器
  ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  //将printedBanner也封装成单例,注册进容器
  if (printedBanner != null) {
    beanFactory.registerSingleton("springBootBanner", printedBanner);
  }
  if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
    //设置是否允许循环引用
    ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
    if (beanFactory instanceof DefaultListableBeanFactory) {
      //设置是否允许覆盖bean
      ((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]));
  //发布容器已加载事件
  listeners.contextLoaded(context);
}

getAllSources就是获取我们的主启动类

load(context, sources.toArray(new Object[0]))将我们的启动类注入到容器中。load方法将我们的对象封装为一个BeanDefinition后最终调用registerBeanDefinition()方法将对象注册到beanDefinitionMap中。

protected void load(ApplicationContext context, Object[] sources) {
  if (logger.isDebugEnabled()) {
    logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
  }
  BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
  if (this.beanNameGenerator != null) {
    loader.setBeanNameGenerator(this.beanNameGenerator);
  }
  if (this.resourceLoader != null) {
    loader.setResourceLoader(this.resourceLoader);
  }
  if (this.environment != null) {
    loader.setEnvironment(this.environment);
  }
  loader.load();
}

load前:

load后:

⑤刷新应用上下文refreshContext(),也就是对IoC容器进行初始化。

对IoC容器进行初始化大致分为三步:BeanDefinitiond的Resource定位、BeanDefinition的载入、将BeanDefinition注册到IoC容器中。

在执行refreshContext,就是执行Spring的refresh方法,此时SpringBoot就是将Spring进行了一次封装,最终执行的还是refresh方法。

private void refreshContext(ConfigurableApplicationContext context) {
  if (this.registerShutdownHook) {
    shutdownHook.registerApplicationContext(context);
  }
  refresh(context);
}
​
protected void refresh(ConfigurableApplicationContext applicationContext) {
  applicationContext.refresh();
}
​
public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
    StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
    //刷新上下文环境
    prepareRefresh();
    
    //这里是在子类中启动refreshBeanFactory()的地方
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    //准备bean工厂,以便在此上下文中使用
    prepareBeanFactory(beanFactory);
    try {
      //设置 beanFactory 的后置处理器
      postProcessBeanFactory(beanFactory);
      StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
      //调用BeanFactory的后处理器,这些处理器是在Bean定义中向容器注册的
      invokeBeanFactoryPostProcessors(beanFactory);
      
      //注册Bean的后处理器,在Bean创建过程中调用
      registerBeanPostProcessors(beanFactory);
      beanPostProcess.end();
      
      //对上下文中的消息源进行初始化
      initMessageSource();
      
      //初始化上下文中的事件机制
      initApplicationEventMulticaster();
​
      //初始化其他特殊的Bean
      onRefresh();
​
      //检查监听Bean并且将这些监听Bean向容器注册
      registerListeners();
​
      //实例化所有的(non-lazy-init)单例
      finishBeanFactoryInitialization(beanFactory);
​
      //发布容器事件,结束Refresh过程
      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();
    }
  }
}

由于上述代码是Spring的源码,本次不进行解读,小伙伴感兴趣可继续深入研究细节,本人直接跳到和SpringBoot自动装配有关的方法invokeBeanFactoryPostProcessors进行解读。

在invokeBeanFactoryPostProcessors方法中,完成了对IoC容器的初始化。大致分为三步:

1>Resource资源定位

在SpringBoot中,我们知道包扫描是从主类开始的,在prepareContext方法中,已经将主类封装为BeanDefinition,然后在此方法中解析主类的BeanDefinition获取basePackage的路径,完成了资源的定位。其次SpringBoot的starter通过SPI机制实现自动装配,还有@Import注解指定的类也进行了定位加载。

2>BeanDefinition的载入

所谓载入就是通过上述定位的basePackage,SpringBoot将路径拼接为:classpath:com/study/**/.class这样的形式,然后进行加载,判断有没有@Component注解,有就装载BeanDefinition。

3>注册BeanDefinition

注册过程就是把载入过程中解析得到的BeanDefinition向IoC容器进行注册。一路跟踪会进入到ConfigurationClassParser的parse方法(由于Spring源码篇幅太多,不是本次的重点,直接给出调用栈)

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    try {
      if (bd instanceof AnnotatedBeanDefinition) {
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
      else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
        parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
      }
      else {
        parse(bd.getBeanClassName(), holder.getBeanName());
      }
    }
    catch (BeanDefinitionStoreException ex) {
      throw ex;
    }
    catch (Throwable ex) {
      throw new BeanDefinitionStoreException(
          "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    }
  }
  //加载配置 (SpringBoot项目则 此处为SpringBoot自动装配的入口)
  this.deferredImportSelectorHandler.process();
}

再进入parse方法

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
​
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }
​
  ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  if (existingClass != null) {
    if (configClass.isImported()) {
      if (existingClass.isImported()) {
        existingClass.mergeImportedBy(configClass);
      }
      // Otherwise ignore new imported config class; existing non-imported class overrides it.
      return;
    }
    else {
      // Explicit bean definition found, probably replacing an import.
      // Let's remove the old one and go with the new one.
      this.configurationClasses.remove(configClass);
      this.knownSuperclasses.values().removeIf(configClass::equals);
    }
  }
​
  // Recursively process the configuration class and its superclass hierarchy.
  SourceClass sourceClass = asSourceClass(configClass, filter);
  do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
  }
  while (sourceClass != null);
​
  this.configurationClasses.put(configClass, configClass);
}

看到do开头的方法,有经验的小伙伴已经知道了这个方法是真正干活的方法(doProcessConfigurationClass)。

//此方法很长,小伙伴可以打开源码浏览,此处只截取片段
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    ...
    // Process any @ComponentScan annotations
    //根据 @ComponentScan 注解,扫描项目中的Bean(SpringBoot 启动类上有该注解)
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        //立即执行扫描,(SpringBoot项目为什么是从主类所在的包扫描,这就是关键 了)
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
            this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
          BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
          if (bdCand == null) {
            bdCand = holder.getBeanDefinition();
          }
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
          }
        }
      }
    }
    //递归处理 @Import 注解
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    ...
}

在doProcessConfigurationClass方法中会对主类的@Component注解和@Import注解进行读取执行响应的逻辑。我们继续往下看

this.componentScanParser.parse方法

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
      componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
  ...
  if (basePackages.isEmpty()) {
    basePackages.add(ClassUtils.getPackageName(declaringClass));
  }    
  ...
  return scanner.doScan(StringUtils.toStringArray(basePackages));
}

在进入doScan方法,将有@Component的类注入到容器中。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  for (String basePackage : basePackages) {
    //从指定的包中扫描需要装载的Bean
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate : candidates) {
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      if (candidate instanceof AnnotatedBeanDefinition) {
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      if (checkCandidate(beanName, candidate)) {
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        beanDefinitions.add(definitionHolder);
        //将该 Bean 注册进 IoC容器(beanDefinitionMap)
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

我们看到解析完com.study包之后,testController被封装加载到。

然后就是重要的@Import解析

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
  Set<SourceClass> imports = new LinkedHashSet<>();
  Set<SourceClass> visited = new LinkedHashSet<>();
  collectImports(sourceClass, imports, visited);
  return imports;
}
​
//递归找到@Import注解
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
    throws IOException {
​
  if (visited.add(sourceClass)) {
    for (SourceClass annotation : sourceClass.getAnnotations()) {
      String annName = annotation.getMetadata().getClassName();
      if (!annName.equals(Import.class.getName())) {
        collectImports(annotation, imports, visited);
      }
    }
    imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
  }
}

这里只是解析了@import,并没有进行处理。

我们回到ConfigurationClassParser的parse方法,在方法的最后会执行

// 去执行组件类 
this.deferredImportSelectorHandler.process();

进入process后一直进入到方法processGroupImports()

public void processGroupImports() {
  for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    Predicate<String> exclusionFilter = grouping.getCandidateFilter();
    grouping.getImports().forEach(entry -> {
      ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
      try {
        processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
            Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
            exclusionFilter, false);
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
                configurationClass.getMetadata().getClassName() + "]", ex);
      }
    });
  }
}

此处调用了getImports()。

public Iterable<Group.Entry> getImports() {
  //遍历DeferredImportSelectorHolder对象集合deferredImports,
  //deferredImports集合装了各种ImportSelector,
  //当然这里装的是AutoConfigurationImportSelector
  for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    //利用AutoConfigurationGroup的process方法来处理自动配置
    //的相关逻辑,决定导入哪些配置类
    this.group.process(deferredImport.getConfigurationClass().getMetadata(),
        deferredImport.getImportSelector());
  }
  //经过上面的处理后,然后再进行选择导入哪些配置类
  return this.group.selectImports();
}

然后就会调用类AutoConfigurationImportSelector中的内部类AutoConfigurationGroup中的process和selectImports方法。

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
  Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
      () -> String.format("Only %s implementations are supported, got %s",
          AutoConfigurationImportSelector.class.getSimpleName(),
          deferredImportSelector.getClass().getName()));
  //调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中       
  AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
      .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
  //又将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合
  this.autoConfigurationEntries.add(autoConfigurationEntry);
  for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    //这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
    this.entries.putIfAbsent(importClassName, annotationMetadata);
  }
}
​
@Override
public Iterable<Entry> selectImports() {
  if (this.autoConfigurationEntries.isEmpty()) {
    return Collections.emptyList();
  }
  Set<String> allExclusions = this.autoConfigurationEntries.stream()
      .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
  Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
      .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
      .collect(Collectors.toCollection(LinkedHashSet::new));
  processedConfigurations.removeAll(allExclusions);
​
  return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
      .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
      .collect(Collectors.toList());
}

进入getAutoConfigurationEntry方法

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    //获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //获得@Congiguration标注的Configuration类
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //得到spring.factories文件配置的所有自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //利用LinkedHashSet移除重复的配置类
    configurations = removeDuplicates(configurations);
    //得到要排除的自动配置类,比如注解属性exclude的配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    //将要排除的配置类移除
    configurations.removeAll(exclusions);
    //因为从spring.factories文件获取的自动配置类太多,
    //如果有些不必要的自动配置类都加载 进内存,会造成内存浪费,
    //因此这里需要进行过滤,这里会调用AutoConfigurationImportFilter
    //的match方法来判断是否符合 @ConditionalOnBean,
    //@ConditionalOnClass或@ConditionalOnWebApplication
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    //将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象
    return new AutoConfigurationEntry(configurations, exclusions);
}

在getCandidateConfigurations方法中就会调用loadFactoryNames方法加载位于META-INF下的spring.factories文件

本次的key是EnableAutoConfiguration。

AutoConfigurationImportSelector可以帮助Springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IOC 容器( ApplicationContext )中。

在spring.factories中有很多的自动装配类,但是不是所有的都会进行装载,只有符合条件的才会被加载到IoC容器中。

过滤前:

过滤后:

因为在spring.factories文件中有很多的配置类并不是我们所需要的,比如下图的RabbitAutoConfiguration,我们没有使用就不会被装配,我们导入了SpringMVC,则DispatcherServlet和HttpEncoding就会被装配,只有满足自动装配类上的Condition才会被装配。

我们来看一下执行refreshContext方法的效果。

执行前:

执行后:

此时符合自动装配的所有bean就会被自动装载到Spring的IoC容器中。

⑥刷新应用上下文后的扩展方法afterRefresh

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

扩展方法,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。

至此,SpringBoot的自动装配全部完成。

六、总结

SpringBoot自动配置的原理,主要做了以下事情:

①从spring.factories配置文件中加载自动配置类

②加载的自动配置类中排除掉@EnableAutoConfiguration注解的 exclude 属性指定的自动配置类

③然后再用AutoConfigurationImportFilter接口去过滤自动配置类是否符合其标注注解@ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication的条件,若都符合的话则返回匹配结果。

④然后触发 AutoConfigurationImportEvent 事件

告诉ConditionEvaluationReport条件评估报告器对象来分别记录符合条件和exclude的自动配置类。

⑤最后由spring将最后筛选后的自动配置类导入IOC容器中

今天的分享就到此结束啦,喜欢的小伙伴记得点赞呦。

关注公众号JavaGrowUp,回复【面试】获取面试题,备战金三银四,我们下期再见。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值