【Spring】component-scan和@ComponentScan配置解析

配置形式

    component-scan和@ComponentScan两者功能一致,用来让Spring容器扫描Bean,其中前者是配置在xml文件中,后者是在类上添加注解。

    配置component-scan需要在xml中配置<component-scan>,属性"base-package"指定待扫描的包,支持多值用逗号","隔开。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.focuse.componentscandemo.xmlcomps" />
</beans>

    配置@ComponentScan只需要在某个bean上添加注解,basePackages指定待扫描的包,支持数组配置多个值如下:

@RestController
@RequestMapping("/focuse")
@ComponentScan(basePackages = {"com.focuse.componentscandemo.annotationcomps"})
public class DemoController {
    ....
}

核心实现-ClassPathBeanDefinitionScanner

    Spring实现组件扫描主要依赖ClassPathBeanDefinitionScanner类,类图如下

 ClassPathScanningCandidateComponentProvider核心属性

先粘贴一下源码:

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
	static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

	private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

	private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();

	private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

	private Environment environment;

	private ConditionEvaluator conditionEvaluator;

	private ResourcePatternResolver resourcePatternResolver;

	private MetadataReaderFactory metadataReaderFactory;
    
    .....
}
  • resourcePattern 资源文件的格式,默认**/*.class所有的类文件;
  • includeFilters 包含的过滤器,即命中其中任意过滤器的则扫描,一般默认有@Component过滤器(AnnotationTypeFilter);
  • excludeFilters 排除的过滤器,即命中其中任意过滤器的则不扫描,优先级高于includeFilters;
  • conditionEvaluator 用来判断是否满足@Conditional条件;
  • resourcePatternResolver 用来加载资源文件的,模式匹配(见resourcePattern);
  • metadataReaderFactory 用来生成MetadataReader对象(可以读取类的元信息)。

ClassPathBeanDefinitionScanner核心属性

先粘贴一下源码:

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

	private final BeanDefinitionRegistry registry;

	private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();

	private String[] autowireCandidatePatterns;

	private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

	private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
   
    ...
}
  • registry BeanDefinition的注册中心,实际上就是容器;
  • beanDefinitionDefaults BeanDefinition默认配置,如果是xml配置bean,就是<beans>标签里面"default-lazy-init"等属性;
  • autowireCandidatePatterns 用来判断BeanDefinition能否作为自动装配候选,即符合模式的就能作为自动装配的候选;
  • beanNameGenerator 用来生成bean的名称;
  • scopeMetadataResolver 用来解析bean的scope,以及scope-proxy的模式(JDK、CGLIB)

扫描流程

ClassPathBeanDefinitionScanner扫描的执行逻辑

  • 1 遍历待扫描的包(basePackages)
    • 1.1 resourcePatternResolver获取符合classpath*: + {basePackage} + {resourcePattern}所有资源
    • 1.2 遍历资源
      • 1.2.1 metadataReaderFactory获取资源的元信息
      • 1.2.2 元信息是否满足excludeFilters过滤器,满足则回到1.2,否则继续
      • 1.2.3 元信息是否满足includeFilters过滤器(一般默认有@Component过滤器,即只要该类被@Component注解都会满足),不满足则回到1.2,否则继续
      • 1.2.4 创建BeanDefinition(ScannedGenericBeanDefinition) 加入到候选集
    • 1.3 遍历BeanDifinition候选集
      • 1.3.1 补充设置属性 ClassPathBeanDefinitionScanner#postProcessBeanDefinition
      • 1.3.2 补充设置属性 AnnotationConfigUtils#processCommonDefinitionAnnotations
      • 1.3.3 根据配置创建代理BeanDifinition  AnnotationConfigUtils#applyScopedProxyMode
      • 1.3.4 注册到Registry  ClassPathBeanDefinitionScanner#registerBeanDefinition
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

    ......
 
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		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);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
 
    ......

}
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {    
    ......
    
    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
						if (isCandidateComponent(metadataReader)) {
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}

    ......

}

ok! 知道了ClassPathBeanDefinitionScanner的逻辑,接下来我们分析component-scan和@ComponentScan是如何利用ClassPathBeanDefinitionScanner进行扫描的

component-scan如何实现扫描

    component-scan是xml配置,利用了Spring的xml schema机制实现解析的(不了解schema机制的,可以阅读另一篇博文Spring中自定义Schema配置Bean)。我们看一下spring-context包下的spring.handlers文件,得知NamesapceHandler是ContextNamespaceHander。

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

 ContextNamespaceHander注册了一系列的标签解析器,其中ComponentScanBeanDefinitionParser来处理<component-scan>标签

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

ComponentScanBeanDefinitionParser很简单,就是根据element配置创建ClassPathBeanDefinitionScanner,配置ClassPathBeanDefinitionScanner过程这里不讲了,element配置了什么属性就设置什么属性。最后执行ClassPathBeanDefinitionScanner#doScan扫描Bean。这到这<component-scan>分析完了。

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {

    ...... 

    @Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}

    ......

}

@ComponentScan如何实现扫描

    @ComponentScan跟component-can不同,它依赖的是ConfigurationClassPostProcessor。ConfigurationClassPostProcessor是一个BeanDefinitionRegistryPostProcessor。容器在启动的早期(Bean实例化之前)会回调各个BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry。

    ConfigurationClassPostProcessor作为BeanDefinitionRegistryPostProcessor也会被调用postProcessBeanDefinitionRegistry,这里直接调用ConfigurationClassPostProcessor#processConfigBeanDefinitions。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    ......

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		processConfigBeanDefinitions(registry);
	}

    ......

}

    ConfigurationClassPostProcessor#processConfigBeanDefinitions首先拿到当前已经注册的BeanDifinition,并过滤符合条件的作为候选集(@Configuration、@Component、@ComponentScan、@Import、@ImportResource或则含有@Bean方法)。然后构建ConfigurationClassParser对象,并执行ConfigurationClassParser#parse方法,这里又会执行到ConfigurationClassParser#processConfigurationClass方法,然后最终执行ConfigurationClassParser#doProcessConfigurationClass。doProcessConfigurationClass除了处理@ComponentScan还处理@Import、@ImportResource、@Bean等逻辑,这里我们只分析@ComponentScan逻辑,所以只贴了部分代码。

class ConfigurationClassParser {

    ......

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			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());
					}
				}
			}
		}

		......
	}

    ......

}

 我们看到ComponentScanAnnotationParser#parse处理了@ComponentScan的,继续往下。首先是根据@ComponentScan的注解配置创建ClassPathBeanDefinitionScanner对象,然后用ClassPathBeanDefinitionScanner扫描@ComponentScan注解指定的待扫描的包(basePackages)。至此,@ComponentScan也就是想了用ClassPathBeanDefinitionScanner扫描Bean。

class ComponentScanAnnotationParser {

    ......

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		Assert.state(this.environment != null, "Environment must not be null");
		Assert.state(this.resourceLoader != null, "ResourceLoader must not be null");

		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>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			basePackages.addAll(Arrays.asList(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);
			}
		});
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

    ......

}

@ComponentScan常见配置问题

如果配置@ComponentScan了,但是其指定的包中的@Component的类都没扫描进来。一般情况从两个方面去排查

  • 被@ComponentScan的注解的类本身有没有先注册到容器中。例如,如果DemoController没有先注册到容器中,其注解也不会被扫描
    @ComponentScan(basePackages = {"com.focuse.componentscandemo.annotationcomps"})
    public class DemoController {
    
        ......
    
    }

     

  • ConfigurationClassPostProcessor这个类也要注册到容器中才可以,SpringBoot默认是注册的,SpringMVC则需要配置<annotation-config> 或者 <component-scan>,这两个标签都会注册ConfigurationClassPostProcessor到容器
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
        <context:annotation-config></context:annotation-config>
    </beans>

     

总结

    Spring扫描bean的核心组件就是ClassPathBeanDefinitionScanner,这个类负责从各个jar包中找出类文件并用ClassLoader加载到JVM中,符合条件(例如@Component)就往Registry注册一个BeanDefinition。

    component-context和@ComponentScan都是利用ClassPathBeanDefinitionScanner实现扫描的。前者是配置的xml中,Spring利用scheme扩展机制,ComponentScanBeanDefinitionParser解析该标签,并根据配置创建ClassPathBeanDefinitionScanner对象,然后扫描;后者是利用容器启动早期的回调机制,执行ConfigurationClassPostProcessor#processConfigBeanDefinitions。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值