Fork me on GitHub

Spring Boot 自动装配源码分析

基于 Spring Boot 开发的应用中,经常需要引用别的 starter 组件或者自定义公司内部使用的 starter。而 starter 的基础是 Spring Boot的自动装配。

什么是自动装配

自动装配,简单说就是自动将 Bean 装配到 IoC 容器中。下面通过 Spring Boot 整合 Redis 的案例感受一下自动装配的用法。

  • 项目中引入 starter 依赖:
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>2.1.7.RELEASE</version>
</dependency>
  • 添加配置:在 application.properties 中添加:
spring.redis.host=localhost
spring.redis.port=6379
  • 使用:新建测试类 RedisTestController 中使用 RedisTemplate
@RestController
public class RedisTestController {

    @Autowired
    RedisTemplate<String, String> redisTemplate;
    
    @GetMapping("/hello")
    public String hello() {
        redisTemplate.opsForValue().set("key", "value");
        return "Hello World";
    }
}

这是我们一般的使用方式。在这个案例中我们并没有通过 xml 或者注解的方式将 RedisTemplate 注入 IoC 容器中,为什么在 Controller 中可以直接通过 @Autowired 注解注入 RedisTemplate 实例呢?这说明,我们使用该实例之前,容器中已经存在 RedisTemplate 了。这就是 Spring Boot 自动装配的结果。

自动装配的实现

@EnableAutoConfiguration

自动装配在 Spring Boot 中是通过 @EnableAutoConfiguration 注解实现的,这个注解的声明是在启动类注解 @SpringBootApplication 内。

@SpringBootApplication
public class TestApplication {

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

点进 @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 {

可以看到这个是复合注解,点进 @EnableAutoConfiguration 注解

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

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@EnableAutoConfiguration 中 @AutoConfigurationPackage 的作用是把使用了该注解的类所在的包和子包下所有组件扫描到 Spring IoC 容器中。同时,@Import 导入了 @AutoConfigurationImportSelector,这个类会导入需要自动配置的类。

@AutoConfigurationImportSelector

点进 @AutoConfigurationImportSelector 中:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!this.isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  } else {
    // 获取需要自动配置的类
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
}

该注解中重写了顶级接口 ImportSelector 中的 selectImports 方法来导入自动配置类,其中又调用了自身的 getAutoConfigurationEntry 方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
  
  if (!this.isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  } else {
    
    AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
    
    // 获取所有需要自动配置的候选类
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    
    // 移除重复的配置项
    configurations = this.removeDuplicates(configurations);
    
    // 排除:根据exclude属性把不需要自动装配的配置类排除
    Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
    this.checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = this.filter(configurations, autoConfigurationMetadata);
    
    // 广播事件
    this.fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
  }
}

ImportSelector 可以实现批量装配,并且还可以通过逻辑处理来实现 Bean 的选择性装配,也就是可以根据上下文来决定哪些类能够被 IoC 容器初始化。

总的来说,getAutoConfigurationEntry 方法先获取所有配置类,再通过去重,exclude 排除等操作,得到最终需要自动配置的配置类。这里着重看如何获取所有的配置类,也就是 getCandidateConfigurations 方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
  return this.beanClassLoader;
}

这里调用 SpringFactoriesLoader.loadFactoryNames 来加载配置,两个参数,一个是自动配置类的 Class 类型,

EnableAutoConfiguration.class ,另一个是本类的一个类加载器 this.beanClassLoader。

查看 SpringFactoriesLoader.loadFactoryNames 方法:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  
  // 获取Class文件的全限定名,这里对于自动配置,即EnableAutoConfiguration.class.getName()
  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 {
      // 加载META-INF/spring.factories文件,获取URL集合
      Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
      LinkedMultiValueMap result = new LinkedMultiValueMap();

      // 遍历URL集合
      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);
    }
  }
}

这里用到了 SpringFactoriesLoader ,它是 Spring 内部提供的一种约定俗成的加载方式,类似于Java 中的 SPI。简单说,它会扫描 classpath 下的 META-INF/spring.factories 文件,spring.factories 文件中的数据以 key = value 形式存储,而 SpringFactoriesLoader.loadFactoryNames 会根据 key 得到对应的 value 值。因此,这个场景中key 对应为 EnableAutoConfiguration ,value 是多个配置类,也就是 getCandidateConfigurations 方法返回值。

spring.factories

找到依赖中的 spring.factories

该配置文件以键值对给出需要自动配置的配置类,通过上述的代码获取 配置类的 String 数组。

随便找个值如 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration 作为示例:

// 配置类
@Configuration(proxyBeanMethods = false)

// 条件判断,只有指定类加载后,才加载找个配置
@ConditionalOnClass({RabbitTemplate.class, Channel.class})

// 绑定的配置文件
@EnableConfigurationProperties({RabbitProperties.class})

// 导入其他配置
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {
  public RabbitAutoConfiguration() {
  }

  @Configuration(
    proxyBeanMethods = false
  )
  @ConditionalOnClass({RabbitMessagingTemplate.class})
  @ConditionalOnMissingBean({RabbitMessagingTemplate.class})
  @Import({RabbitAutoConfiguration.RabbitTemplateConfiguration.class})
  protected static class MessagingTemplateConfiguration {
    protected MessagingTemplateConfiguration() {
    }

    @Bean
    @ConditionalOnSingleCandidate(RabbitTemplate.class)
    public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
      return new RabbitMessagingTemplate(rabbitTemplate);
    }
  }

可以看到这里对配置做了一定的约束,比如加载顺序、加载条件、额外的组件等。

@ConditionalOnClass :该系列注解用于条件判断,之前写过关于该注解的笔记。

另外,该配置类还绑定了一个配置文件 @EnableConfigurationProperties({RabbitProperties.class}) ,可以根据绑定的这个配置文件做个性化修改:

@ConfigurationProperties(
    prefix = "spring.rabbitmq"
)
public class RabbitProperties {
    private String host = "localhost";
    private int port = 5672;
    private String username = "guest";
    private String password = "guest";
    private final RabbitProperties.Ssl ssl = new RabbitProperties.Ssl();
    private String virtualHost;
    private String addresses;
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration requestedHeartbeat;
    private boolean publisherReturns;
    private ConfirmType publisherConfirmType;
    private Duration connectionTimeout;
    private final RabbitProperties.Cache cache = new RabbitProperties.Cache();
    private final RabbitProperties.Listener listener = new RabbitProperties.Listener();
    private final RabbitProperties.Template template = new Rab

@ConfigurationProperties 标注当前类是配置文件,它绑定前缀为 ”spring.rabbitmq“ 的配置,我们添加到配置会被传递到具体的组件覆盖默认配置。

总结

简单总结 Spring Boot 的自动装配流程:

  1. 通过 @EnableAutoConfiguration 注解开启自动配置。
  2. 自动加载类路径下 META-INF/spring.factories 文件,读取以 EnableAutoConfiguration 的全限定类名对应的值,作为候选配置类。这里默认给出了很多组件的自动配置类。
  3. 自动配置类可能会再导入一些依赖(比如 @Import),或者给出一些配置条件,并且会通过 @Bean 注解把该组件所包含的组件注入到 spring 容器中以供使用。
  4. 自动配置类还可能会绑定 xxxProperties 配置文件类,该类又会和应用程序中的 application.properties 中的指定前缀绑定。第 3 步注入组件的时候,组件可能还会获取配置文件类中的内容,所以用户可以在 application.properties 修改指定配置,来制定自己的个性化服务。
posted @ 2020-10-07 19:10  itzhouq  阅读(324)  评论(0编辑  收藏  举报