Spring5 源碼分析-容器刷新-@ComponentScan

上一篇:Spring5 源碼分析-容器刷新-@PropertySourcejava

功能說明

@ComponentScan能夠完成對指定路徑下的類文件掃描,將符合Spring規則(@Component @Service @Controller @Repository @Named(JSR-250))或者自定義規則(自定義的FilterType)的類定義註冊到容器中(即將各個類的BeanDefinition添加到DefaultListableBeanFactory.beanDefinitionMaps當中)。能夠用@ComponentScan的屬性指定多個代理模式、是否懶加載、ResourcePattern、includeFilters 、 excludeFitlersapp

@ComponentScan屬性ide

    value和basePackages效果同樣,都是用來指定掃描的包路徑源碼分析

    basePackageClasses:添加指定class所在包路徑做爲掃描包路徑post

    nameGenerator:指定beanName名稱生成器,通常不會去修改它測試

    scopeResolver:指定Scope元數據提取器,通常不會去修改它this

    scopedProxy:指定Bean的代理模式,默認爲DEFAULT,其效果和NO同樣,還有TARGET_CLASS(CGLIB)和INTERFACES(JDK動態代理)lua

    resoucePattern:過濾包路徑,好比包掃描路徑是com.jv.scan,你能夠設置一個pattern,將com.jv.scan.inner目錄過濾掉,結合includeFilters和excludeFilters能夠實現更靈活的包掃描spa

    useDefaultFilters:默認爲true,誰也不會去改這個值,由於它是Spring支持@Component註解的關鍵點.net

    lazyInit:默認爲false,若是你想全局設置爲懶加載,則能夠設置爲true

    includeFilters:符合Filter指定規則的類會被掃描

    excludeFilters:符合Filter指定規則的類不會被掃描

注:對於能夠在Bean上面單獨設置的屬性,若是針對某一個類單獨設置了,在@ComponentScan上設置的會被覆蓋掉。好比lazyInit和scopeProxyMode

舉例Demo

    這個簡單,任何一個Spring項目都會在配置類上添加@ComponentScan註解,至於上面的各個屬性也很方便測試,不復雜。因爲時間關係,沒有將測試的代碼一個一個整理並貼出來。裏面可能會比較經常使用的就是使用value,basePackages,basePackageClasses去指定掃描包路徑,還有設置includeFilters和excludeFilters用以控制自定義的掃描規則

    注:若是沒有指定任何的包掃描路徑,則使用的是配置類所在包的路徑

源碼分析

方法按照以下順序refresh()->invokeBeanFactoryPostProcessors()->ConfigurationClassPostProcessor->postProcessBeanDefinitionRegistry()-ConfigurationClassParse.doProcessConfigurationClass()...ComponentScanAnnotationParse.parse()...ClassPathBeanDefinitionScanner.doScan

在ConfigurationClassParse.doProcessConfigurationClass方法中的代碼段

// Process any @ComponentScan annotations  處理@ComponentScan註解,完成符合Spring規則類定義註冊
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately  當即執行掃描
				//不會掃描到本身(@Configuration修飾了的類)
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				// 檢查掃描到全部類中是否還有被@Configuration修飾的類,若是還有,則須要再按照配置類的流程處理一遍
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						//若是掃描的包路徑下還有被@Configuration修飾的類,則遞歸進行處理
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

ComponentScanAnnotationParse.parse方法

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		/**
		 * 這個初始化包含了兩個很是重要的功能
		 * 1.添加默認的@Component @Named AnnotationFilterType,是Spring能夠掃描@Component @Service @Controller @Configuration的關鍵
		 * 2.設置參數對象environment
		 */

		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));

		//默認不使用代理,能夠設置爲JDK的動態代理或者基於CGLIB的動態代理
		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			//ScopeMetadataResolver 從BeanDefinition中將類的Scope元數據提取出來並返回
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}
		//獲取類文件過濾模式,默認是處理全部類路徑下的全部class文件,能夠自定義pattern只讓某一個目錄下的class文件有效
		scanner.setResourcePattern(componentScan.getString("resourcePattern"));
		//添加includeFilter過濾器,Spring會處理符合過濾器規則的class
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
		//添加excludeFilter過濾器,Spring會排出符合過濾器規則的class
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}
		//全部的class的BeanDefinition的lazyInit都會被設置爲true,意思就是全部的都會在調用getBean的時候纔會被實例化
		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}

		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		//添加掃描包路徑-由basePackages指定的相對路徑
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		//添加掃描包路徑-由basePackageClasses類所在的包路徑
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		//若是前面二者都沒有指定,則使用當前處理類的所在包路徑。。。估計這也是爲何SpringBoot默認是掃描的啓動類所在目錄的緣由
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}
		//添加一個默認ExcludeFilter,用於再也不二次處理配置類
		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		//掃描
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

ClassPathBeanDefinitionScanner.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) {
			//爲被掃描到的全部類生成BeanDefinition
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			//遍歷全部的候選者並進行處理最終註冊到容器中
			for (BeanDefinition candidate : candidates) {
				//提取Scope元數據
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());

				//生成Spring規則的beanName
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					//設置全局默認值,好比lazyInit=false(默認爲false,能夠在@ComponentScan中設置爲true),autowireMode,InitMethod等
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					/**
					 * 這裏很重要,建立BeanDefinition的ProxyDefinition
					 * 1.若是ScopedProxyMode是DEFAULT或者NO, 則不會建立ProxyDefinition
					 * 2.若是ScopedProxyMode不是TARGET_CLASS,則建立的ProxyDefinition會用於JDK動態代理
					 * 3.若是ScopedProxyMode是TARGET_CLASS,則建立的ProxyDefinition會用於CGLIB動態代理
					 */

					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//註冊BeanDefinition
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

下一篇:Spring5 源碼分析-容器刷新-@Import(普通類)

相關文章
相關標籤/搜索