component-scan和@ComponentScan两者功能一致,用来让Spring容器扫描Bean,其中前者是配置在xml文件中,后者是在类上添加注解。
配置component-scan需要在xml中配置
配置@ComponentScan只需要在某个bean上添加注解,basePackages指定待扫描的包,支持数组配置多个值如下:
@RestController
@RequestMapping("/focuse")
@ComponentScan(basePackages = {"com.focuse.componentscandemo.annotationcomps"})
public class DemoController {
....
}
Spring实现组件扫描主要依赖ClassPathBeanDefinitionScanner类,类图如下
先粘贴一下源码:
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
private final List includeFilters = new LinkedList();
private final List excludeFilters = new LinkedList();
private Environment environment;
private ConditionEvaluator conditionEvaluator;
private ResourcePatternResolver resourcePatternResolver;
private MetadataReaderFactory metadataReaderFactory;
.....
}
先粘贴一下源码:
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();
...
}
ClassPathBeanDefinitionScanner扫描的执行逻辑
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
......
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
for (String basePackage : basePackages) {
Set 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 findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
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是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来处理
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。这到这
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 beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
......
}
@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 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 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 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 basePackages = new LinkedHashSet();
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了,但是其指定的包中的@Component的类都没扫描进来。一般情况从两个方面去排查
@ComponentScan(basePackages = {"com.focuse.componentscandemo.annotationcomps"})
public class DemoController {
......
}
Spring扫描bean的核心组件就是ClassPathBeanDefinitionScanner,这个类负责从各个jar包中找出类文件并用ClassLoader加载到JVM中,符合条件(例如@Component)就往Registry注册一个BeanDefinition。
component-context和@ComponentScan都是利用ClassPathBeanDefinitionScanner实现扫描的。前者是配置的xml中,Spring利用scheme扩展机制,ComponentScanBeanDefinitionParser解析该标签,并根据配置创建ClassPathBeanDefinitionScanner对象,然后扫描;后者是利用容器启动早期的回调机制,执行ConfigurationClassPostProcessor#processConfigBeanDefinitions。