SpringBoot自動裝配-源碼分析


1. 簡介

通過源碼探究SpringBoot的自動裝配功能。

2. 核心代碼

2.1 啟動類

我們都知道SpringBoot項目創建好后,會自動生成一個當前模塊的啟動類。如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

2.2 @SpringBootApplication

在啟動類中有個很重要的注解@SpringBootApplication,在該注解中除了元注解,就是@SpringBootConfiguration
@EnableAutoConfiguration@ComponentScan

  • @SpringBootConfiguration:標識了當前類為配置類
  • @ComponentScan:配置類的組件掃描
  • @EnableAutoConfiguration:激活自動裝配
@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 {
  /** 
   * 排除特定的自動配置類,以便它們永遠不會被應用
   */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};
  /** 
   * 排除特定的自動配置類名稱,以便它們永遠不會被應用
   */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};

  /**
   * 用於掃描帶注解組件的基本包
   */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
  
  /**
   * 用於指定要掃描帶注釋組件的包。將掃描指定的每個類的包。
   */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  Class<?>[] scanBasePackageClasses() default {};
  ...
}

2.3 @EnableAutoConfiguration

這里我們重點看@EnableAutoConfiguration注解。

在該注解中我們看到了熟悉的@Import注解,並且該注解指定導入了AutoConfigurationImportSelector.class

@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 {};
}

2.4 AutoConfigurationImportSelector

我們進入到AutoConfigurationImportSelector.class,看到當前類繼承自DeferredImportSelector接口,而通過查看DeferredImportSelector源碼 public interface DeferredImportSelector extends ImportSelector {}得知,DeferredImportSelector繼承自ImportSelector接口。因此我們大概得知SpringBoot默認裝載了ImportSelector::selectImports()方法返回的全限類名數組。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
        /**
         * 重寫ImportSelector接口中的selectImports方法
         * <p>
         *    該方法返回的數組<全限類名> 都將被裝載到IOC容器
         */
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
	    if (!isEnabled(annotationMetadata)) {
	        return NO_IMPORTS;
	    }
	    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
            // 將符合注入IOC條件的Bean類信息返回
	    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 = removeDuplicates(configurations);
	    // 獲取任何限制候選配置的排除項
	    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	    // 判斷排除項是否存在
	    checkExcludedClasses(configurations, exclusions);
	    // 從候選配置集合中排除需要排除的項
	    configurations.removeAll(exclusions);
	    // 獲取在spring.factories中注冊的過濾器,並執行filter方法,返回符合注冊條件的元素
	    configurations = getConfigurationClassFilter().filter(configurations);
	    // 觸發自動配置導入事件
	    fireAutoConfigurationImportEvents(configurations, exclusions);
	    // 返回自動配置和排除項信息
	    return new AutoConfigurationEntry(configurations, exclusions);
	}
  
	/**
	 * 獲取屬性
	 */
	protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
	    String name = getAnnotationClass().getName();
	    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
	    Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName() 
                + " annotated with " + ClassUtils.getShortName(name) + "?");
	    return attributes;
	}
  
	/**
	 * 獲取候選的配置信息
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
	        getBeanClassLoader());
            // 這個就很重要了,從這里大概可以判斷出 配置信息是從META-INF/spring.factories這個文件中獲取到的
	    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;
	}
}

2.5 SpringFactoriesLoader

為了驗證配置信息是不是從META-INF/spring.factories獲取的,我們繼續跟蹤源碼SpringFactoriesLoader::loadFactoryNames()

public final class SpringFactoriesLoader {

	/**
	 * 工廠資源位置 
	 *
	 * <p>
	 *     可以存在於多個Jar文件中
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

	static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

	/**
	 * 加載工廠名稱
	 * 
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
                // 當前上下文中 factoryTypeName = EnableAutoConfiguration注解的全限類名
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	/**
	 * 加載spring工廠
	 */
	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<>();
		try {
                    // 獲取 META-INF/spring.factories 枚舉信息
		    Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
		    while (urls.hasMoreElements()) {
                        // spring.factories 文件地址
			URL url = urls.nextElement();
                        // 獲取resource信息
			UrlResource resource = new UrlResource(url);
                        // 加載配置文件中的配置信息
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                        // 遍歷配置信息放入全局的Map緩存中
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
			    String factoryTypeName = ((String) entry.getKey()).trim();
			    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);
		}
		catch (IOException ex) {
		    throw new IllegalArgumentException("Unable to load factories from location [" +
		        FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}
}

在這里為了更方便的查看loadSpringFactories中各步驟是用來干嘛的,特意添加debug截圖如下:

2.6 spring.factories

spring-boot-autoconfigure下的META-INF/spring.factories文件信息

從上圖中我們能看出spring.factories 中指定了很多常用中間件的auto configure文件信息。

2.7 RedisAutoConfiguration

我們僅查看我們比較熟悉的redis中間件的autoconfiguration文件信息

RedisAutoConfiguration源碼中我們能看出在文件中使用很多的@Conditional注解來實現注入符合條件的SpringBean

// 標識為配置類
@Configuration(proxyBeanMethods = false)
// 當存在RedisOperations.class時注入當前類
@ConditionalOnClass(RedisOperations.class)
// 激活RedisProperties屬性文件
@EnableConfigurationProperties(RedisProperties.class)
// 導入客戶端配置類
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

  @Bean
  // 當 當前環境中沒有redisTemplate Bean時注入當前Bean
  @ConditionalOnMissingBean(name = "redisTemplate")
  /*
   * 當指定RedisConnectionFactory類已存在於 BeanFactory 中,並且可以確定單個候選項才會匹配成功。
   * 或者 BeanFactory 存在多個 RedisConnectionFactory 實例,但是有一個 primary 候選項被指定(通常在類上使用 @Primary 		 * 注解),也會匹配成功
   */ 
  @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

}

3. 小結

至此我們大概了解了SpringBoot是如何實現自動裝配的。

  1. 項目啟動
  2. 通過啟動類上的@SpringBootApplication注解加載@EnableAutoConfiguration注解
  3. 通過@EnableAutoConfiguration加載@Import(AutoConfigurationImportSelector.class)執行AutoConfigurationImportSelector導入選擇器
  4. AutoConfigurationImportSelector中執行selectImports()方法
  5. AutoConfigurationImportSelector::selectImports()通過加載ClassPath下的META-INF/spring.factories文件來動態的注入*AutoConfiguration類
  6. *AutoConfiguration類中通過使用@Conditional注解及其派生注解實現了Bean的靈活裝載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM