該系列文章是本人在學習 Spring 的過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring 源碼分析 GitHub 地址 進行閱讀
Spring 版本:5.1.14.RELEASE
開始閱讀這一系列文章之前,建議先查看《深入了解 Spring IoC(面試題)》這一篇文章
該系列其他文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》
BeanDefinition 的解析過程(面向注解)
前面的幾篇文章對 Spring 解析 XML 文件生成 BeanDefinition 並注冊的過程進行了較為詳細的分析,這種定義 Bean 的方式是面向資源(XML)的方式。面向注解定義 Bean 的方式 Spring 的處理過程又是如何進行的?本文將會分析 Spring 是如何將 @Component 注解或其派生注解
標注的 Class 類解析成 BeanDefinition(Bean 的“前身”)並注冊。
在上一篇 《解析自定義標簽(XML 文件)》文章中提到了處理 <context:component-scan />
標簽的過程中,底層借助於 ClassPathBeanDefinitionScanner 掃描器,去掃描指定路徑下符合條件的 BeanDefinition 們,這個類就是處理 @Component 注解定義 Bean 的底層實現。關於 @ComponentScan 注解的原理也是基於這個掃描器來實現的,我們先來看看這個掃描器的處理過程。
類圖

ClassPathBeanDefinitionScanner
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
,繼承 ClassPathScanningCandidateComponentProvider,classpath 下 BeanDefinition 的掃描器,支持設置過濾器
默認有三個過濾器: @Component
注解的過濾器,Java EE 6 的 javax.annotation.ManagedBean
注解過濾器,JSR-330 的 javax.inject.Named
注解過濾器,這里我們重點關注第一個過濾器
構造函數
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
/** BeanDefinition 注冊中心 DefaultListableBeanFactory */
private final BeanDefinitionRegistry registry;
/** BeanDefinition 的默認配置 */
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
@Nullable
private String[] autowireCandidatePatterns;
/** Bean 的名稱生成器 */
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
/** 是否注冊幾個關於注解的 PostProcessor 處理器 */
private boolean includeAnnotationConfig = true;
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this(registry, true);
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment) {
this(registry, useDefaultFilters, environment,
(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
// 注冊默認的過濾器,@Component 注解的過濾器(具有層次性)
registerDefaultFilters();
}
setEnvironment(environment);
// 設置資源加載對象,會嘗試加載出 CandidateComponentsIndex 對象(保存 `META-INF/spring.components` 文件中的內容,不存在該對象為 `null`)
setResourceLoader(resourceLoader);
}
}
屬性不多,構造函數都會進入最下面這個構造方法,主要調用了兩個方法,如下:
-
調用父類的
registerDefaultFilters()
方法,注冊幾個默認的過濾器,方法如下:protected void registerDefaultFilters() { // 添加 @Component 注解的過濾器(具有層次性),@Component 的派生注解都符合條件 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }
添加
@Component
注解的過濾器(具有層次性),@Component
的派生注解都符合條件也會添加 Java EE 6 的
javax.annotation.ManagedBean
注解過濾器,JSR-330 的javax.inject.Named
注解過濾器 -
調用父類的
setResourceLoader(@Nullable ResourceLoader resourceLoader)
方法,設置資源加載對象並嘗試加載出 CandidateComponentsIndex 對象,方法如下:@Override public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); // 獲取所有 `META-INF/spring.components` 文件中的內容 this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader()); }
這里有個關鍵的步驟,加載出 CandidateComponentsIndex 對象,嘗試去獲取所有
META-INF/spring.components
文件中的內容,后續進行分析
1. scan 方法
scan(String... basePackages)
方法,掃描出包路徑下符合條件 BeanDefinition 並注冊,方法如下:
public int scan(String... basePackages) {
// <1> 獲取掃描前的 BeanDefinition 數量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// <2> 進行掃描,將過濾出來的所有的 .class 文件生成對應的 BeanDefinition 並注冊
doScan(basePackages);
// Register annotation config processors, if necessary.
// <3> 如果 `includeAnnotationConfig` 為 `true`(默認),則注冊幾個關於注解的 PostProcessor 處理器(關鍵)
// 在其他地方也會注冊,內部會進行判斷,已注冊的處理器不會再注冊
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// <4> 返回本次掃描注冊的 BeanDefinition 數量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
過程如下:
-
獲取掃描前的 BeanDefinition 數量
-
進行掃描,將過濾出來的所有的 .class 文件生成對應的 BeanDefinition 並注冊,調用
doScan(String... basePackages)
方法 -
如果
includeAnnotationConfig
為true
(默認),則注冊幾個關於注解的 PostProcessor 處理器(關鍵),在其他地方也會注冊,內部會進行判斷,已注冊的處理器不會再注冊,記住這個 AnnotationConfigUtils 類 -
返回本次掃描注冊的 BeanDefinition 數量
2. doScan 方法
doScan(String... basePackages)
方法,掃描出包路徑下符合條件 BeanDefinition 並注冊,方法如下:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// <1> 定義個 Set 集合 `beanDefinitions`,用於保存本次掃描成功注冊的 BeanDefinition 們
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) { // 遍歷需要掃描的包名
// <2> 【核心】掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出所有符合條件的 BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// <3> 對第 `2` 步解析出來的 BeanDefinition 依次處理,並注冊
for (BeanDefinition candidate : candidates) {
// <3.1> 解析出 @Scope 注解的元信息並設置
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// <3.2> 獲取或者生成一個的名稱 `beanName`
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// <3.3> 設置相關屬性的默認值
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// <3.4> 根據這個類的相關注解設置屬性值(存在則會覆蓋默認值)
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// <3.5> 檢查 beanName 是否已存在,已存在但是不兼容則會拋出異常
if (checkCandidate(beanName, candidate)) {
// <3.6> 將 BeanDefinition 封裝成 BeanDefinitionHolder 對象,這里多了一個 `beanName`
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// <3.7> 如果代理模式是 `TARGET_CLASS`,則再創建一個 BeanDefinition 代理對象(重新設置了相關屬性),原始 BeanDefinition 已注冊
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// <3.8> 添加至 `beanDefinitions` 集合
beanDefinitions.add(definitionHolder);
// <3.9> 注冊該 BeanDefinition
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
// <4> 返回 `beanDefinitions`(已注冊的 BeanDefinition 集合)
return beanDefinitions;
}
過程如下:
- 定義個 Set 集合
beanDefinitions
,用於保存本次掃描成功注冊的 BeanDefinition 們 - 【核心】掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出所有符合條件的 BeanDefinition,調用父類的
findCandidateComponents(String basePackage)
方法 - 對第
2
步解析出來的 BeanDefinition 依次處理,並注冊- 解析出 @Scope 注解的元信息並設置
- 獲取或者生成一個的名稱
beanName
- 設置相關屬性的默認值
- 根據這個類的相關注解設置屬性值(存在則會覆蓋默認值)
- 檢查
beanName
是否已存在,已存在但是不兼容則會拋出異常 - 將 BeanDefinition 封裝成 BeanDefinitionHolder 對象,這里多了一個
beanName
- 如果代理模式是
TARGET_CLASS
,則再創建一個 BeanDefinition 代理對象(重新設置了相關屬性),原始 BeanDefinition 已注冊 - 添加至
beanDefinitions
集合 - 注冊該 BeanDefinition
- 返回
beanDefinitions
(已注冊的 BeanDefinition 集合)
第 2
步是這個掃描過程的核心步驟,在父類 ClassPathScanningCandidateComponentProvider 中進行分析,接下來的處理過程不復雜,獲取相關屬性進行配置
第 7
步創建代理對象,和 AOP 相關,感興趣的可自行查看
ClassPathScanningCandidateComponentProvider
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
,classpath 下掃描符合條件的 BeanDefinition
構造函數
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
/** 包含過濾器 */
private final List<TypeFilter> includeFilters = new LinkedList<>();
/** 排除過濾器 */
private final List<TypeFilter> excludeFilters = new LinkedList<>();
@Nullable
private Environment environment;
/** {@link Condition} 注解計算器 */
@Nullable
private ConditionEvaluator conditionEvaluator;
/** 資源加載器,默認 PathMatchingResourcePatternResolver */
@Nullable
private ResourcePatternResolver resourcePatternResolver;
/** MetadataReader 工廠 */
@Nullable
private MetadataReaderFactory metadataReaderFactory;
/** 所有 `META-INF/spring.components` 文件的內容 */
@Nullable
private CandidateComponentsIndex componentsIndex;
protected ClassPathScanningCandidateComponentProvider() {
}
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
this(useDefaultFilters, new StandardEnvironment());
}
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(null);
}
@Override
public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
// 獲取所有 `META-INF/spring.components` 文件中的內容
this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
}
}
構造函數在上一小節的 ClassPathBeanDefinitionScanner 的構造函數中都已經講過了
我們來看到 componentsIndex
屬性,調用 CandidateComponentsIndexLoader#loadIndex(@Nullable ClassLoader classLoader)
方法生成的
CandidateComponentsIndexLoader
org.springframework.context.index.CandidateComponentsIndexLoader
,CandidateComponentsIndexLoader 的加載器,代碼如下:
public final class CandidateComponentsIndexLoader {
public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
public static final String IGNORE_INDEX = "spring.index.ignore";
private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX);
private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class);
/** CandidateComponentsIndex 的緩存,與 ClassLoader 對應 */
private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache = new ConcurrentReferenceHashMap<>();
private CandidateComponentsIndexLoader() {
}
@Nullable
public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader();
}
// 獲取所有 `META-INF/spring.components` 文件中的內容
return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex);
}
@Nullable
private static CandidateComponentsIndex doLoadIndex(ClassLoader classLoader) {
// 是否忽略 Index 的提升,通過配置 `spring.index.ignore` 變量,默認為 `false`
if (shouldIgnoreIndex) {
return null;
}
try {
// 獲取所有的 `META-INF/spring.components` 文件
Enumeration<URL> urls = classLoader.getResources(COMPONENTS_RESOURCE_LOCATION);
if (!urls.hasMoreElements()) {
return null;
}
// 加載所有 `META-INF/spring.components` 文件的內容
List<Properties> result = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
result.add(properties);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + result.size() + "] index(es)");
}
// 總共配置多少個 component 組件
int totalCount = result.stream().mapToInt(Properties::size).sum();
// 如果配置了 component 組件,則封裝成 CandidateComponentsIndex 對象並返回
return (totalCount > 0 ? new CandidateComponentsIndex(result) : null);
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load indexes from location [" +
COMPONENTS_RESOURCE_LOCATION + "]", ex);
}
}
}
CandidateComponentsIndexLoader 被 final
修飾,也不允許實例化,提供 loadIndex(@Nullable ClassLoader classLoader)
靜態方法,獲取所有 META-INF/spring.components
文件中的內容,存在文件並包含內容則創建對應的 CandidateComponentsIndex 對象
整過過程不復雜,如下:
- 根據
spring.index.ignore
變量判斷是否需要忽略本次加載過程,默認為false
- 獲取所有的
META-INF/spring.components
文件 - 加載出所有
META-INF/spring.components
文件的內容,並生成多個 key-value - 內容不為空則創建對應的 CandidateComponentsIndex 對象返回
例如 META-INF/spring.components
文件這樣配置:
example.scannable.AutowiredQualifierFooService=example.scannable.FooService
example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component
example.scannable.NamedComponent=org.springframework.stereotype.Component
example.scannable.FooService=example.scannable.FooService
example.scannable.FooServiceImpl=org.springframework.stereotype.Component,example.scannable.FooService
example.scannable.ScopedProxyTestBean=example.scannable.FooService
example.scannable.StubFooDao=org.springframework.stereotype.Component
example.scannable.NamedStubDao=org.springframework.stereotype.Component
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
example.scannable.sub.BarComponent=org.springframework.stereotype.Component
生成的 CandidateComponentsIndex 對象如下所示:

3. findCandidateComponents 方法
findCandidateComponents(String basePackage)
方法,解析出包路徑下所有符合條件的 BeanDefinition,方法如下:
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
/*
* 2. 掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出符合條件的 AnnotatedGenericBeanDefinition 們,並返回
* 說明:
* 針對 `1` 解析過程中去掃描指定路徑下的 .class 文件的性能問題,從 Spring 5.0 開始新增了一個 @Indexed 注解(新特性),
* @Component 注解上面就添加了 @Indexed 注解
*
* 這里不會去掃描指定路徑下的 .class 文件,而是讀取所有 `META-INF/spring.components` 文件中符合條件的類名,
* 直接添加 .class 后綴就是編譯文件,而不要去掃描
*
* 沒在哪看見這樣使用過,可以參考 ClassPathScanningCandidateComponentProviderTest#customAnnotationTypeIncludeFilterWithIndex 測試方法
*/
if (this.componentsIndex != null // `componentsIndex` 不為空,存在 `META-INF/spring.components` 文件並且解析出數據則會創建
&& indexSupportsIncludeFilters()) // `includeFilter` 過濾器的元素(注解或類)必須標注 @Indexed 注解
{
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
/*
* 1. 掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出符合條件的 ScannedGenericBeanDefinition 們,並返回
* 首先需要去掃描指定路徑下所有的 .class 文件,該過程對於性能有不少的損耗
* 然后通過 ASM 根據 .class 文件可以獲取到這個類的所有元信息,也就可以解析出對應的 BeanDefinition 對象
*/
return scanCandidateComponents(basePackage);
}
}
這個方法的實現有兩種方式,都是基於 ASM(Java 字節碼的操作和分析框架)實現的,默認情況下都是第 1
種,分別如下:
-
1
,調用scanCandidateComponents(String basePackage)
方法,默認掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出符合條件的 ScannedGenericBeanDefinition 們,並返回。首先需要去掃描指定路徑下所有的 .class 文件,該過程對於性能有不少的損耗;然后通過 ASM 根據 .class 文件可以獲取到這個類的所有元信息,也就可以解析出對應的 BeanDefinition 對象
-
2
,componentsIndex
不為空,也就是說是通過META-INF/spring.components
文件配置的 Bean,並且定義 Bean 的注解必須標注@Index
注解,則調用addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage)
方法進行解析掃描包路徑,通過 ASM(Java 字節碼的操作和分析框架)解析出符合條件的 AnnotatedGenericBeanDefinition 們,並返回。針對
1
解析過程中去掃描指定路徑下的 .class 文件的性能問題,從 Spring 5.0 開始新增了一個@Index
注解(新特性),@Component 注解上面就添加了 @Index 注解;這里不會去掃描指定路徑下的 .class 文件,而是讀取所有META-INF/spring.components
文件中符合條件的類名,直接添加 .class 后綴就是編譯文件,而不要去掃描,提高性能。
ASM 是一個 Java 字節碼操控框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行為。Java Class 被存儲在嚴格格式定義的 .class 文件里,這些類文件擁有足夠的元數據來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據用戶要求生成新類。
Spring 在很多地方都使用到了 ASM
4. scanCandidateComponents 方法
scanCandidateComponents(String basePackage)
方法,解析出包路徑下所有符合條件的 BeanDefinition,方法如下:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
// <1> 定義 `candidates` 用於保存符合條件的 BeanDefinition
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// <2> 根據包名生成一個掃描的路徑,例如 `classpath*:包路徑/**/*.class`
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// <3> 掃描到包路徑下所有的 .class 文件
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
// <4> 開始對第 `3` 步掃描到的所有 .class 文件(需可讀)進行處理,符合條件的類名會解析出一個 ScannedGenericBeanDefinition
for (Resource resource : resources) {
if (resource.isReadable()) { // 文件資源可讀
try {
// <4.1> 根據這個類名找到 `.class` 文件,通過 ASM(Java 字節碼操作和分析框架)獲取這個類的所有信息
// `metadataReader` 對象中包含 ClassMetadata 類元信息和 AnnotationMetadata 注解元信息
// 也就是說根據 `.class` 文件就獲取到了這個類的元信息,而不是在 JVM 運行時通過 Class 對象進行操作,提高性能
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// <4.2> 根據所有的過濾器判斷這個類是否符合條件(例如必須標注 @Component 注解或其派生注解)
if (isCandidateComponent(metadataReader)) {
// <4.3> 如果符合條件,則創建一個 ScannedGenericBeanDefinition 對象
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
// 來源和源對象都是這個 .class 文件資源
sbd.setResource(resource);
sbd.setSource(resource);
/*
* <4.4> 再次判斷這個類是否符合條件(不是內部類並且是一個具體類)
* 具體類:不是接口也不是抽象類,如果是抽象類則需要帶有 @Lookup 注解
*/
if (isCandidateComponent(sbd)) {
// <4.5> 符合條件,則添加至 `candidates` 集合
candidates.add(sbd);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
// <5> 返回 `candidates` 集合
return candidates;
}
過程如下:
- 定義
candidates
用於保存符合條件的 BeanDefinition - 根據包名生成一個掃描的路徑,例如
classpath*:包路徑/**/*.class
- 掃描到包路徑下所有的 .class 文件
- 開始對第
3
步掃描到的所有 .class 文件(需可讀)進行處理,符合條件的類名會解析出一個 ScannedGenericBeanDefinition- 根據這個類名找到
.class
文件,通過 ASM(Java 字節碼操作和分析框架)獲取這個類的所有信息,生成metadataReader
對象。這個對象其中包含 ClassMetadata 類元信息和 AnnotationMetadata 注解元信息,也就是說根據.class
文件就獲取到了這個類的元信息,而不是在 JVM 運行時通過 Class 對象進行操作,提高性能 - 根據所有的過濾器判斷這個類是否符合條件(例如必須標注 @Component 注解或其派生注解)
- 如果符合條件,則創建一個 ScannedGenericBeanDefinition 對象,來源和源對象都是這個 .class 文件資源
- 再次判斷這個類是否符合條件(不是內部類並且是一個具體類),具體類:不是接口也不是抽象類,如果是抽象類則需要帶有 @Lookup 注解
- 符合條件,則添加至
candidates
集合
- 根據這個類名找到
- 返回
candidates
集合
關於 ASM 的實現本文不進行探討,感興趣的可自行研究
4. addCandidateComponentsFromIndex 方法
addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage)
方法,根據 META-INF/spring.components
文件,獲取帶有 @Indexed 注解的類名,然后解析出符合條件的 BeanDefinition,方法如下:
private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
// <1> 定義 `candidates` 用於保存符合條件的 BeanDefinition
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
Set<String> types = new HashSet<>();
// <2> 根據過濾器從所有 `META-INF/spring.components` 文件中獲取所有符合條件的**類名稱**
for (TypeFilter filter : this.includeFilters) {
// <2.1> 獲取過濾注解(或類)的名稱(例如 `org.springframework.stereotype.Component`)
String stereotype = extractStereotype(filter);
if (stereotype == null) {
throw new IllegalArgumentException("Failed to extract stereotype from " + filter);
}
// <2.2> 獲取注解(或類)對應的條目,並過濾出 `basePackage` 包名下的條目(類的名稱)
types.addAll(index.getCandidateTypes(basePackage, stereotype));
}
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
// <3> 開始對第 `2` 步過濾出來類名進行處理,符合條件的類名會解析出一個 AnnotatedGenericBeanDefinition
for (String type : types) {
// <3.1> 根據這個類名找到 `.class` 文件,通過 ASM(Java 字節碼操作和分析框架)獲取這個類的所有信息
// `metadataReader` 對象中包含 ClassMetadata 類元信息和 AnnotationMetadata 注解元信息
// 也就是說根據 `.class` 文件就獲取到了這個類的元信息,而不是在 JVM 運行時通過 Class 對象進行操作,提高性能
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
// <3.2> 根據所有的過濾器判斷這個類是否符合條件(例如必須標注 @Component 注解或其派生注解)
if (isCandidateComponent(metadataReader)) {
// <3.3> 如果符合條件,則創建一個 AnnotatedGenericBeanDefinition 對象
AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition(
metadataReader.getAnnotationMetadata());
/*
* <3.4> 再次判斷這個類是否符合條件(不是內部類並且是一個具體類)
* 具體類:不是接口也不是抽象類,如果是抽象類則需要帶有 @Lookup 注解
*/
if (isCandidateComponent(sbd)) {
// <3.5> 符合條件,則添加至 `candidates` 集合
candidates.add(sbd);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
// <4> 返回 `candidates` 集合
return candidates;
}
過程如下:
- 定義
candidates
用於保存符合條件的 BeanDefinition - 根據過濾器從所有
META-INF/spring.components
文件中獲取所有符合條件的類名稱- 獲取過濾注解(或類)的名稱(例如
org.springframework.stereotype.Component
) - 獲取注解(或類)對應的條目,並過濾出
basePackage
包名下的條目(類的名稱)
- 獲取過濾注解(或類)的名稱(例如
- 開始對第
2
步過濾出來類名進行處理,符合條件的類名會解析出一個 AnnotatedGenericBeanDefinition- 根據這個類名找到
.class
文件,通過 ASM(Java 字節碼操作和分析框架)獲取這個類的所有信息,生成metadataReader
對象。這個對象其中包含 ClassMetadata 類元信息和 AnnotationMetadata 注解元信息,也就是說根據.class
文件就獲取到了這個類的元信息,而不是在 JVM 運行時通過 Class 對象進行操作,提高性能 - 根據所有的過濾器判斷這個類是否符合條件(例如必須標注 @Component 注解或其派生注解)
- 如果符合條件,則創建一個 AnnotatedGenericBeanDefinition 對象
- 再次判斷這個類是否符合條件(不是內部類並且是一個具體類),具體類:不是接口也不是抽象類,如果是抽象類則需要帶有 @Lookup 注解
- 符合條件,則添加至
candidates
集合
- 根據這個類名找到
- 返回
candidates
集合
該過程不會去掃描到所有的 .class 文件,而是從 META-INF/spring.components
文件中讀取,知道了類名稱也就知道了 .class 文件的路徑,然后可以通過 ASM 進行操作了。Spring 5.0 開始新增的一個 @Indexed 注解(新特性),目的為了提高性能。
總結
本文面向注解(@Component 注解或其派生注解
)定義的 Bean,Spring 是如何將他們解析成 BeanDefinition(Bean 的“前身”)並注冊的,大致過程如下:
- ClassPathBeanDefinitionScanner 會去掃描到包路徑下所有的 .class 文件
- 通過 ASM(Java 字節碼操作和分析框架)獲取 .class 對應類的所有元信息
- 根據元信息判斷是否符合條件(帶有
@Component 注解或其派生注解
),符合條件則根據這個類的元信息生成一個 BeanDefinition 進行注冊
關於上面的第 1
步性能損耗不少,Spring 5.0 開始新增的一個 @Indexed 注解(新特性),@Indexed 派生注解(例如 @Component)或 @Indexed 注解的類可以定義在 META-INF/spring.components
文件中,Spring 會直接從文件中讀取,找到符合條件的類名稱,也就找到了 .class 文件。這樣一來對於上面第 1
步來說在性能上得到了提升,目前還沒見到這種方式,畢竟 還要再文件中定義類名,感覺太復雜了,啟動過程慢就慢點😄
到這里,對於通過 面向資源(XML、Properties)、面向注解 兩種定義 Bean 的方式,Spring 將定義的信息轉換成 BeanDefinition(Bean 的“前身”)的過程差不多都分析了。我們接下來研究一下 Bean 的生命周期,BeanDefinition 是如何變成 Bean 的。
本文你是否還有疑惑,@Bean 注解定義的 Bean 怎么沒有解析成 BeanDefinition 呢?別急,在后續的文章會進行分析