zoukankan      html  css  js  c++  java
  • 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 修改指定配置,来制定自己的个性化服务。
  • 相关阅读:
    [转]create a basic sql server 2005 trigger to send email alerts
    SDUT OJ 2783 小P寻宝记
    联想杨元庆:互联网不包治百病 概念被夸大
    【Stackoverflow好问题】Java += 操作符实质
    poj 2513 Colored Sticks (trie 树)
    Nginx基础教程PPT
    POJ 1753 Flip Game (DFS + 枚举)
    poj 3020 Antenna Placement (最小路径覆盖)
    Unable to boot : please use a kernel appropriate for your cpu
    HDU 2844 Coins (多重背包)
  • 原文地址:https://www.cnblogs.com/itzhouq/p/13778496.html
Copyright © 2011-2022 走看看