該系列文章是本人在學習 Spring 的過程當中總結下來的,裏面涉及到相關源碼,可能對讀者不太友好,請結合個人源碼註釋 Spring 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.1.14.RELEASEjava
開始閱讀這一系列文章以前,建議先查看《深刻了解 Spring IoC(面試題)》這一篇文章git
該系列其餘文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》github
前面的幾篇文章對 Spring 解析 XML 文件生成 BeanDefinition 並註冊的過程進行了較爲詳細的分析,這種定義 Bean 的方式是面向資源(XML)的方式。面向註解定義 Bean 的方式 Spring 的處理過程又是如何進行的?本文將會分析 Spring 是如何將 @Component 註解或其派生註解
標註的 Class 類解析成 BeanDefinition(Bean 的「前身」)並註冊。面試
在上一篇 《解析自定義標籤(XML 文件)》文章中提到了處理 <context:component-scan />
標籤的過程當中,底層藉助於 ClassPathBeanDefinitionScanner 掃描器,去掃描指定路徑下符合條件的 BeanDefinition 們,這個類就是處理 @Component 註解定義 Bean 的底層實現。關於 @ComponentScan 註解的原理也是基於這個掃描器來實現的,咱們先來看看這個掃描器的處理過程。spring
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
,繼承 ClassPathScanningCandidateComponentProvider,classpath 下 BeanDefinition 的掃描器,支持設置過濾器緩存
默認有三個過濾器: @Component
註解的過濾器,Java EE 6 的 javax.annotation.ManagedBean
註解過濾器,JSR-330 的 javax.inject.Named
註解過濾器,這裏咱們重點關注第一個過濾器app
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()
方法,註冊幾個默認的過濾器,方法以下:ide
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
文件中的內容,後續進行分析
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 數量
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; }
過程以下:
beanDefinitions
,用於保存本次掃描成功註冊的 BeanDefinition 們findCandidateComponents(String basePackage)
方法2
步解析出來的 BeanDefinition 依次處理,並註冊
beanName
beanName
是否已存在,已存在可是不兼容則會拋出異常beanName
TARGET_CLASS
,則再建立一個 BeanDefinition 代理對象(從新設置了相關屬性),原始 BeanDefinition 已註冊beanDefinitions
集合beanDefinitions
(已註冊的 BeanDefinition 集合)第 2
步是這個掃描過程的核心步驟,在父類 ClassPathScanningCandidateComponentProvider 中進行分析,接下來的處理過程不復雜,獲取相關屬性進行配置
第 7
步建立代理對象,和 AOP 相關,感興趣的可自行查看
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)
方法生成的
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例如 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 對象以下所示:
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
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
用於保存符合條件的 BeanDefinitionclasspath*:包路徑/**/*.class
3
步掃描到的全部 .class 文件(需可讀)進行處理,符合條件的類名會解析出一個 ScannedGenericBeanDefinition
.class
文件,經過 ASM(Java 字節碼操做和分析框架)獲取這個類的全部信息,生成 metadataReader
對象。這個對象其中包含 ClassMetadata 類元信息和 AnnotationMetadata 註解元信息,也就是說根據 .class
文件就獲取到了這個類的元信息,而不是在 JVM 運行時經過 Class 對象進行操做,提升性能candidates
集合candidates
集合關於 ASM 的實現本文不進行探討,感興趣的可自行研究
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
用於保存符合條件的 BeanDefinitionMETA-INF/spring.components
文件中獲取全部符合條件的類名稱
org.springframework.stereotype.Component
)basePackage
包名下的條目(類的名稱)2
步過濾出來類名進行處理,符合條件的類名會解析出一個 AnnotatedGenericBeanDefinition
.class
文件,經過 ASM(Java 字節碼操做和分析框架)獲取這個類的全部信息,生成 metadataReader
對象。這個對象其中包含 ClassMetadata 類元信息和 AnnotationMetadata 註解元信息,也就是說根據 .class
文件就獲取到了這個類的元信息,而不是在 JVM 運行時經過 Class 對象進行操做,提升性能candidates
集合candidates
集合該過程不會去掃描到全部的 .class 文件,而是從 META-INF/spring.components
文件中讀取,知道了類名稱也就知道了 .class 文件的路徑,而後能夠經過 ASM 進行操做了。Spring 5.0 開始新增的一個 @Indexed 註解(新特性),目的爲了提升性能。
本文面向註解(@Component 註解或其派生註解
)定義的 Bean,Spring 是如何將他們解析成 BeanDefinition(Bean 的「前身」)並註冊的,大體過程以下:
@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 呢?別急,在後續的文章會進行分析