两步了解SpringBoot自动配置

本文深入解析了SpringBoot的自动配置机制,从@SpringBootApplication注解的源码入手,详细阐述了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个核心注解的功能。@SpringBootConfiguration标识配置类,@EnableAutoConfiguration启动自动配置,@ComponentScan用于组件扫描。自动配置通过@EnableAutoConfiguration的exclude/excludeName排除特定配置,然后借助@Import导入AutoConfigurationImportSelector,该选择器根据条件过滤并加载配置类,确保只有符合条件的自动配置类被加载到Spring容器中。整个过程涉及类路径扫描、条件注解判断以及事件监听,实现了SpringBoot的智能化配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概述

自动配置:根据添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法。@SpringBootApplication: SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

2. 核心注解

2.1 @SpringBootApplication

@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@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 {};

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;
}

从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:
@SpringBootConfiguration
@EnableAutoConfiguration、
@ComponentScan
这三个就是@SpringBootApplication的核心注解,后面会对他们分别进行分析。

2.2 @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
public @interface SpringBootConfiguration {
   
   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}

@SpringBootConfiguration: SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。

2.3 @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class) //Spring底层的@Import注解,给容器中导入一个组件
//告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   
   //返回不会被导入到 Spring 容器中的类
   Class<?>[] exclude() default {};
   
   //返回不会被导入到 Spring 容器中的类名
   String[] excludeName() default {};
}

Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IOC容器。

2.3.1 @AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class) //导入Registrar中注册的组件
public @interface AutoConfigurationPackage {

}

@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例: @Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar类registerBeanDefinitions 方法:

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      //将注解标注的元信息传入,获取到相应的报名
      register(registry, new PackageImport(metadata).getPackageName());
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImport(metadata));
   }
}

debug模式进入到register方法处:
在这里插入图片描述
可以看到com.lagou这个包名就是启动类所处的包路径。继续到register方法中看一下:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
   // 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
   // @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包
   if (registry.containsBeanDefinition(BEAN)) {
      // 如果该bean已经注册,则将要注册包名称添加进去
      BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
      ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
      constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
   }
   else {
      //如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      beanDefinition.setBeanClass(BasePackages.class);
      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
      beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      registry.registerBeanDefinition(BEAN, beanDefinition);
   }
}

关注一下这里的这个BEAN定义是:

private static final String BEAN = AutoConfigurationPackages.class.getName();

AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

2.3.2 @Import(AutoConfigurationImportSelector.class)

该注解就是将AutoConfigurationImportSelector 这个类导入到 Spring 容器中,
AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
在这里插入图片描述
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被调。
确定自动配置实现逻辑的入口方法:
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法:

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

【1】 处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector()) ;主要做的事情就是在 this.group 即AutoConfigurationGroup 对象的 process 方法中,传入的 AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。

注:AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;
AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;
metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据
标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入

分析自动配置逻辑:
AutoConfigurationImportSelector.AutoConfigurationGroup#process方法:

@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()));
   // [1] 调用 getAutoConfigurationEntry方法得到自动配置类放到autoConfigurationEntry对象中
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
   // [2] 又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   // [3] 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

在 [1] 调用 getAutoConfigurationEntry方法得到自动配置类放到autoConfigurationEntry对象中, 这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑

//获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
   // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
   // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //[1] 得到spring.factories文件配置的所有自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 利用LinkedHashSet移除重复的配置类
   configurations = removeDuplicates(configurations);
   // 得到要排除的自动配置类,比如注解属性exclude的配置类
   // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
   // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
   checkExcludedClasses(configurations, exclusions);
   // [2] 将要排除的配置类移除
   configurations.removeAll(exclusions);
   // [3] 因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载
   //进内存,会造成内存浪费,因此这里需要进行过滤
   //// 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
   //@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分
   //析一下
   configurations = filter(configurations, autoConfigurationMetadata);
   // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
   // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
   // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
   fireAutoConfigurationImportEvents(configurations, exclusions);
   // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
   return new AutoConfigurationEntry(configurations, exclusions);
}

深入了解getCandidateConfigurations方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   //这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和
   //getBeanClassLoader()
   // getSpringFactoriesLoaderFactoryClass()这个方法返回的是
   //EnableAutoConfiguration.class
   //getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

继续观察loadFactoryNames方法:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
        //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();
            //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryImplementationName = var9[var11];
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下spring.factories文件。
spring.factories里面保存着springboot的默认提供的自动配置类。
META-INF/spring.factories
在这里插入图片描述

AutoConfigurationEntry 方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。
下面看一下AutoConfigurationImportSelectorfilter 方法,了解一下是怎么样过滤自动配置类的:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   // 将从spring.factories中获取的自动配置类转出字符串数组
   String[] candidates = StringUtils.toStringArray(configurations);
   // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   // getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
   // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
      // 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
      invokeAwareMethods(filter);
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {
            skip[i] = true;
            candidates[i] = null;
            skipped = true;
         }
      }
   }
   if (!skipped) {
      return configurations;
   }
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return new ArrayList<>(result);
}

AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。

有选择的导入自动配置类
AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports

@Override
public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   // 这里得到所有要排除的自动配置类的set集合
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   // 这里得到经过滤后所有符合条件的自动配置类的set集合
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   // 移除掉要排除的自动配置类
   processedConfigurations.removeAll(allExclusions);
   // 对标注有@Order注解的自动配置类进行排序
   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

可以看到, selectImports 方法主要是针对经过排除掉 exclude 的和被AutoConfigurationImportFilter 接口过滤后的满足条件的自动配置类再进一步排除 exclude 的自动配置类,然后再排序。

2.4 总结

最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:

  1. 从spring.factories配置文件中加载自动配置类;
  2. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
  3. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和@ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
  4. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值