Spring源碼情操陶冶-ComponentScanBeanDefinitionParser文件掃描解析器


承接前文Spring源碼情操陶冶-自定義節點的解析,本文講述spring通過context:component-scan節點干了什么事

ComponentScanBeanDefinitionParser#私有屬性

羅列下context:component-scan可填的基礎屬性

	private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";

	private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";

	private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";

	private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";

	private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";

	private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";

	private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";

	private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";

	private static final String INCLUDE_FILTER_ELEMENT = "include-filter";

	private static final String FILTER_TYPE_ATTRIBUTE = "type";

	private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";

ComponentScanBeanDefinitionParser#parse()-主方法

統一接口parse()方法,看下總體邏輯,代碼如下

@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//解析base-package屬性值,掃描的包可以,;分隔
		String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		//通過ClassPathBeanDefinitionScanner掃描類來獲取包名下的所有class並將他們注冊到spring的bean工廠中
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		//注冊其他注解組件
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}

我們關注下ComponentScanBeanDefinitionParser#configureScanner()創建掃描器操作和ClassPathBeanDefinitionScanner#doScan()掃描包方法

ComponentScanBeanDefinitionParser#configureScanner()-創建掃描器

觀察下如何創建掃描器,以及相關的初始操作,代碼奉上

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
		XmlReaderContext readerContext = parserContext.getReaderContext();
		//默認使用spring自帶的注解過濾
		boolean useDefaultFilters = true;
		//解析`use-default-filters`,類型為boolean
		if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
			useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
		}

		// Delegate bean definition registration to scanner class.
		//此處如果`use-default-filters`為true,則添加`@Component`、`@Service`、`@Controller`、`@Repository`、`@ManagedBean`、`@Named`添加到includeFilters的集合過濾
		ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
		scanner.setResourceLoader(readerContext.getResourceLoader());
		scanner.setEnvironment(parserContext.getDelegate().getEnvironment());
		scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
		scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
		//設置`resource-pattern`屬性,掃描資源的模式匹配,支持正則表達式
		if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {			
                    scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
		}

		try {
			//解析name-generator屬性 beanName生成器
			parseBeanNameGenerator(element, scanner);
		}
		catch (Exception ex) {
			readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
		}

		try {
			//解析scope-resolver屬性和scoped-proxy屬性,但兩者只可存在其一
			//后者值為targetClass:cglib代理、interfaces:JDK代理、no:不使用代理
			parseScope(element, scanner);
		}
		catch (Exception ex) {
			readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
		}
		//解析子節點`context:include-filter`、`context:exclude-filter`主要用於對掃描class類的過濾
               //例如<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller.RestController" />
		parseTypeFilters(element, scanner, readerContext, parserContext);

		return scanner;
	}

此處只簡單的羅列了如何創建一個文件掃描器以及相關的初始操作,具體的讀者可自行去閱讀分析

ClassPathBeanDefinitionScanner#doScan()-掃描操作

真實掃描base-package指定的目錄並返回注冊的所有beanDefinition,具體的掃描簡析如下

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		//表明base-package屬性是需要被指定的
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			//對每個基礎包都進行掃描尋找並且對基礎包下的所有class都注冊為BeanDefinition
                        /**
                        **
                        **並對得到的candidates集合進行過濾,此處便用到include-filters和exclude-filters
                        */
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				//解析一個bean的scope屬性,代表作用范圍
				//prototype->每次請求都創建新的對象 singleton->單例模式,處理多請求
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				//使用beanName生成器生成
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				/**
				**對注冊的bean進行另外的賦值處理,比如默認屬性的配置
				*返回的candidate類型為ScannedGenericBeanDefinition,下面兩者
				*條件滿足
				*/
				if (candidate instanceof AbstractBeanDefinition) {
					//設置lazy-init/autowire-code默認屬性,從spring配置的<beans>節點屬性讀取
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					//讀取bean上的注解,比如`@Lazy`、`@Dependson`的值設置相應的屬性
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//查看是否已注冊
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					//默認采取cglib來做代理
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//注冊bean信息到工廠中
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

在這里我們只簡單的看下其父類ClassPathScanningCandidateComponentProvider#findCandidateComponents獲取包下的所有class資源文件並實例化為BeanDefinition對象

ClassPathScanningCandidateComponentProvider#findCandidateComponents()-找尋符合條件的資源文件

掃描包下的所有class文件並對其進行過濾,過濾的條件為includeFiltersexcludeFilters集合。代碼簡析如下

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
		try {
			//值類似為classpath*:com/question/sky/**/*.class
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + "/" + this.resourcePattern;
			//通過PathMatchingResourcePatternResolver來找尋資源
			//常用的Resource為FileSystemResource
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {

				if (resource.isReadable()) {
					try {
						//生成MetadataReader對象->SimpleMetadataReader,內部包含AnnotationMetadataReadingVisitor注解訪問處理類
						MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
						//判斷class是否不屬於excludeFilters集合內但至少符合一個includeFilters集合
						if (isCandidateComponent(metadataReader)) {
							//包裝為ScannedGenericBeanDefinition對象
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							//保存文件資源
							sbd.setResource(resource);
							sbd.setSource(resource);
							//判斷class文件是否不為接口或者抽象類並且是獨立的
							if (isCandidateComponent(sbd)) {
								//完成驗證加入集合中
								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);
		}
		return candidates;
	}

對上面的代碼解釋作下補充,主要是驗證beanDefinition的兩個方法

  • ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader metadataReader)
    對class類進行filter集合過濾
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			//滿足excludeFilter集合中的一個便返回false,表示不對對應的beanDefinition注冊
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			//首先滿足其中includeFilter集合中的一個
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				//判斷對應的beanDifinition不存在@Conditional注解或者滿足@Conditional中指定的條件,則返回true
				//@Conditional注解的使用可自行查看相關資料
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}
  • ClassPathScanningCandidateComponentProvider#isCandidateComponent(AnnotatedBeanDefinition beanDefinition)
    驗證beanDefinition class類是否為具體類
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		//非抽象類、接口類並且有獨立特性[它是一個頂級類還是一個嵌套類(靜態內部類),可以獨立於封閉類構造。]
		return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
	}

ComponentScanBeanDefinitionParser#registerComponents-注冊其他組件

在掃描包內的class文件注冊為beanDefinition之后,ComponentScanBeanDefinitionParser還需要注冊其他的組件,具體是什么可簡單看下相關的源碼

protected void registerComponents(
			XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

		Object source = readerContext.extractSource(element);
		//包裝為CompositeComponentDefinition對象,內置多ComponentDefinition對象
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
		//將已注冊的所有beanDefinitionHolder對象放到上述對象中
		for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
			compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
		}

		// Register annotation config processors, if necessary.
		boolean annotationConfig = true;
		//獲取annotation-config的屬性值,默認為true
		if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
			annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
		}
		if (annotationConfig) {
			//注冊多個BeanPostProcessor接口,具體什么可自行查看,返回的是包含BeanPostProcessor接口的beanDefinitionHolder對象集合
			Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
			//繼續裝入CompositeComponentDefinition對象
			for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
				compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
			}
		}
		//此處為空
		readerContext.fireComponentRegistered(compositeDef);
	}

此處的目的主要是注冊多個BeanPostProcessor接口實現類【供后續spring調用統一接口進行解析,比如>>>Spring源碼情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors可執行下述的@Configuration解析】具體的有

  • ConfigurationClassPostProcessor解析@Configuration注解類
  • AutowiredAnnotationBeanPostProcessor解析@Autowired/@Value注解
  • RequiredAnnotationBeanPostProcessor解析@Required注解
  • CommonAnnotationBeanPostProcessor解析@Resource注解
  • PersistenceAnnotationBeanPostProcessor解析JPA注解,持久層

小結

  1. context:component-scan節點的屬性及其含義
  • base-package 掃描的基礎包名,必填項,也可指定多個包名,以,;分隔
  • use-default-filters 默認為true,如果設置為false,則不啟用@Component及其相關注解
  • resource-pattern 自定義掃描的文件名,支持正則匹配,默認為**/*.class
  • name-generator beanName生成器
  • scope-resolver 指定bean的作用范圍溶解器 與scope-proxy分開使用
  • scope-proxy 與scope-resolver分開使用,targetClass:cglib代理、interfaces:JDK代理、no:不使用代理 ,默認使用cglib代理
  • context:include-filter/context:exclude-filter 子節點,可有多個,表示可對beanDefinition上的注解過濾
  1. 具體的通過掃描base-package指定的包名來得到所有的class文件,請看>>>Spring源碼情操陶冶-PathMatchingResourcePatternResolver路徑資源匹配溶解器注意:掃描一個類的時候,如果其內部有靜態內部類也是會被掃描注冊的

  2. context:component-scan的指定表明默認可將base-package指定的包下的所有注解class比如@Service等注冊為bean到bean工廠,方便后續的業務調用

  3. 注冊BeanFactoryPostProcessor/BeanPostProcessor接口實現類用於后續的bean實例化,比如ConfigurationClassPostProcessor解析@Configuration注解類、AutowiredAnnotationBeanPostProcessor解析@Autowired/@Value注解、RequiredAnnotationBeanPostProcessor解析@Required注解、CommonAnnotationBeanPostProcessor解析@Resource注解、PersistenceAnnotationBeanPostProcessor解析JPA注解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM