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是如何實現自動裝配的。
- 專案啟動
- 通過啟動類上的
@SpringBootApplication
註解載入@EnableAutoConfiguration註解 - 通過
@EnableAutoConfiguration
載入@Import(AutoConfigurationImportSelector.class)
執行AutoConfigurationImportSelector
匯入選擇器 - 在
AutoConfigurationImportSelector
中執行selectImports()
方法 AutoConfigurationImportSelector::selectImports()
通過載入ClassPath下的META-INF/spring.factories
檔案來動態的注入*AutoConfiguration類- *AutoConfiguration類中通過使用
@Conditional
註解及其派生註解實現了Bean的靈活裝載。