@ComponentScan注解原理

3,370

ComponentScan源码解析

博客索引

下文源码分析版本Spring 5.2.5 release

理解@Component

@Component作为一种由Spring容器托管的通用组件,任何被@Component标注的组件均为组件扫描的对象。 类似的组件比如@Repository@Service或者使用@Component注解作为自定义注释。

//@see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}

1. 利用@Repository自定义一个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//@Component
@Repository
public @interface StringRepository {

    String value() default "";
}

2. 写一个测试类NameRepository,加上该注解

@StringRepository("chineseNameRepository")
public class NameRepository {

    public List<String> findAll() {
        return Arrays.asList("张三", "李四", "王二麻子");
    }
}

3. 测试NameRepository类是否能被Spring容器加载

创建一个测试类ComponentBootStrap,代码如下

@Configuration
@ComponentScan
public class ComponentBootStrap {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentBootStrap.class);
//        context.register(ComponentBootStrap.class);
//        context.refresh();
        NameRepository nameRepository = context.getBean(NameRepository.class);
        System.out.println("nameRepository.findAll() = " + nameRepository.findAll());
    }
}

小提示:类NameRepository与引导类ComponentBootStrap需放到一个包下,才能被@ComponentScan扫描加载。 输出结果为:nameRepository.findAll() = [张三, 李四, 王二麻子],这说明了自定义注解类@StringRepository也有类似于@Component的功能。 结论:只要你注解中包含了@Component,就能被Spring容器托管。因为@Component是元注解,也可以联合元注解去创造组合注解,比如@RestController就是由@Controller@ResponseBody组成的。 官网beans-meta-annotations

上面例子来源于小马哥的《Spring Boot编程思想》,NameRepository注解不是继承@Component,但是效果却是继承,借用小马哥对这种模式注解的定义为@Component的派生性。那下文就探索一下@Component派生性的原理。

探索源码

只要注解里面含有@Component,就会被Spring扫描,并注册。为什么会这么呢?是不是跟扫描方式有关系呢? Spring扫描bean的方式有两种,一种是自定义标签<context:component-scan base-package="com.xxx"/>这里base-package值填写NameRepository的包名,一种是注解@ComponentScan,如果不填写value值,就默认扫描注解类的包下所有类。

第一种自定义标签的方式这里暂时不讨论,以后会专门写一篇来解析。 我们这里重点讲解注解@ComponentScan方式。

1. 先看@ComponentScan源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

    // 这里默认为true,说明使用默认的过滤器,下文会详细说这里
	boolean useDefaultFilters() default true;

	Filter[] includeFilters() default {};

	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;

	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		FilterType type() default FilterType.ANNOTATION;

		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};

		String[] pattern() default {};

	}
}

该注解里面有很多属性,我这里就不介绍,有兴趣的自行查阅资料。里面有个注解@AliasFor("value")经常出现,要是有读者有兴趣,我下次就单独写篇文章来介绍这个。说偏题了,来继续探索,Spring是在哪里进行扫描的呢?看到这里很多人都会比较困惑,我的方法一般是先利用idea的find usages,快捷键也就是CTRL+G,来搜索那个地方调用了该注解,如下

然后在usage in .class里面去寻找有关系的,这里,我们在org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass该方法里面找到了解析@ComponentScan的地方。但是猛的一看这个类,好家伙,里面不仅仅只解析我们谈论的@ComponentScan,还解析@Import,@Bean,@PropertySources,@ComponentScans,@ImportResource等,下次将一次性来讲解这个类。

来就重点来看核心代码ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
			···
		// Process any @ComponentScan annotations
		// 获取ComponentScans,ComponentScan注解里面所有的属性
		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
				// 核心代码parse:下面重点解析
				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
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		···

ComponentScanAnnotationParser#parse(AnnotationAttributes,String)

	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
	    // 这里new一个scanner对象,因为@ComponentScan注解里面的useDefaultFilters默认为true,所以这里传入的值为true
	    // 也就是在构造器中使用了默认的过滤器,下文会介绍
		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));
		}
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		// 上面都是将注解里面的属性赋值给scanner,然后解析方法委托给ClassPathBeanDefinitionScanner#doScan
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	// 这里的basePackages就是之前的@ScanComponent所在的包名
	for (String basePackage : basePackages) {
	    // 找到候选组件
		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);
				// 将候选组件注册为BeanDefinition
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

扫描候选组件的类路径 ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
	}
	else {
		return scanCandidateComponents(basePackage);
	}
}

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
	    // String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
	    // 假如这里的basePackage是com.example.learnspring,resolveBasePackage(basePackage)就把包名变成com/example/learnspring
	    // resolveBasePackage(basePackage)的目的是将包名里面的"."换成"/",
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + "**/*.class";
		// 根据传入的路径解析成Resource
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		···省去日志
			if (resource.isReadable()) {
				try {
				    // 产生一个访问元数据的门面类MetadataReader
					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					// 判断是否为候选组件
					if (isCandidateComponent(metadataReader)) {
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						sbd.setResource(resource);
						sbd.setSource(resource);
						if (isCandidateComponent(sbd)) {
							candidates.add(sbd);
						}
						else {
							if (debugEnabled) {
								logger.debug("Ignored because not a concrete top-level class: " + resource);
							}
						}
					}
											}
				}
				catch (Throwable ex) {
					···
				}
	return candidates;
}

这里先介绍一下MetadataReader,这是一个通过ASM访问类元数据的门面类,在这里的实现类是SimpleMetadataReader,元数据就是SimpleAnnotationMetadataReadingVisitor通过ASM产生的。

final class SimpleMetadataReader implements MetadataReader {

	private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG
			| ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;

	private final Resource resource;

    // 数据元信息,包括className,superClassName等等,
    // 如果还是不清楚,看下面类StringRepository数据元信息图
	private final AnnotationMetadata annotationMetadata;

	SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
	    // 通过SimpleAnnotationMetadataReadingVisitor来创建AnnotationMetadata,原理是ASM,对这里有兴趣的同学可以在这里打个断点,尽情调试
		SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
		getClassReader(resource).accept(visitor, PARSING_OPTIONS);
		this.resource = resource;
		this.annotationMetadata = visitor.getMetadata();
	}

数据元信息 再来判断是否是候选组件

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	for (TypeFilter tf : this.excludeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return false;
		}
	}
	for (TypeFilter tf : this.includeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return isConditionMatch(metadataReader);
		}
	}
	return false;
}

我们的类StringRepository之前已经成功被Spring加载了,所有在这里肯定是返回true的,因此在这里打个断点进行调试。 excludeFilters: 排除含有注解的元信息 includeFilters:匹配含有注解的元信息 这里发现includeFilters里面包含了两个AnnotationTypeFilter,分别包含ComponentManagedBean,这是因为在registerDefaultFilters方法里面注册了默认的过滤器。

protected void registerDefaultFilters() {
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
	try {
		this.includeFilters.add(new AnnotationTypeFilter(
				((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
		logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
	}
	catch (ClassNotFoundException ex) {
		// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
	}
	try {
		this.includeFilters.add(new AnnotationTypeFilter(
				((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
		logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
	}
	catch (ClassNotFoundException ex) {
		// JSR-330 API not available - simply skip.
	}
}

搜索一下哪里调用了registerDefaultFilters,发现ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner()

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		if (useDefaultFilters) {
		    // 设置默认的过滤器
			registerDefaultFilters();
		}
		setEnvironment(environment);
		setResourceLoader(resourceLoader);
	}

最后发现在ClassPathBeanDefinitionScanner构造器中设置了默认的过滤器,也就是在ComponentScanAnnotationParser#parse方法的第一行,创建ClassPathBeanDefinitionScanner对象,与上文形成呼应。

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

绕来绕去,你可能有些晕了,让我们在整理一下,刚才介绍到判断是候选组件,也就是下面的match()方法返回true。从方法名猜测只要类中包含注解@Component或者@ManageBean就会匹配成功。下面继续深入源码分析。

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
		    // 核心判断逻辑
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
			    // 判断元信息是否包含@Conditional,跟条件相关的,这里省略
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

AbstractTypeHierarchyTraversingFilter#match(MetadataReader,MetadataReaderFactory)

	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
        // 主要讲解这个方法,下面的逻辑有兴趣的同学可以自己研究
		if (matchSelf(metadataReader)) {
			return true;
		}
        ······
		return false;
	}

	protected boolean matchSelf(MetadataReader metadataReader) {
	    // 获取元信息,这里是自定义注解@StringRepository信息
		AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
		// 下面的this.annotationType.getName()是传入的org.springframework.stereotype.Component
		return metadata.hasAnnotation(this.annotationType.getName()) ||
				(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
	}

AnnotationMetadata#hasMetaAnnotation

// 判断底层注解是否包含metaAnnotationName
default boolean hasMetaAnnotation(String metaAnnotationName) {
	return getAnnotations().get(metaAnnotationName,
			MergedAnnotation::isMetaPresent).isPresent();
}

到了这里, 就已经很清晰了,只有两个方法,一个是metadata.hasAnnotation(this.annotationType.getName()),这是判断类上是否含有给定的注解, 还有个方法就是 metadata.hasMetaAnnotation(this.annotationType.getName()),这是判断底层class是否含有给定的注解,而@StringRepository的底层确实包含了@Component,所以这里方法返回true。

isConditionMatch(MetadataReader)方法是判断元数据里面是否有满足条件注解,这里不介绍,有兴趣的看我另一篇文章,条件装配@Conditional源码分析

如果有地方有疑惑或者写的有不好,可以评论或者通过邮箱联系我creazycoder@sina.com