SpringBoot自动装配原理

简介: SpringBoot自动装配原理

一、引入

SpringBoot以其强大的自动装配功能简化了应用搭建的复杂性,让开发者能够更高效地构建应用。那么,SpringBoot是如何实现这种“智能配置”的呢?本文将深入探讨SpringBoot的自动装配原理,揭示其背后的“魔法”,帮助大家更好地理解和运用这一工具。

二、基本流程

自动配置流程图

SpringBoot的自动装配主要依赖于Spring框架的条件配置(Conditional Configuration)和Java的配置类(Java Config)功能。以下是自动装配的基本原理:

  1. 启动类注解:SpringBoot应用的启动类上通常会有一个@SpringBootApplication注解,这是一个复合注解,它包括了@EnableAutoConfiguration,正是这个注解开启了自动装配的功能。
  2. 自动配置类:在SpringBoot的jar包中,包含了许多以META-INF/spring.factories文件指定的自动配置类。这些类上通常会有@Configuration注解,表明它们是用来定义Bean的配置类。
  3. 条件化配置:这些自动配置类使用了诸如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean等条件注解,来决定是否创建和配置某个Bean。这些条件注解根据类路径中是否存在某个类、是否已经定义了某个Bean等条件来决定配置是否生效。
  4. 依赖注入:当自动配置类中的条件满足时,SpringBoot会自动创建和配置相应的Bean,并通过依赖注入的方式将它们注入到其他需要它们的Bean中。

三、源码解读

为了深入理解自动装配的原理,我们可以查看SpringBoot的源码。以下是一个简化的源码解析过程:

3.1. 启动类

在构建SpringBoot项目的时候,启动类一般是这样的

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

3.2.@SpringBootApplication注解:

这个注解是一个复合注解,它包括了@EnableAutoConfiguration,而@EnableAutoConfiguration又导入了AutoConfigurationImportSelector类。

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@SpringBootConfiguration  
@EnableAutoConfiguration  
@ComponentScan(excludeFilters = { ... })  
public @interface SpringBootApplication {  
    ...  
}

可以发现@SpringBootApplication注解其实是对@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的封装,也就是它们之间具有相互替代性。本文我们主要讲下自动配置,关注@EnableAutoConfiguration这个注解

@EnableAutoConfiguration主要是标记需要导入哪些配置,这个就是一个key(factoryType),之后依据key获取spring.facteries文件中的配置信息

3.3. @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 导入自动配置的组件
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  ...
}

有两个需要注意的地方,那就是@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class),我们先来看下@AutoConfigurationPackage

3.3.1.@AutoConfigurationPackage

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

3.3.2. @AutoConfigurationImportSelector类:

这个类是自动装配的关键,它实现了DeferredImportSelector接口,用于在配置类解析时动态地导入其他配置类。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {  
    ...  
    @Override  
    public String[] selectImports(AnnotationMetadata annotationMetadata) {  
        // 这里会加载META-INF/spring.factories中定义的自动配置类  
        ...  
    }  
    ...  
}

进入到组件AutoConfigurationPackages.Registrar

3.3.2.1. AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      // 注册当前主程序的同级以及子集的包组件,其实就是注册了一个Bean的定义
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImports(metadata));
    }
  }
  1. 第一步,我们需要获取被注解标记的类,即***Application。
  2. 在这个类所在的包及其子包中,我们将扫描所有的类文件。
  3. 当扫描到这些类时,我们将把它们导入到Spring容器中进行管理。这意味着它们可以被其他组件使用,并且可以被配置文件spring.factories引用。
3.3.2.3. AutoConfigurationImportSelector类
看下@Import(AutoConfigurationImportSelector.class),通过Spring底层注解@Import,给容器导入一个组件,这个组件就是AutoConfigurationImportSelector.class,它实现了DeferredImportSelector,关键方法是selectImports()
/**
   * 获取需要导入的全限定类名数组
   *
   * @param annotationMetadata
   * @return
   */
  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 配置参数 spring.boot.enableautoconfiguration 是否打开,默认开启
    if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    }
    // 获取自动配置对象,对象包含需要配置的全限定类名列表和需要排除的列表
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
  /**
   * spring.boot.enableautoconfiguration 是否打开,默认处于打开状态
   *
   * @param metadata
   * @return
   */
  protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
      return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
  }

通过getAutoConfigurationEntry方法来获取的配置

/**
   * 基于annotationMetadata发现标有{@link Configuration @Configuration}的配置类并返回{@link AutoConfigurationEntry}
   *
   * @param annotationMetadata 配置类的注解元数据
   * @return 应该被导入的自动配置
   */
  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);
  }

获取原始的配置集合方法是getCandidateConfigurations

/**
   * 返回需要自动配置的类名列表
   *
   * @param metadata   the source metadata
   * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
   *                   attributes}
   * @return a list of candidate configurations
   */
  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 获取需要被加载的FactoryClass,也就是key
    Class<?> clazz = getSpringFactoriesLoaderFactoryClass();
    // 获取需要配置的全限定类名集合
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(clazz, 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;
  }
  
  /**
   * 获取需要加载的工厂类
   * 也就是key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置都需要被加载到IoC
   *
   * @return the factory class
   */
  protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
  }

SpringFactoriesLoader类属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件

META-INF/spring.factories加载配置,来加载到需要自动配置的类的全限定名列表,接下来我们到Spring框架中去看下SpringFactoriesLoader.loadFactoryNames()静态方法.

/**
   * 使用给定的类加载器,从META-INF/spring.factories中加载给定的工厂类型实现
   * 在spring5.3中,如果给定的工厂类型下的实现类名发现不止一次,会进行去重处理
   *
   * @param factoryType factoryType,eg:org.springframework.beans.BeanInfoFactory=xxx
   * @param classLoader 用于加载资源的类加载器
   *                    {@code null} to use the default
   * @throws IllegalArgumentException if an error occurs while loading factory names
   * @see #loadFactories
   */
  public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    // 加载Factories文件
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
  }

关键方法在loadSpringFactories

/**
   * 加载Factories文件
   *
   * @param classLoader factories文件解析之后的map
   * @return
   */
  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 现在缓存中查找,classLoader为key
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
      return result;
    }
    result = new HashMap<>();
    try {
      // 获取资源,这里固定目录为:META-INF/spring.factories
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        UrlResource resource = new UrlResource(url);
        // 将资源加载为Properties
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        // 遍历配置
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
          String factoryTypeName = ((String) entry.getKey()).trim();
          // 获取value的数组,一般为类的全限定名
          // 比如 org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
          String[] factoryImplementationNames =
              StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
          // 添加到result
          for (String factoryImplementationName : factoryImplementationNames) {
            result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                .add(factoryImplementationName.trim());
          }
        }
      }
      // 对value进行去重
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
          .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      // 添加到缓存中
      cache.put(classLoader, result);
    } catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
          FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
  }

获取配置集合,主要流程如下:

  1. 检查缓存:首先,程序会检查缓存(例如一个内存中的数据结构,如ConcurrentHashMap)中是否已经有需要的配置集合。如果有,则直接返回,避免重复加载和解析配置文件。
  2. 加载资源:如果缓存中没有找到配置,程序会利用ClassLoader的getResources方法从类路径(classpath)中加载所有匹配的资源文件。对于Spring
    Boot来说,这通常是查找所有META-INF/spring.factories文件。
  3. 解析文件:加载到资源文件后,程序会解析这些文件的内容。这通常涉及读取文件的每一行,并将每一行解析成key=value的格式,然后保存到集合中。
  4. 缓存结果:解析完所有文件后,程序会将结果保存到缓存中,以便下次可以直接从缓存中获取,无需再次加载和解析文件。
  5. 返回结果:最后,程序返回解析后的配置集合,通常是一个Map<String,
    List>类型的数据结构,其中key是工厂类型(如org.springframework.boot.autoconfigure.EnableAutoConfiguration),value是与该类型关联的配置值列表。

3.4. META-INF/spring.factories文件:

这个文件位于SpringBoot的jar包中,它定义了要加载的自动配置类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\  
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\  
...

4、使用注意事项

虽然SpringBoot的自动装配功能强大且方便,但在使用时还是需要注意以下几点:

避免自动配置冲突:有时候,自动配置可能会与你的手动配置冲突。例如,如果你在配置文件中定义了一个DataSource Bean,而SpringBoot又尝试自动配置一个DataSource Bean,这可能会导致冲突。此时,你可以使用@Primary注解来指定优先使用的Bean,或者使用@EnableAutoConfiguration(exclude={...}来排除某些自动配置类。

注意依赖版本:确保你使用的SpringBoot版本与其他依赖的版本兼容。不兼容的版本可能导致自动装配失败或出现异常行为。

仔细阅读文档:SpringBoot的官方文档非常详尽,包含了大量关于自动装配的信息和示例。在遇到问题时,首先应该查阅官方文档。

调试和日志:如果遇到自动装配相关的问题,可以开启Spring的调试日志(通过设置logging.level.org.springframework=DEBUG),这将帮助你更好地理解自动装配过程中的细节。

5、小结

配合@EnableAutoConfiguration注解使用时,它主要扮演的是配置查找器的角色。这个注解利用其自身的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为搜索的关键词(Key),来定位并加载一组相关的@Configuration类。

因此,@EnableAutoConfiguration的大致自动配置流程可以描述为以下几个步骤:

  1. 首先,它会在classpath中扫描所有的META-INF/spring.factories配置文件。
  2. 接着,它会从这些配置文件中提取所有以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key的配置项(注意,这里的key应严格匹配,包括大小写,且不应有笔误,如EnableautoConfiguration应为EnableAutoConfiguration)。
  3. 最后,通过Java反射机制,它会实例化那些标注有@Configuration注解的Java配置类,这些类采用JavaConfig的方式定义了IoC容器的配置。完成实例化后,这些配置会被统一注册到Spring的IoC容器中,从而完成了自动配置的过程。
相关文章
|
5月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1885 0
|
7月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1155 0
|
4月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
462 3
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
547 2
|
7月前
|
前端开发 Java 数据库连接
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
298 0
|
11月前
|
Java Spring
SpringBoot自动配置原理
本文深入解析了SpringBoot的核心功能——自动配置,重点探讨了`org.springframework.boot.autoconfigure`及相关注解的工作机制。通过分析`@SpringBootApplication`、`@EnableAutoConfiguration`等注解,揭示了SpringBoot如何基于类路径和条件自动装配Bean
546 8
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
468 17
springboot自动配置原理
|
11月前
|
Java
SpringBoot自动装配的原理
在SpringBoot项目的启动引导类上都有一个注解@SpringBootApplication 这个注解是一个复合注解, 其中有三个注解构成 , 分别是 ● @SpringBootConfiguration : 是@Configuration的派生注解 , 标注当前类是一个SpringBoot的配置类 ● @ComponentScan : 开启组件扫描, 默认扫描的是当前启动引导了所在包以及子包 ● @EnableAutoConfiguration : 开启自动配置(自动配置核心注解) 2.在@EnableAutoConfiguration注解的内容使用@Import注解导入了一个AutoC
|
11月前
|
JavaScript 前端开发 Java
Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use;端口冲突的原理与解决方案
本文解决了Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use的问题,并通过介绍端口的使用原理和操作系统的端口管理机制,可以更有效地解决端口冲突问题,并确保Web服务器能够顺利启动和运行。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~