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是系统启动类的全限定名。
    -w787

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存入缓存中。

原文地址:https://www.cnblogs.com/-1007813544/p/spring-componentscans-zhu-jie-de-jie-xi.html