springboot 自动装配原理

在理解springboot自动装配之前需要了解spring的@Configuration原理和@Conditional两个注解原理。可以看下以前写的这两篇文章

spring 源码阅读之@Configuration解析

spring使用@Conditional进行条件装配

spring的SPI机制之使用SpringFactoriesLoader加载服务实现

@SpringBootApplication注解

一切的开始要从SpringBootApplication注解开始,来看下SpringBootApplication注解定义

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

1、@SpringBootConfiguration注解用来标识当前是一个@Configuration配置类

2、@ComponentScan指定exclude,主要是跳过即有@Configuration注解又有@EnableAutoConfiguration注解的类。先不加载。

3、@EnableAutoConfiguration开启自动装配,引入AutoConfigurationImportSelector类,AutoConfigurationImportSelector是一个ImportSelector。

前面的文章说@Configuration注解的时候,注解spring容器默认会有一个ConfigurationClassPostProcessor处理器,这个是registery后置处理器,在bean扫描完后,根据已经获取的bean进行可能的扩展beanDef加载。

ConfigurationClassPostProcessor后置方法会拿出所有的@Configuration类型的bean然后逐一使用ConfigurationClassParser进行解析,然后在处理@Import的时候如果是ImportSelector会调用其selectImports()方法来进行扩展beanDef的加载。

@SpringBootApplication引入@EnableAutoConfiguration注解,然后引入@AutoConfigurationImportSelector注解,其selectImports方法调用getAutoConfigurationEntry()方法来解析自动配置。

AutoConfigurationImportSelector#selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //获取候选的配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   //configurations一系列过滤处理
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
  //@Condition条件过滤,最后剩下需要加载的confiuration
   configurations = getConfigurationClassFilter().filter(configurations);
   //创建AutoConfigurationImport事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

这里看到首先通过getCandidateConfigurations() 获取候选的config类,会通过SpringFactoriesLoader来读取配置。

SpringFactoriesLoader前面说过,可以理解成对java SPI的一种扩展,只不过约定配置文件是META-INF/spring.factories。

SpringFactoriesLoader#loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
	//加载classpath下的所有配置文件
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
        //读取配置文件内容
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
           //value多个用英文逗号相隔,解析成数组
            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);

   return result;
}

这里变量FACTORIES_RESOURCE_LOCATION=“META-INF/spring.factories”。spring.factories文件是一个properties文件,key value形式来标识配置类。多个value用英文逗号相隔。这里解析的是所有的配置,不仅仅是autoconfiguration类型的,所有解析完成会存放到cache后面多次使用,最后还会根据key=org.springframework.boot.autoconfigure.EnableAutoConfiguration从map中获取所有的configuration。

来看下spring-boot.jar中的配置样例:

# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
...

获取到所有的autoconfiguration类后会进行一系列的过滤,主要一步是

getConfigurationClassFilter().filter(configurations)。这一步会对configuration进行条件过滤,这里就使用了spring的@Conditional来进行条件装配,只不过springboot对其进行了扩展了很多注解。

来看几个例子:

RabbitAutoConfiguration

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration

RedisAutoConfiguration

@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration 

JdbcTemplateAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
      NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}

上面几个例子使用的@ConditionalOnClass就是当classpath中有对应的类时才会加载该类,这样经过过滤后就是满足条件的configurations来进行加载。

比如上面的JdbcTemplateAutoConfiguration,满足条件就会引入JdbcTemplateConfiguration,这个类里为我们自动装配好一个JdbcTemplate实例bean,所以在业务代码需要使用jdbcTemplate的直接注入就可以。

class JdbcTemplateConfiguration {
   @Bean
   @Primary
   JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
      JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
      JdbcProperties.Template template = properties.getTemplate();
      jdbcTemplate.setFetchSize(template.getFetchSize());
      jdbcTemplate.setMaxRows(template.getMaxRows());
      if (template.getQueryTimeout() != null) {
         jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
      }
      return jdbcTemplate;
   }

}

你可能感兴趣的:(spring,spring,boot,后端,java)