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的靈活裝載。