SpringBoot自动装配源码解析(40000字大章)

 CSDN里貌似没法搞代码的自动换行,嫌看着麻烦的话可以直接看我的笔记里的版本,排版会更舒服些https://note.youdao.com/s/IgsPbrpd

目录

一、原理说明

二、源码讲解

2.1@SpringBootApplication

2.2@SpringBootConfiguration

2.3@ComponentScan

2.4@EnableAutoConfiguration

2.5AutoConfigurationImportSelector.class

2.5.1selectImports函数

2.5.2getAutoConfigurationEntry函数

2.5.3AutoConfigurationGroup

2.5.4Spring IOC源码

2.6@AutoConfigurationPackage

2.7TypeExcludeFilter、AutoConfigurationExcludeFilter

2.7.1AutoConfigurationExcludeFilter.class

2.7.2TypeExcludeFilter

三、代码举例

3.1自定义ImportBeanDefinitionRegistrar实现、自定义ImportSelector实现、自定义注解,以通过一个注解实现一堆Bean的功能。

3.2自定义Starter

3.2.1实现Starter

3.2.2测试Starter


一、原理说明

在Spring的学习过程中,我们已经了解到了IOC控制反转的含义,通过将组件的控制权从应用程序代码中转移到框架或容器中的机制(由Spring代替我们进行组件的初始化、获得实例、销毁等),从而使应用程序只需关注业务逻辑,不需要关心对象的创建和管理,实现了解耦和可维护性的目标。

在Bean的生命周期中,bean类被加载到JVM中时,会被Spring扫描并存入到一个Map中(比如Person.class,存入),然后BeanFactoryPostProcessor可以对其中某些BeanDefinition进行一些修改,然后使用 ClassLoader 加载BeanDefinition中的类名对应的类,再 利用类的构造函数创建一个新的实例,就此Bean的实例化就完成了。

更详细的Bean生命周期:

  1. 加载到 JVM: Spring 容器在启动时,会解析配置文件或扫描注解,创建相应的 BeanDefinition,并将其存储在 BeanDefinitionMap 中。BeanDefinitionMap 是一个 Map,它将 Bean 的名称映射到 BeanDefinition 实例。BeanFactoryPostProcessor 中的方法 postProcessBeanFactory() 在 Spring 容器加载 Bean 的定义信息后、实例化 Bean 之前被调用。这个方法可以获取到 Bean 的定义信息,从而可以在实例化之前对这些定义信息进行修改。一般来说,这些修改可能涉及属性值的更改、添加新的 Bean 定义、移除不需要的 Bean 定义等。
  2. 实例化:当容器需要创建 Bean 时,它会根据 BeanDefinition 的信息创建相应的 Bean 实例。这一步骤通常由 BeanDefinitionReader 和 BeanFactory 负责。BeanFactory 是容器的核心接口,它实际上是一个工厂模式的实现,用于实例化和管理 Bean。在容器启动时,会初始化 BeanFactory,BeanFactory 会根据 BeanDefinition 创建 Bean 实例。
  3. 属性注入:在实例化后,容器会根据 BeanDefinition 中的属性值配置来进行属性注入。这一过程由 BeanDefinitionMap 中的 BeanDefinition 实例提供的属性信息来完成。
  4. 初始化前的函数调用:如果 Bean 实现了 BeanNameAware 或 BeanFactoryAware 接口,容器会在实例化后,但在初始化之前,调用相应的方法,使 Bean 可以获取 Bean 的名称或容器的引用;BeanPostProcessor 的实现类也会被调用,允许对 Bean 进行一些前置处理,这一阶段的处理由 AbstractAutowireCapableBeanFactory 的 applyBeanPostProcessorsBeforeInitialization 方法负责。
  5. 初始化:如果 Bean 实现了 InitializingBean 接口或在 BeanDefinition 中通过 init-method 属性指定了初始化方法,容器会在 Bean 实例化和属性注入之后调用相应的方法,执行 Bean 的初始化逻辑。
  6. 初始化后的函数调用:在 Bean 初始化之后,BeanPostProcessor 的实现类会被调用,允许对 Bean 进行一些后置处理。这一阶段的处理由 AbstractAutowireCapableBeanFactory 的 applyBeanPostProcessorsAfterInitialization 方法负责。
  7. Bean使用。
  8. 销毁:如果 Bean 实现了 DisposableBean 接口,或者在配置中通过 destroy-method 属性指定了销毁方法,容器会在 Bean 销毁阶段调用相应的方法,执行 Bean 的销毁逻辑。这一过程由 DisposableBean 接口的 destroy 方法或在 BeanDefinition 中指定的 destroy-method 属性负责。

那么为什么要回顾这个呢?因为自动装配涉及到这个过程,并且其实质就是更改BeanDefinition。

比如,我们发现在实例化过程中,实现BeanDefinition接口的抽象类AbstractBeanDefinition中包含了一个int属性autowireMode,该属性的作用是设置是否启用自动装配:

  • autowireMode包含4个“枚举”,依次为NO/BYNAME/BYTYPE/CONSTRUCTOR,在AbstractApplicationContext类执行refresh方法时,会根据此属性决定是否进行自动装配。
  • 如A是一个Component,B也是一个Component,A是B的一个字段:
@Component
public class TestB {
    //@Autowire
    public TestA testA;
    public void setTestA(TestA testA) {
        this.testA = testA;
    }
    //......
}

 平时我们自己进行配置时,需要给TestA类加上@Autowire注解(或使用xml配置文件),这样TestA的Bean才会注入到TestB的Bean中,否则此属性实际为Null。

  • 但是如果在BeanFactoryPostProcessor中将TestB的BeanDefinition的autowireMode属性更改为非默认值(默认为NO,即不开启自动装配):
@Configuration
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        GenericBeanDefinition defA = (GenericBeanDefinition) beanFactory.getBeanDefinition("testB");
        defA.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
}

那么就不需要@Autowire注解了,再执行refresh方法,其会自动将用到的属性配置好。


我们已经初步了解到自动装配与BeanDefinition的关联,但是这和我们的目的还差得远:我们希望有一个可以不需要任何配置文件,开箱即用,按需装配的开发框架,而不仅仅局限于Spring的Bean管理功能。我们需要从「控制反转」递进到『开箱即用』。

正常情况下,我们使用Bean对象,需要@Import该类,不过上述的A类B类都是在SpringBootApplication同一个目录下的,所以不需要Import就能被扫描到。我们现在已经知道将一个Bean加载到Spring容器的过程,但是讲所需要的Class全部使用@Import进行导入过于麻烦。

我们的希望是仅使用一个@SpringBootApplication注解,就能按需将合适的Bean进行加载。


(前文内容只是一个引子,与自动装配关联不大,着重看接下来的内容)

在这个逐步优化的过程中,需要提到两个特殊的接口——ImportBeanDefinitionRegistrar和ImportSelector。

ImportBeanDefinitionRegistrar是用于动态将额外的 bean 定义注册到容器中的接口,使用方式是@Import一个实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法的类,该方法可以使用函数形参BeanDefinitionRegistry来向容器注册和管理 bean 定义,比如注册是void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)。

ImportSelector是用于在配置类中根据条件选择性地导入其他的配置类或组件的接口,使用方式也是@Import一个实现了ImportSelector接口的selectImports方法的类,在该方法中可以加入各种条件判断来生成一个String[],该数组中的元素就是一个个配置类,Spring容器会加载该函数返回的String数组中的配置类。

说到这里,想必大家就有了一些思路:可以自定义一个@EnableAutoConfiguration注解,该注解中包含了一些@Import注解,这些@Import注解就用来导入ImportBeanDefinitionRegistrar和ImportSelector的实现类,以此,我们在写新项目时,只需要加上@EnableAutoConfiguration注解,即可自动装配一大堆Bean对象。(建议查看举例代码3.1)

@MyEnableAutoConfiguration实现思路:

虽然我们可以直接在@MyEnableAutoConfiguration注解中加入一堆@Import来将所有需要的Bean都进行注入,但是ImportBeanDefinitionRegistrar和ImportSelector的优点就是可以根据条件进行装载,并且是直接根据代码中的逻辑进行动态装载,则为后续的按需装配做好了准备。


我们现在已经实现了使用一个注解来加载一堆Bean到Spring容器,但是整个过程是写死在代码中的,并且无法做到按需装配,目前的自定义注解会将所有Bean都进行注入。因此需要继续优化。

我们可以考虑使用Spring的PropertySource机制,将配置文件中的String数组作为一个属性值进行读取。这样可以方便地进行配置文件的修改和维护,同时也提高了代码的可读性和可维护性。

也可以通过扫描指定的包路径来获取SpringBootApplication中导入的所有类,然后在配置文件中描述根据这些路径加载需要的配置类的规则,从而实现按需装配和开箱即用的效果。接下来我们需要查看源码以深入了解实现细节。

二、源码讲解

我们从@SpringBootApplication注解入手,一层层看它是如何实现开箱即用的。(建议查看此章节时一边Debug一边看)

2.1@SpringBootApplication

@SpringBootApplication可以被拆解为这7个注解:前4个注解不用在意;注解中也只是给包含的注解属性加上别名,不用在意。重点是包含的@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解。

@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 {//...
}

2.2@SpringBootConfiguration

@SpringBootConfiguration就是@Configuration注解的别名,用于指定一个类是配置类,因此使用了@SpringBootApplication注解后,启动类中也可加入@Bean。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {//...
}

2.3@ComponentScan

@ComponentScan注解中使用了两个过滤器TypeExcludeFilter 和 AutoConfigurationExcludeFilter,来排除一些类型的组件。由于@ComponentScan中没有使用其他参数,所以默认开启扫描的范围为当前注解所在类所在的包及其子包下的所有类。
TypeExcludeFilter:这个过滤器用于在组件扫描过程中排除特定类型的类。
AutoConfigurationExcludeFilter:这个过滤器用于在自动配置过程中排除特定的自动配置类。自动配置类是 Spring Boot 应用启动时自动加载的,通过排除某些自动配置类,可以控制哪些自动配置应该被应用,哪些应该被排除。
TypeExcludeFilter和AutoConfigurationExcludeFilter的源码讲解在后续内容,现在先继续讲@EnableAutoConfiguration注解。

2.4@EnableAutoConfiguration

看一眼@EnableAutoConfiguration注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //...
}

@AutoConfigurationPackage注解的作用是显式地指定自动配置的基础包。这个注解的细节我们也后续在讲,先关注最重要的注解:@Import(AutoConfigurationImportSelector.class)。

2.5AutoConfigurationImportSelector.class

AutoConfigurationImportSelector的作用是根据项目的依赖和配置情况,动态地选择并加载适合的自动配置类。

2.5.1selectImports函数

AutoConfigurationImportSelector的源码是真的超级大一堆,从每个函数出发太复杂了,我们就先从其根本目的出发。看这个类的后缀名为ImportSelector,并且实现了DeferredImportSelector接口,而DeferredImportSelector接口又继承了ImportSelector,说明我们的中心还是要放到selectImports函数上,毕竟是这个函数决定了哪些配置类会被自动装载。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

单看方法很简单,就是判断注解元数据是否启用,不启用则直接返回空数组,不载入配置类;如果启用则从注解元数据中获得需要启用的配置类数组,进行自动装载。

isEnabled函数的逻辑很简单:

    protected boolean isEnabled(AnnotationMetadata metadata) {
        if (getClass() == AutoConfigurationImportSelector.class) {
            return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
        }
        return true;
    }

先判断当前类是否是AutoConfigurationImportSelector.class,如果是就从配置文件中获得spring.boot.enableautoconfiguration属性,该属性用于决定是否启用自动装配,默认为启用。

isEnabled判断为true,然后就执行

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);

查看该语句,使用了getAutoConfigurationEntry函数,我们就从这个函数入手,看他是怎么获得需要自动装配的配置类的。

2.5.2getAutoConfigurationEntry函数

这个函数的作用是选择适合的自动配置类作为入口,并将其返回供后续处理使用,它决定了哪些自动配置会在应用中生效。

看源码:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

接下来对这个源码进行分析:

  • 第二行依旧是一个判断是否启用自动装配的if语句,当然是启用,忽略!
  • 第五行「AnnotationAttributes attributes = getAttributes(annotationMetadata);」调用了getAttributes函数,这个方法的主要作用是从给定的注解元数据中获取自动配置的属性值,并确保属性值不为 null。这里补充一下AnnotationMetadata是啥,这是一个数据结构,用来存储各个类和它们的注解之间的对应关系,我们可以从中获得指定类的指定注解的属性等。好了我们现在已经获得了@EnableAutoConfiguration注解的属性值,其实就是两个属性,exclude和excludeName。
  • 第六行「List configurations = getCandidateConfigurations(annotationMetadata, attributes);」调用了getCandidateConfigurations函数,该函数的作用是获取所有候选的自动配置类。看一下源码解析:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = new ArrayList<>(
         SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
//loadFactoryNames会使用给定的类加载器,从所有的“META-INF/spring.factories”文件中加载给定类型的工厂实现的完全限定类名,该函数源码在后面。
   ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
//加载与 AutoConfiguration 注解相关的配置类,这些类同样也会被添加到候选配置类列表中,该函数源码在后面。
   Assert.notEmpty(configurations,
         "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
               + "are using a custom packaging, make sure that file is correct.");
//断言,判断configurations是否为空,为空说明加载配置类失败,报错
   return configurations;
}

 SpringFactoriesLoader.loadFactoryNames源码:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   //此次执行,factoryTypeName即为getSpringFactoriesLoaderFactoryClass()的返回结果,“org.springframework.boot.autoconfigure.EnableAutoConfiguration”。
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {//由于并不是首次执行,所以会直接从cache中获得结果,但是我们还是要看result的加载过程
      return result;
   }

   result = new HashMap<>();
   try {
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);//FACTORIES_RESOURCE_LOCATION即"META-INF/spring.factories",此行代码的意思是通过类加载器获取指定路径下的所有资源,就是所有的被导入的jar包的各种META-INF/spring.factories文件都会被加载一遍
      while (urls.hasMoreElements()) {//遍历所有找到的资源
         URL url = urls.nextElement();//获取下一个资源的URL
         UrlResource resource = new UrlResource(url);//将URL转换为UrlResource对象,以便于加载资源内容
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);//使用PropertiesLoaderUtils加载资源内容为属性对象。
         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);//如果在加载资源时出现异常,抛出IllegalArgumentException异常。
   }
   return result;
}

ImportCandidates.load源码:

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
   Assert.notNull(annotation, "'annotation' must not be null");//确保传入的注解参数不为null。
   ClassLoader classLoaderToUse = decideClassloader(classLoader);//若传入的classLoader为空,则使用当前类的类加载器;否则使用传入的类加载器
   String location = String.format(LOCATION, annotation.getName());//location为"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"
   Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);//从类路径中搜索所有导入的 JAR 包,并尝试在每个 JAR 包中查找 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。
   List<String> importCandidates = new ArrayList<>();//创建一个空的列表,用于存储读取到的候选导入类。
   while (urls.hasMoreElements()) {//遍历所有找到的资源文件。
      URL url = urls.nextElement();//获取下一个资源文件的URL。
      importCandidates.addAll(readCandidateConfigurations(url));//读取并添加从资源文件中提取的候选导入类列表。
   }
   return new ImportCandidates(importCandidates);//将读取到的候选导入类列表包装成 ImportCandidates 对象并返回。
}
  • 第七行「configurations = removeDuplicates(configurations);」的作用是去除重复的自动配置类名。实现很简单,return new ArrayList<>(new LinkedHashSet<>(list))即可。
  • 第八行「Set exclusions = getExclusions(annotationMetadata, attributes);」的作用是获取需要排除的自动配置类名集合。之前我们提到,AnnotationAttributes即为exclude和excludeName两个属性,这里就会将这两个属性中的值并入到一个Set集合中。此外,还会使用getExcludeAutoConfigurationsProperty方法获取在应用程序的配置文件中指定排除自动配置的属性名称。看源码:
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   Set<String> excluded = new LinkedHashSet<>();
   excluded.addAll(asList(attributes, "exclude"));// 将 "exclude" 属性的值加入到排除集合中
   excluded.addAll(asList(attributes, "excludeName"));// 将 "excludeName" 属性的值加入到排除集合中
   excluded.addAll(getExcludeAutoConfigurationsProperty());// 获取从属性中获取的需要排除的自动配置类名列表,并加入到排除集合中
   return excluded;// 返回最终的排除集合
}
protected List<String> getExcludeAutoConfigurationsProperty() {
   Environment environment = getEnvironment();// 获取应用程序的环境对象
   if (environment == null) {
      return Collections.emptyList();// 如果环境对象为空,则返回空列表
   }
   if (environment instanceof ConfigurableEnvironment) {
      Binder binder = Binder.get(environment);// 如果环境对象是可配置的环境,使用 Binder 来绑定属性值
      return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
         .map(Arrays::asList)
         .orElse(Collections.emptyList());//PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE即"spring.autoconfigure.exclude",从配置文件中读取该数组类属性,生成List并返回
   }
   String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);// 如果环境对象不是可配置的环境,直接从环境中获取属性值
   return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
  • 第⑨行「checkExcludedClasses(configurations, exclusions);」检查是否有被排除的类不存在于候选自动配置类中。看源码:
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
   List<String> invalidExcludes = new ArrayList<>(exclusions.size());//invalidExcludes 是一个用于存储无效的排除类名的列表,初始容量与排除集合大小相同。
   for (String exclusion : exclusions) {// 遍历排除集合中的每个类名
      if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {//如果这个类存在,且不包含在configuration类中
         invalidExcludes.add(exclusion);//则添加到无效排除类
      }
   }
   if (!invalidExcludes.isEmpty()) {
      handleInvalidExcludes(invalidExcludes);//处理无效排除类
   }
}
//handleInvalidExcludes函数的作用是发现无效的被排除类名时,抛出一个 IllegalStateException 异常,其中包含了详细的错误信息,指明哪些类不能被排除,因为它们不是自动配置类。
protected void handleInvalidExcludes(List<String> invalidExcludes) {
   StringBuilder message = new StringBuilder();
   for (String exclude : invalidExcludes) {
      message.append("\t- ").append(exclude).append(String.format("%n"));
   }
   throw new IllegalStateException(String.format(
         "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
         message));
}
  • 第十行「configurations.removeAll(exclusions);」的作用是从候选配置中移除被排除的配置类。
  • 第十一行「configurations = getConfigurationClassFilter().filter(configurations);」的作用是使用配置类过滤器来对候选的自动配置类列表进行过滤,以最终得到符合条件的自动配置类列表。getConfigurationClassFilter()用于获取配置类过滤器(ConfigurationClassFilter)的实例,可以根据一定的条件来决定哪些类应该被包含,哪些类应该被排除。filter 方法将遍历候选列表中的每个类,根据过滤器的条件来判断是否应该将这个类包含在最终的自动配置类列表中。先看一下getConfigurationClassFilter的源码:
private ConfigurationClassFilter getConfigurationClassFilter() {
   if (this.configurationClassFilter == null) {//如果 this.configurationClassFilter 尚未初始化(为 null)则执行以下操作
      List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();//获取自动配置导入过滤器的列表。这些过滤器用于决定哪些自动配置应该被包含,哪些应该被排除。
      for (AutoConfigurationImportFilter filter : filters) {//遍历自动配置导入过滤器列表。
         invokeAwareMethods(filter);//对于每个过滤器,调用可能的 Aware 接口方法,将相关的信息传递给过滤器。invokeAwareMethods函数的实现就是使用各类set函数将一些环境之类的属性传递过去,不用在意。
      }
      this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);//使用自动配置导入过滤器列表创建配置类过滤器的实例
   }
   return this.configurationClassFilter;
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
   return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);//又使用到SpringFactoriesLoader了,说明过滤器的配置文件可能也在spring.factories中
}

SpringFactoriesLoader.loadFactories源码:

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
   Assert.notNull(factoryType, "'factoryType' must not be null");//factoryType为"org.springframework.boot.autoconfigure.AutoConfigurationImportFilter",
   ClassLoader classLoaderToUse = classLoader;// 获取用于加载类的类加载器
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);//这里调用了loadFactoryNames函数,该函数的作用之前已经介绍过了,可以从之前已经加载过的所有spring.factories中获得指定"org.springframework.boot.autoconfigure.AutoConfigurationImportFilter"接口对应的自动配置导入过滤器,即OnBeanCondition、OnClassCondition和OnWebApplication。
   if (logger.isTraceEnabled()) {//这是看日志级别决定是否进行输出
      logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
   }
   List<T> result = new ArrayList<>(factoryImplementationNames.size());
   for (String factoryImplementationName : factoryImplementationNames) {
      result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
   }//instantiateFactory函数是用来实例化过滤器的方法,将上面提到的3个类进行实例化,然后加到List集合中
   AnnotationAwareOrderComparator.sort(result);// 使用AnnotationAwareOrderComparator对结果列表进行排序
   return result;
}

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter接口的三个相关类。

 然后是filter函数执行的源码,但是我们先看构造器函数:

private static class ConfigurationClassFilter {
    
   private final AutoConfigurationMetadata autoConfigurationMetadata;
   private final List<AutoConfigurationImportFilter> filters;
   
   ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
      this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
      this.filters = filters;
   }
   //...
}

发现他调用了AutoConfigurationMetadataLoader类的静态方法loadMetadata,看一下这个静态方法的源码:

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
   return loadMetadata(classLoader, PATH);//PATH就是"META-INF/spring-autoconfigure-metadata.properties"。该文件帮助应用程序在启动时智能地选择和应用自动配置类,以及提供开发体验的改进,其中记录了每个自动配置类的名称、包路径、条件等信息。
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
   try {
      Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);//似曾相识的代码,用于获得所有jar包中的所有META-INF/spring-autoconfigure-metadata.properties文件。
      Properties properties = new Properties();
      while (urls.hasMoreElements()) {
         properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
      }//将所有的META-INF/spring-autoconfigure-metadata.properties文件转换为属性键值对,存入properties中。
      return loadMetadata(properties);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
   }
}
static AutoConfigurationMetadata loadMetadata(Properties properties) {
   return new PropertiesAutoConfigurationMetadata(properties);//就此生成autoConfigurationMetadata
}

autoConfigurationMetadata中存储了所有的自动配置类装载的条件信息,随便看一个文件:

 现在再看filter函数的源码:

List<String> filter(List<String> configurations) {
   long startTime = System.nanoTime();//记录开始时间
   String[] candidates = StringUtils.toStringArray(configurations);//将配置类列表转换为String数组
   boolean skipped = false;//标记是否有类被过滤掉
   for (AutoConfigurationImportFilter filter : this.filters) {//遍历所有的自动配置导入过滤器
      boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {//对未匹配的类置为空,并且设置标记为true
            candidates[i] = null;
            skipped = true;
         }
      }
   }
   if (!skipped) {//如果没有类被过滤掉,直接返回
      return configurations;
   }
   List<String> result = new ArrayList<>(candidates.length);//如果有类被过滤掉,重新构建没有被过滤的类的列表
   for (String candidate : candidates) {
      if (candidate != null) {
         result.add(candidate);
      }
   }
   if (logger.isTraceEnabled()) {//如果日志级别为TRACE,记录被过滤掉的类的数量和过滤时间,可忽略
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return result;
}

看了代码,发现还是得看具体的三个过滤器实现类中的match函数,因为这里的match只是接口:三个过滤器(OnBeanCondition、OnClassCondition和OnWebApplication)的实现类都是很大一坨,看着就头疼,不过好在他们都没重写父类FilteringSpringBootCondition的match方法,我们看父类的match方法源码:

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
   ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);//尝试查找与条件评估相关的报告,这个报告用于记录条件的评估结果。
   ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);//调用getOutcomes方法获取配置类的条件评估结果,这些结果表示每个配置类是否满足条件。
   boolean[] match = new boolean[outcomes.length];//创建一个布尔数组 match,用于记录每个配置类是否满足条件。
   for (int i = 0; i < outcomes.length; i++) {
      match[i] = (outcomes[i] == null || outcomes[i].isMatch());//如果条件结果为空(没有条件)或为匹配,则将对应位置的布尔值标记为 true,表示满足条件。
      if (!match[i] && outcomes[i] != null) {// 如果不匹配且结果不为空,记录日志并将评估结果添加到报告中
         logOutcome(autoConfigurationClasses[i], outcomes[i]);
         if (report != null) {
            report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
         }
      }
   }
   return match;// 返回每个配置类是否匹配条件的布尔数组
}

然而繁琐的是每个实现类都有一个自己的getOutcomes函数,而且源码略微有些复杂...部分函数只说明功能,不做具体解析了。

  1. OnBeanCondition:基于 @ConditionalOnBean 注解和 @ConditionalOnSingleCandidate 注解。
    @Override
    protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
          AutoConfigurationMetadata autoConfigurationMetadata) {
       ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];//存储每个自动配置类的条件判断结果。
       for (int i = 0; i < outcomes.length; i++) {//遍历每个自动配置类名。
          String autoConfigurationClass = autoConfigurationClasses[i];
          if (autoConfigurationClass != null) {
             Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");//获取条件注解 @ConditionalOnBean 中定义的 bean 类型集合。
             outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);//使用 getOutcome 方法判断是否满足 @ConditionalOnBean 的条件。如果满足,直接存储在 outcomes 数组中。
             if (outcomes[i] == null) {//如果不满足,获取条件注解 @ConditionalOnSingleCandidate。
                Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
                      "ConditionalOnSingleCandidate");
                outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);//使用 getOutcome 方法判断是否满足 @ConditionalOnSingleCandidate 的条件。如果满足,存储到 outcomes 数组。
             }
          }
       }
       return outcomes;
    }
    private ConditionOutcome getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) {
       List<String> missing = filter(requiredBeanTypes, ClassNameFilter.MISSING, getBeanClassLoader());//过滤出在给定的 bean 类型集合中不存在的 bean 类型,并将结果存储在 missing 列表中。filter实际上是调用matches函数,matches函数再调用isPresent函数,isPresent函数在调用resolve函数,resolve中会使用Class.forName(className, false, classLoader)来判断,如果加载成功,就说明该类存在。
       if (!missing.isEmpty()) {//如果 missing 列表不为空,说明存在某些 bean 类型在当前 Spring 容器中不存在。使用 ConditionMessage 来创建条件判断失败的消息,指出找不到所需的 bean 类型。
          ConditionMessage message = ConditionMessage.forCondition(annotation)
             .didNotFind("required type", "required types")
             .items(Style.QUOTE, missing);
          return ConditionOutcome.noMatch(message);
       }
       return null;
    }
  2. OnClassCondition :基于 @ConditionalOnClass 注解
    @Override
    protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
          AutoConfigurationMetadata autoConfigurationMetadata) {
       if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {//如果autoConfigurationClasses 数组的长度大于 1,且当前运行时的可用处理器数量大于 1,说明可以使用多线程来执行,调用 resolveOutcomesThreaded 方法。
          return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
       }
       else {//否则直接创建一个 StandardOutcomesResolver 实例,并调用 resolveOutcomes 方法,获取条件判断结果的数组。
          OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
                autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
          return outcomesResolver.resolveOutcomes();
       }
    }
    //两个方法应该是类似的,我们直接看单线程的那个方法。resolveOutcomes直接调用getOutcomes,所以直接看StandardOutcomesResolver类的getOutcomes方法:逻辑和上面OnBeanCondition的getOutcomes函数很类似,就不做赘述了。
    private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
          AutoConfigurationMetadata autoConfigurationMetadata) {
       ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
       for (int i = start; i < end; i++) {
          String autoConfigurationClass = autoConfigurationClasses[i];
          if (autoConfigurationClass != null) {
             String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
             if (candidates != null) {
                outcomes[i - start] = getOutcome(candidates);
             }
          }
       }
       return outcomes;
    }
  3. OnWebApplicationCondition:基于应用程序是否是 Web 应用程序的条件
    //逻辑和上面OnBeanCondition的getOutcomes函数也很类似,就不做赘述了。
    @Override
    protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
          AutoConfigurationMetadata autoConfigurationMetadata) {
       ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
       for (int i = 0; i < outcomes.length; i++) {
          String autoConfigurationClass = autoConfigurationClasses[i];
          if (autoConfigurationClass != null) {
             outcomes[i] = getOutcome(autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));//getOutcome函数的作用是检查是否存在指定的 Web 应用类(Servlet Web、Reactive Web),如果都不存在,再用ClassNameFilter.isPresent 方法检查是否存在 SERVLET_WEB_APPLICATION_CLASS 类,用 ClassUtils.isPresent 方法检查是否存在 REACTIVE_WEB_APPLICATION_CLASS 类,如果以上条件都不满足,说明不是Web应用,返回NULL。
          }
       }
       return outcomes;
    }

    在获得了判断结果后,即可获得过滤完成的configurations。

  • 第十二行「fireAutoConfigurationImportEvents(configurations, exclusions);」用于在 Spring Boot 中触发自动配置导入事件。在 Spring Boot 的自动配置过程中,当自动配置类被加载和应用时,可以触发一系列事件,这些事件通常用于通知监听器或其他组件,某些自动配置已经被应用。这个方法会创建一个 AutoConfigurationImportEvent 对象,然后将它发送到 Spring 应用程序上下文中。通过监听这个事件,就可以在自动配置过程中执行自定义逻辑,或者根据配置的情况进行一些额外的处理。看源码:
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
        List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();//获取所有已注册的 AutoConfigurationImportListener 监听器,思路和获得过滤器的思路一样,都是调用SpringFactoriesLoader.loadFactories方法。
        if (!listeners.isEmpty()) {//如果不为空,说明存在监听器需要处理自动配置导入事件。
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);//创建一个 AutoConfigurationImportEvent 对象,该对象包含了触发事件的上下文信息,包括被导入的自动配置类列表 configurations 和被排除的自动配置类列表 exclusions。
            for (AutoConfigurationImportListener listener : listeners) {//遍历所有的监听器,逐个调用它们的 onAutoConfigurationImportEvent 方法,将创建的事件对象传递给监听器。
                invokeAwareMethods(listener);//确保监听器可以获取必要的上下文信息。
                listener.onAutoConfigurationImportEvent(event);
            }
        }
    }
  • 第十三行「return new AutoConfigurationEntry(configurations, exclusions);」就是返回最终的自动配置类名列表和排除列表。

好了,selectImports函数终于讲完了,好大一串玩意,但是AutoConfigurationImportSelector类还并没有讲完呢......

2.5.3AutoConfigurationGroup

当我们进行debug时,会发现如果断点直接打在selectImports方法上时,是没有反应的;而如果将断点打在内部代码上,Step out一下,就会发现实际上跑的是AutoConfigurationGroup的process函数。

那么我们就再讲讲为什么是这个process函数。如果我们一层层看上去,可以一直追溯到AbstractApplicationContext的refresh(),但是我们不用管那么多,先搞明白AutoConfigurationGroup的功能。


AutoConfigurationGroup 是 AutoConfigurationImportSelector 的内部类,用于协调和管理多个导入选择器的行为。(这种设计是为了更加灵活地处理自动配置的导入,并且允许根据条件进行分组管理。

在 Spring Boot 的自动配置导入过程中,会先解析 AutoConfigurationImportSelector,然后在其内部实例化 AutoConfigurationGroup,并最终调用 AutoConfigurationGroup 的 process 方法。


然后我们要了解一下ConfigurationClassParser的工作流程(但这个不是现在的重点):

  1. parse() 方法是入口,调用 doProcessConfigurationClass() 处理配置类的元数据。
  2. 在 doProcessConfigurationClass() 方法中,依次调用 processConfigurationClass()、processImports()、processMemberClasses()、processBeanMethods() 处理不同的元数据信息。
  3. 在 ConfigurationClassParser 的 doProcessConfigurationClass() 方法中,会调用 processImports() 方法,而在 processImports() 方法内部,会调用 processGroupImports() 方法来处理所有的分组导入选择器,而 processGroupImports() 方法会调用 getImports(),然后再调用selectImports()。

 看一下getImports函数的源码:

public Iterable<Group.Entry> getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}

getImports 方法会遍历 this.deferredImports 列表,对每个 DeferredImportSelectorHolder 调用其 getConfigurationClass().getMetadata() 来获取配置类的元数据,并调用 deferredImport.getImportSelector() 来获取相关的导入选择器。随后,将这些信息传递给 this.group.process 方法,最后再调用 selectImports 方法获得确定需要导入的自动配置类。

所以执行的实际上是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()));//使用断言来确保 deferredImportSelector 是 AutoConfigurationImportSelector 的实例,因为 AutoConfigurationGroup 只支持处理这种类型的导入选择器。
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);//通过调用 getAutoConfigurationEntry() 方法来获取导入选择器中的自动配置信息,其中包括了导入的自动配置类以及其对应的排除类。(其实和selectImports函数中的逻辑是一样的)
            this.autoConfigurationEntries.add(autoConfigurationEntry);//将获取的 AutoConfigurationEntry 对象添加到 autoConfigurationEntries 列表中,这个列表用于后续选择导入的自动配置。
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }//将自动配置类名作为键,将对应的注解元数据作为值,存储到 entries 集合中。这个集合用于后续选择导入的自动配置时查找对应的元数据。
        }
  @Override
        public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {//检查 autoConfigurationEntries 是否为空。如果为空,表示没有任何自动配置条目,直接返回一个空集合。
                return Collections.emptyList();
            }
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                .map(AutoConfigurationEntry::getExclusions)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());//对所有自动配置条目中的排除项进行合并,得到一个包含所有要排除的自动配置类的集合 allExclusions。
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                .map(AutoConfigurationEntry::getConfigurations)
                .flatMap(Collection::stream)
                .collect(Collectors.toCollection(LinkedHashSet::new));//遍历所有自动配置条目,将每个条目中的配置类合并到一个 processedConfigurations 集合中。
            processedConfigurations.removeAll(allExclusions);//排除已经在 allExclusions 中的配置类。

            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
                .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
                .collect(Collectors.toList());//使用 sortAutoConfigurations 方法对 processedConfigurations 进行排序,以便确定自动配置的加载顺序;再遍历排序后的自动配置类名,将每个自动配置类名与对应的 Entry 对象关联起来,形成最终的导入项集合,并返回。
        }

看完了这个逻辑之后,我们就发现,AutoConfigurationGroup 的process+selectImports的逻辑其实和原先 AutoConfigurationImportSelector 的 selectImports 的逻辑是一致的,只是 selectImports 是直接返回String[],Spring容器对返回的配置类进行实例化;而process是将所有成员的autoConfigurationEntry放入到一个List中,等所有组成员的autoConfigurationEntry都收集好后,调用selectImports将最终要导入的自动配置类返回回去,供后续的自动装配过程中使用。

2.5.4Spring IOC源码

如果要理解为什么自定义的Bean生效了,自动装配功能就不会将其再次实例化,就必须要去看Spring IOC的源码,但是这已经超出了本文的范畴了,请读者课外了解吧。(如果有读者知道相关文章,希望可以在评论区指一下路。)

2.6@AutoConfigurationPackage

好了现在我们已经大致了解了@Import(AutoConfigurationImportSelector.class)的工作流程,我们回到这个注解的上一个注解:@AutoConfigurationPackage。

@AutoConfigurationPackage用于指示 Spring Boot 在自动配置时要扫描的基础包,可以告诉 Spring Boot 在进行自动装配时应该扫描哪些包来寻找需要自动配置的类。比如我们可能希望自动装配的范围更广,可能需要在多个模块或不同的包中进行自动配置。这时就可以使用 @AutoConfigurationPackage 注解来显式地指定一个基础包,使 Spring Boot 在自动装配时也会扫描该包及其子包下的类。看一下源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

发现@Import了一个AutoConfigurationPackages的内部类Registrar。看一下他的源码:我们又看到了一个很熟悉的东西——ImportBeanDefinitionRegistrar,我们之前介绍过,这个类是用来以编程方式注册额外的 bean 定义的。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {//这两个接口用于处理 bean 定义的注册和导入操作
   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
   }//ImportBeanDefinitionRegistrar接口的方法。
   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImports(metadata));
   }//DeterminableImports接口的方法。这个接口的作用是返回一个包含导入信息的集合,以告诉 Spring 容器需要导入哪些额外的类或配置。即在 Spring 容器中注册一个 PackageImports 对象,以供 Spring Boot 的自动配置过程使用,帮助确定需要扫描的基础包范围。
}
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
   if (registry.containsBeanDefinition(BEAN)) {//检查 BeanDefinitionRegistry 是否已经包含名为 BEAN 的 bean 定义,Bean即为"org.springframework.boot.autoconfigure.AutoConfigurationPackage"
      BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
      beanDefinition.addBasePackages(packageNames);//如果已存在,则尝试将传入的包名数组添加到现有的 bean 定义中。尝试将传入的包名数组添加到现有的 bean 定义中。
   }
   else {//如果不存在,就创建一个新的 BasePackagesBeanDefinition 对象,并将传入的包名数组作为参数传递给构造函数。然后,使用 BeanDefinitionRegistry 的 registerBeanDefinition 方法将这个新的 bean 定义注册到容器中。
      registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
   }
}
 static final class BasePackagesBeanDefinition extends GenericBeanDefinition {

   private final Set<String> basePackages = new LinkedHashSet<>();

   BasePackagesBeanDefinition(String... basePackages) {
      setBeanClass(BasePackages.class);//设置 bean 类型为 BasePackages.class。这意味着这个 bean 实际上是一个 BasePackages 类型的实例。
      setRole(BeanDefinition.ROLE_INFRASTRUCTURE);//设置 bean 的角色为基础设施(ROLE_INFRASTRUCTURE)。这意味着这个 bean 是一个基础设施类,可能不会被用户直接访问,而是用于内部处理。
      addBasePackages(basePackages);//调用 addBasePackages 方法,将传入的基础包名数组添加到 basePackages 集合中。
   }

   @Override
   public Supplier<?> getInstanceSupplier() {
      return () -> new BasePackages(StringUtils.toStringArray(this.basePackages));
   }

   private void addBasePackages(String[] additionalBasePackages) {
      this.basePackages.addAll(Arrays.asList(additionalBasePackages));
   }

}

看完源码,我们发现他很奇怪地将一个基础包的包名加到了AutoConfigurationPackage的Bean定义中,Spring容器其实会根据这个基础包名进行扫描,并扫描其子包中的类,将这些类进行自动装配。(如何使用BeanDefinition中的包名进行自动装配就是Spring IOC的任务了,这里就省略了。)

这里的@AutoConfigurationPackage没有加任何参数,说明他就是直接将主启动类的所在的包作为扫描的基础包。其实这个注解不是为了扫描当前包而使用的,因为@ComponentScan已经有这个作用了,其实他的作用就是单纯地把包路径放到Bean容器中,以备后续的使用...(determineImports 方法和 registerBeanDefinitions 其实也有重复的功能,但是它们是在不同的上下文中使用的,这种设计可能是为了让 Spring Boot 的自动配置更加内聚和灵活。)

2.7TypeExcludeFilter、AutoConfigurationExcludeFilter

接下来还差一个@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })没有讲,那么就最后讲一下这个注解和这两个过滤器。

先提到一点,就是其实@AutoConfigurationPackage和@ComponentScan的作用是有重复的,但是他们之间也是有区别的:

  • 扫描方式:@ComponentScan 扫描通过 ClassPathBeanDefinitionScanner 类实现扫描;@AutoConfigurationPackage通过注册一个特殊的 BasePackagesBeanDefinition bean 定义来实现,该 bean 存储了需要扫描的基础包名。
  • 使用场景:@ComponentScan 适用于寻找和注册自定义的 Spring 组件,如带有 @Component、@Service、@Repository、@Controller 等注解的类。也可以用于注册带有其他自定义注解的类为 Spring bean;@AutoConfigurationPackage适用于 Spring Boot 自动配置过程,用于控制自动配置时需要扫描的基础包。可以用于在项目中扩展自动配置范围,使得自动装配能够更好地适应多模块或跨包的项目结构。

这个注解的扫描功能大家都知道,而且是SpringIOC的功能,这里就不讲了,主要还是讲讲两个过滤器的功能。

2.7.1AutoConfigurationExcludeFilter.class

还是先讲AutoConfigurationExcludeFilter.class吧,这个简单一点,看源码:AutoConfigurationExcludeFilter的match会对@ComponentScan扫描到的类进行过滤。

public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
//实现了TypeFilter接口,说明该类是用来过滤被扫描类的。
    private ClassLoader beanClassLoader;

    private volatile List<String> autoConfigurations;

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {//设置类加载器
        this.beanClassLoader = beanClassLoader;
    }

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    }

    private boolean isConfiguration(MetadataReader metadataReader) {//判断是否是配置类
        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());//判断有无 @Configuration 注解
    }

    private boolean isAutoConfiguration(MetadataReader metadataReader) {//判断是否是自动配置类或者在自动配置列表中
        boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata()
            .isAnnotated(AutoConfiguration.class.getName());//判断有无 @AutoConfiguration 注解
        return annotatedWithAutoConfiguration
                || getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());//判断是否在自动配置列表中
    }

    protected List<String> getAutoConfigurations() {//这个和之前的EnableAutoConfiguration.class的selectImports函数的getCandidateConfigurations的逻辑是一样的,获得spring.factories和org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的类。
        if (this.autoConfigurations == null) {
            List<String> autoConfigurations = new ArrayList<>(
                    SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
            ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader).forEach(autoConfigurations::add);
            this.autoConfigurations = autoConfigurations;
        }
        return this.autoConfigurations;
    }

}

根据上述源码,我们可以得出结论,AutoConfigurationExcludeFilter用于过滤调用同时是@Configuration和自动装配的类,避免可能发生的重复实例化同一个类的 Bean。

2.7.2TypeExcludeFilter

TypeExcludeFilter的match同样会对@ComponentScan扫描到的类进行过滤。

public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {

   private BeanFactory beanFactory;
   private Collection<TypeExcludeFilter> delegates;

   @Override
   public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
         throws IOException {
      //如果当前的 TypeExcludeFilter 是 TypeExcludeFilter 类的实例,并且 beanFactory 是 ListableBeanFactory,那么会调用其他实现了 TypeExcludeFilter 接口的类的 match 方法来判断是否应该排除当前类。
      if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
         for (TypeExcludeFilter delegate : getDelegates()) {
            if (delegate.match(metadataReader, metadataReaderFactory)) {
               return true;
            }
         }
      }
      return false;
   }
   private Collection<TypeExcludeFilter> getDelegates() {
      // 获取其他 TypeExcludeFilter 实例的集合。如果 delegates 为空,那么会从 beanFactory 中获取所有实现了 TypeExcludeFilter 接口的 Bean。
      Collection<TypeExcludeFilter> delegates = this.delegates;
      if (delegates == null) {
         delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
         this.delegates = delegates;
      }
      return delegates;
   }
   //...
}

观察源码,我们发现TypeExcludeFilter 类的match用于统筹所有Bean容器中的TypeExcludeFilter 实例一起进行过滤,但是默认情况下是不会自动注册其他的 TypeExcludeFilter 实例,所以可以忽略。

补充:自定义的TypeExcludeFilter不能直接使用@Component注解进行配置,因为TypeExcludeFilter要求在@ComponentScan开始扫描前就已经加载包,所以他需要先在spring.factories中添加初始化器,然后再在初始化器中添加自定义TypeExcludeFilter,将其注册到spring容器才行。

三、代码举例

3.1自定义ImportBeanDefinitionRegistrar实现、自定义ImportSelector实现、自定义注解,以通过一个注解实现一堆Bean的功能。

项目结构为

 @SpringBootApplication注解在Main中,会不自动扫描到chilun2包中的组件。在MyController中添加注解@MyEnableAutoConfiguration,将注入pojo类TestC和TestD。


先定义两个pojo:我们将会使用ImportBeanDefinitionRegistrar的实现类对TestC进行注入,使用ImportSelector和对应的配置类对TestD进行注入。

public class TestC {
    @Value("CCC")
    public String name;

    @Override
    public String toString() {
        return "TestC{name='" + name + "\'}";
    }
}
public class TestD {
    @Value("DDD")
    public String name;

    @Override
    public String toString() {
        return "TestD{name='" + name + "\'}";
    }
}

然后先是ImportBeanDefinitionRegistrar的动态注入Bean功能:注入了TestC。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("testC",BeanDefinitionBuilder.genericBeanDefinition(TestC.class).getBeanDefinition());
    }
}

然后是使用ImportSelector注入配置类,再使用配置类MyConfig注入TestD:

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.chilun2.TestMyEnableAutoConfiguration.config.MyConfig"};
    }
}
@Configuration
public class MyConfig {
    @Bean
    public TestD getTestD(){
        return new TestD();
    }
}

最后是自定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportBeanDefinitionRegistrar.class, MyImportSelector.class})
public @interface MyEnableAutoConfiguration {
}

然后我们就能直接使用@MyEnableAutoConfiguration来获得TestC和TestD的实例:

@RestController
@MyEnableAutoConfiguration
public class MyController {
    @Autowired
    TestC testC;

    @Autowired
    TestD testD;

    @GetMapping("/test2")
    public String test2(){
        return testC.toString()+testD.toString();
    }
}

执行效果:

3.2自定义Starter

我们尝试性地写一个很简单很简单的Starter,他的作用是单纯的将一个TestE类的Bean实例进行自动装载(何为自动装载?就是我在测试项目中只需要把那个Starter的依赖导入,然后@Autowired TestE testE,我就能拿到一个完整的testE)。

3.2.1实现Starter

好,那就先把Starter写好。

使用Maven创建一个项目,这是项目结构:

然后是两个pojo的代码,待会测试就是测试这两个pojo能不能直接注入:(已省略getset函数)

public class TestF {
    @Value("FFF")
    private String name;

}
public class TestE {
    @Value("EEE")
    private String name;

    @Autowired
    private TestF testF;

}

 然后是自动配置类:(注意看,我们并没有使用什么ConditionOnClass之类的注解,因为那是用来过滤的,我们要的是直接加载,不需要过滤,只需要将这个类写到spring.factories或.imports文件中即可)

@Configuration
public class MyAutoConfig {

    @Bean
    public TestE testE(){
        return new TestE();
    }

    @Bean
    public TestF testF(){
        return new TestF();
    }
}

然后是spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.chilun.starter.config.MyAutoConfig

然后使用maven进行构建一下,把jar包放到本地仓库:(直接在终端输入 mvn install)

补充一下pom.xml文件,就是加入spring-boot-autoconfigure的依赖即可:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>MyStarter-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.7.9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

这是终端输出的结果。

3.2.2测试Starter
<dependency>
    <groupId>org.example</groupId>
    <artifactId>MyStarter-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

看一下项目结构有没有问题:

 没有什么问题,然后就直接测试:(在RestController中进行测试)

public class MyController {
    @Autowired
    TestE testE;

    @GetMapping("/test3")
    public String test3(){
        return testE.getTestF().getName();
    }
}

访问一下:

 很完美,说明自动装配成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值