天天看点

Spring 注解之 @ComponentScan

  1. 如何开启自动扫描?
  2. 没有指定包且没有指定类时,默认使用当前类所在包
  3. 哪些类会被扫描成 Spring Bean?
  4. 扫描得到的 Spring Bean 上,如果有 @ComponentScan 或 @ComponentScans,会继续处理
  5. 如何使用其属性 excludeFilters

1. 如何开启自动扫描?

在初始化 ApplicationContext 时,传入配置类,配置类上有 @ComponentScan 或 @ComponentScans 注解

配置类,有以下一个或多个注解:

  1. @Configuration
  2. @Component
  3. @ComponentScan
  4. @Import
  5. @ImportResource
  6. @Bean

也就是说

  1. 传入的类,只有一个 @ComponentScan 注解即可开启自动扫描
  2. 而只有 @ComponentScans 注解,是不会开启自动扫描的

相关源码如下

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	// 获取所有已注册的 beanDefinitionName,用户自定义的,就只有初始化上下文时传入来的
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		// 如果这个 beanDefinition 已经处理过,就会设置这个属性,这里就是避免重复处理
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		// 判断这个 beanDefinition 对应的类,是否是配置类
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
		return;
	}

	// ...
}
           
org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate

public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

    // ...
    
    // 是否有 @Configuration 注解
	Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
	if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
	}
	// 是否有 @Component、@ComponentScan、@Import、@ImportResource、@Bean 注解
	else if (config != null || isConfigurationCandidate(metadata)) {
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
	}
	// 都没有,就不是配置类
	else {
		return false;
	}

	// ...

	return true;
}
           
org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
	// Do not consider an interface or an annotation...
	if (metadata.isInterface()) {
		return false;
	}

	// Any of the typical annotations found?
	// candidateIndicators 是一个 Set,包括 @Component、@ComponentScan、@Import、@ImportResource
	// 有其一,即是配置类
	for (String indicator : candidateIndicators) {
		if (metadata.isAnnotated(indicator)) {
			return true;
		}
	}

	// Finally, let's look for @Bean methods...
	try {
        // 是否有 @Bean 标记的方法
		return metadata.hasAnnotatedMethods(Bean.class.getName());
	}
	catch (Throwable ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
		}
		return false;
	}
}
           
org.springframework.context.annotation.ConfigurationClassUtils

private static final Set<String> candidateIndicators = new HashSet<>(8);

static {
	candidateIndicators.add(Component.class.getName());
	candidateIndicators.add(ComponentScan.class.getName());
	candidateIndicators.add(Import.class.getName());
	candidateIndicators.add(ImportResource.class.getName());
}
           

2. 没有指定包且没有指定类时,默认使用当前类所在包

class ComponentScanAnnotationParser {
    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		
        //...

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

		// ...
	}
}
           

3. 哪些类会被扫描成 Spring Bean?

被 @Component 注解的类

扫描器在初始化时,会注册 @Component 过滤器(其实还有 @ManagedBean 和 @Named)

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#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.
	}
}
           

使用过滤器过滤

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent

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

4. 扫描得到的 Spring Bean 上,如果有 @ComponentScan 或 @ComponentScans,会继续递归处理

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

    // ...
	
	// Process any @ComponentScan annotations
	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
			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());
				}
			}
		}
	}

	// ...
	
	return null;
}
           

5. 如何使用其属性 excludeFilters

@ComponentScan(value = {"com.balsam.componentScan1"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ComponentScanBean1.class)})
@Component
public class ComponentScanBean {
    public ComponentScanBean() {
        System.out.println("ComponentScanBean init..");
    }
}
           
public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	// 过滤含有给定注解的
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	// 过滤给定类型的
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	// 过滤匹配 AspectJ 表达式的
	ASPECTJ,
 
	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	// 过滤匹配正则表达式的
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	// 过滤匹配自定义规则的
	CUSTOM
}