【乾貨點】 此處是 【好好面試】 系列文的第13篇文章。 如今網上大把說IoC的文章,但是手把手調試源碼的卻很少,也不多會描述IoC容器的實現步驟,如今就讓稀飯下雪手把手調試源碼看看IoC是怎麼個東西,順便記住幾個關鍵知識點,之後面試無憂;不懂如何看源碼的兄弟們要跟上啦,能夠仔細看看個人調試騷法。程序員
依稀記得,在不少年前,我在簡歷技能欄目上寫着熟悉spring幾個字,後來面試官瞄了一眼,熟悉spring?不錯,說說看IoC容器的實現。面試
我當時就懵逼了,說實話,我一直都知道IoC是什麼,原理是什麼,我還能夠用類比的方式和你說這個鬼東西,但是你讓我說說IoC容器的實現,我還真TM一時回答不上來,由於我沒有看過源碼。而這也是我寫這篇文章的初衷,爲了可以說清楚這個過程,我將整個SpringBoot的啓動過程和IoC容器的實現過程都仔仔細細看了一遍。spring
仔細瀏覽了源碼後,得出了一個總結,同時也是**【面試熱點】** ,IoC容器的實現能夠分爲如下幾步:bash
使用過SpringBoot的都知道,關於組件的掃描是從主類所在的包開始掃描的,經過閱讀源碼咱們能夠看到,在prepareContext()方法中,SpringBoot會先將主類,也就是使用了註解@SpringBootApplication的類,解析成BeanDefinition,以後在invokeBeanFactoryPostProcessors()方法中解析主類的BeanDefinition從而獲取basePackage的路徑,而這個路徑也就是咱們所說的定位了,固然了,此處不討論使用了註解@Import的狀況。app
在取得了Resource的定位後,天然而然下一步確定是將各類BeanDefinition的載入到內存中了,這裏看源碼其實就調試了好久,可是其實流程極其簡單。所謂的載入內存中,用腳指頭想一想都知道其實就是經過Resource的定位,拼接了一下路徑變成:classpath*:org/springframework/boot/demo/**/*.class這樣的形式,以後再使用了一個比較屌的類PathMatchingResourcePatternResolver,將該路徑下的全部.class的文件加載進來,以後再遍歷判斷是否有@Component註解,不過不要覺得能夠直接經過@Component註解來反調源碼,年輕的我也是這麼作的,不過發現其實不是喲,想知道就繼續看。若是有註解的話,就是該步驟要裝載的BeanDefinition了。ide
所謂的註冊,聽起來很高大上,將類註冊入IoC容器內,可是其實很簡單,其實就是將其put到一個CurrentHashMap中罷了,是的,IoC容器其實就是使用這個Map來存放這些BeanDefinition數據的。函數
【舒適提示】:若是不知道BeanDefinition是什麼的同窗就該面壁了喲,說明Spring瞭解確實淺,同時也沒有看我以前的文章,能夠看看這篇Spring之Bean的動態註冊工具
其實看到這裏也差很少能夠了,畢竟大體描述下過程的話確實就是上面三個了,有興趣看源碼解析的能夠繼續。後面我會將調試的流程跟着源碼描述出來,最好是跟着代碼調試,否則會暈圈的。post
依舊按照以前套路,直接先定位到資源定位的地方,再經過調試頁面,看看程序作了什麼,咱們能夠直接跳到 ComponentScanAnnotationParser類中的parse函數,首先先解釋一波什麼是ComponentScanAnnotationParser ,所謂的ComponentScanAnnotationParser其實只是Spring的一個內部工具,它會基於某個類上的 @ComponentScan 註解屬性分析指定包(package)以獲取其中的BeanDefiniiton。學習
接下來咱們看看其中的parse函數,跟着調試的同窗能夠直接給這個函數的最後一段代碼加個、斷點,啓動項目,就能夠看到項目的運行流程了,以下所示
// 經過調試能夠看到componentScan攜帶者basePackages這個數據,而declaringClass是
// "com.nuofankj.demo.DemoApplication",也就是包路徑+主類
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 【重點】真正存放資源位置的地方
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 【重點】該次運行,因爲沒有是默認啓動,所以最終basePackages存放的是declaringClass的包路徑,這點能夠直接看
// ClassUtils.getPackageName(declaringClass)ll
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 【重點】StringUtils.toStringArray(basePackages) 斷點能夠看到打印出來的是主類的包名【com.nuofankj.demo】
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
複製代碼
接下來讓咱們看看 scanner.doScan 中作了什麼
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 【重點】從指定的包中掃描須要裝載的Bean
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 【重點】將該 Bean 註冊進 IoC容器(beanDefinitionMap)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
複製代碼
上面中有標註了兩個比較重要的方法 Set candidates = findCandidateComponents(basePackage); 做用是從basePackage中掃描類並解析成BeanDefinition,鄧拿到全部符合條件的類後在 **registerBeanDefinition(definitionHolder, this.registry);**中將該類註冊進IoC容器。也就是說在這個方法中完成了IoC容器初始化過程的第二三步,BeanDefinition的載入,和BeanDefinition的註冊。
到這裏,資源的定位到這裏就結束了。
咱們接着上面所說的函數scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 【重要】拼接掃描路徑,最終生成的是 classpath*:com/nuofankj/demo/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 從上面拼接出來的packageSearchPath 路徑中掃描全部的類
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 【重要】這裏判斷該類是否是 @Component 註解標註的類
if (isCandidateComponent(metadataReader)) {
// 將該類封裝成ScannedGenericBeanDefinition,這是BeanDefinition接口的實現類
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
複製代碼
看源碼和註釋咱們能夠看到,packageSearchPath 是經過拼接出來的,而會在getResources(packageSearchPath)方法中掃描到了該路徑下的全部的類,以後遍歷這些Resources,判斷是不是@Component 註解標註的類,而且不是須要排除掉的類。以後便將掃描到的類,解析成ScannedGenericBeanDefinition,該類是BeanDefinition接口的實現類。 到這裏 IoC容器的BeanDefinition載入就結束了。
載入結束了,接下來讓咱們看看如何進行BeanDefinition的註冊,從新回到 registerBeanDefinition(definitionHolder, this.registry) ,根據調試能夠看到,最終會進入如下函數
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
// 【重點】
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
複製代碼
咱們先看看什麼是BeanDefinitionRegistry,該類的做用主要是向註冊表中註冊 BeanDefinition 實例,完成 註冊的過程。繼續看下重點函數 registerBeanDefinition
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 【重點】能夠看到這裏,採用ConcurrentHashMap存放bean,看到這裏即可以知道了,其實IoC容器的最底層就是一個ConcurrentHashMap,只是它被放到了某個對象中,經過看源碼能夠知道這個對象是DefaultListableBeanFactory
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 【重點】若是該類不容許 Overriding 直接拋出異常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isWarnEnabled()) {
logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 【重點】註冊進beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
// 【重點】若是仍處於啓動註冊階段,註冊進beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
複製代碼
經過一步步調試,咱們能夠看到最終走到了DefaultListableBeanFactory,首先介紹下什麼是DefaultListableBeanFactory,該類能夠說就是IoC容器本器了,調試源碼看到最後,其實IoC容器就是一個ConcurrentHashMap。 那麼DefaultListableBeanFactory是何時構建的呢?咱們能夠看到
經過obtainFreshBeanFactory構建的,內部函數調試後看了下沒什麼,不影響總體流程,就不深刻講解了,有興趣能夠本身跟着調試看看。文章總結的我都調到前面了,因此總結就不說了,說說看最近更文速度變慢的緣由,主要仍是狀態很差,忙是一直都忙的,畢竟身處遊戲行業,天天9點或者10點上班,23點左右才下班,不過狀態很差的緣由主要仍是情感問題,因此無意學習致使無意寫文章。其次是發現最近容易脖子酸,職業病呀,關注個人應該大部分是程序員,若是有好的建議,能夠提下哈,我說的是職業病預防,固然了,若是你要給我介紹女友我也是不介意的 ○( ^皿^)っHiahiahia…