手把手帶你閱讀源碼,看看IoC容器的實現

【乾貨點】 此處是 【好好面試】 系列文的第13篇文章。 如今網上大把說IoC的文章,但是手把手調試源碼的卻很少,也不多會描述IoC容器的實現步驟,如今就讓稀飯下雪手把手調試源碼看看IoC是怎麼個東西,順便記住幾個關鍵知識點,之後面試無憂;不懂如何看源碼的兄弟們要跟上啦,能夠仔細看看個人調試騷法程序員

必讀大前提

依稀記得,在不少年前,我在簡歷技能欄目上寫着熟悉spring幾個字,後來面試官瞄了一眼,熟悉spring?不錯,說說看IoC容器的實現。面試

我當時就懵逼了,說實話,我一直都知道IoC是什麼,原理是什麼,我還能夠用類比的方式和你說這個鬼東西,但是你讓我說說IoC容器的實現,我還真TM一時回答不上來,由於我沒有看過源碼。而這也是我寫這篇文章的初衷,爲了可以說清楚這個過程,我將整個SpringBoot的啓動過程和IoC容器的實現過程都仔仔細細看了一遍。spring

看源碼後的總結

仔細瀏覽了源碼後,得出了一個總結,同時也是**【面試熱點】** ,IoC容器的實現能夠分爲如下幾步:bash

  • 一、Resource的定位

使用過SpringBoot的都知道,關於組件的掃描是從主類所在的包開始掃描的,經過閱讀源碼咱們能夠看到,在prepareContext()方法中,SpringBoot會先將主類,也就是使用了註解@SpringBootApplication的類,解析成BeanDefinition,以後在invokeBeanFactoryPostProcessors()方法中解析主類的BeanDefinition從而獲取basePackage的路徑,而這個路徑也就是咱們所說的定位了,固然了,此處不討論使用了註解@Import的狀況。app

  • 二、BeanDefinition的載入

在取得了Resource的定位後,天然而然下一步確定是將各類BeanDefinition的載入到內存中了,這裏看源碼其實就調試了好久,可是其實流程極其簡單。所謂的載入內存中,用腳指頭想一想都知道其實就是經過Resource的定位,拼接了一下路徑變成:classpath*:org/springframework/boot/demo/**/*.class這樣的形式,以後再使用了一個比較屌的類PathMatchingResourcePatternResolver,將該路徑下的全部.class的文件加載進來,以後再遍歷判斷是否有@Component註解,不過不要覺得能夠直接經過@Component註解來反調源碼,年輕的我也是這麼作的,不過發現其實不是喲,想知道就繼續看。若是有註解的話,就是該步驟要裝載的BeanDefinition了。ide

  • 三、註冊BeanDefinition

所謂的註冊,聽起來很高大上,將類註冊入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的註冊。

到這裏,資源的定位到這裏就結束了。

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的註冊

載入結束了,接下來讓咱們看看如何進行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…

logo
相關文章
相關標籤/搜索