1. 程式人生 > 實用技巧 >Spring @ComponentScans註解的解析

Spring @ComponentScans註解的解析

@ComponentScans註解是組成@SpringBootApplication註解的之一。主要用於掃描專案中的元件,比如@Compoent``@Service等等,預設掃描路徑是應用程式啟動類的同級路徑,可以新增應用的自定義路徑。

@ComponentScans註解是在 @Configuration前被解析。

@ComponentScans註解是被ComponentScanAnnotationParser#parse()方法解析的。

ComponentScanAnnotationParser

先簡單瞭解ComponentScanAnnotationParser這個類。
成員變數:

  1. Environment environment 系統環境
  2. ResourceLoader resourceLoader 資源載入器
  3. BeanNameGenerator beanNameGenerator beanName生成策略
  4. BeanDefinitionRegistry registry bean工廠(DefaultListableBeanFactory)

核心方法

  1. parse(AnnotationAttributes componentScan, final String declaringClass)
    componentScan@ComponentScan註解的11個屬性。
    declaringClass是系統啟動類的全限定名。

parse()

方法主要是在構建一個ClassPathBeanDefinitionScanner類,在方法的最後會呼叫ClassPathBeanDefinitionScanner#doScan()方法掃描專案。

其中doScan()方法的傳參是掃描路徑,預設是啟動類的路徑,也可以自定義,支援多個路徑

    //basePackages掃描路徑,如:com.kafkaproducer
	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) {
		      //1.這裡會掃描路徑下的所有類。如:/Users/XX/kafka-test/kafkaproducer/target/classes/com/kafkaproducer/KafkaProducerTest.class
		      //2.把資源封裝成一個SimpleMetadataReader
		      //3.根據@ComponentScan的filter判斷資源是否滿足條件
		      //4. 滿足步驟3的資源,再進行一次篩選。最終篩選出來的類的註解都包含了@Component
			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);
				//對掃描出來的candidate的BeanDefinition,設定一些引數。
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				
				//檢查beanName在登錄檔中是否存在(可能會有衝突)
				//true:可以註冊;false:bean已存在,(beanName相同,beanDefinition也相同)
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					   //是否通過代理建立
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					
					//註冊beanDefinition(登錄檔快取)
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

總結

掃描註解@ComponentScans預設會根據應用程式啟動類所在的根路徑為預設掃描路徑(參考:classpath:com/kafkaproducer/**/.class),也可以通過指定其他路徑(比如專案中的DAO層是通過jar包的形式引入進來的,可以指定路徑掃描DAO的元件)。負責掃描的類--ClassPathBeanDefinitionScanner會掃描專案的所有class檔案,再經過Filter過濾,得到所有被@Component註解修飾的類。最後把相關類的beanDefiniton存入快取中。