概述
在spring的配置文件中,通常会配置自动扫描包路径下的bean,而</context:component-scan>
元素即用来实现该功能,下面是一个配置简单实例:
<context:component-scan base-package="com.xxx">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
在之前文章中我们可以知道,spring在解析配置文件时是通过spring.handlers文件中指定的处理类来查找对应类来处理元素,我们打开spring-context jar包下面的spring.handlers这个文件,内容如下:
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
可以看到,<context>
命名空间下的元素是使用org.springframework.context.config.ContextNamespaceHandler
这个类来处理的,下面进入ContextNamespaceHandler这个类中:
@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());
}
可以看到,在该类中指定了’http://www.springframework.org/schema/context‘命名空间下各个元素对应的具体处理类,让我们回到之前DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
方法中:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
之前说过,当该方法处理不是默认命名空间下的元素时,会调用BeanDefinitionParserDelegate.parseCustomElement(Element element)
这个方法来处理,在这个方法中:
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上面代码首先查找各个命名空间对应的处理类,然后调用处理类的parse方法进行解析,而在ContextNamespaceHandler的parse方法中会调用在init()方法中注册的各个元素对应的处理类的parse方法进行解析.其实就是调用ComponentScanBeanDefinitionParser.parse方法来处理</context:component-scan>
元素.
ComponentScanBeanDefinitionParser
ComponentScanBeanDefinitionParser实现了BeanDefinitionParser接口,该接口只有一个parse方法用来解析Dom Element元素为BeanDefinition对象(实际可能返回多个BeanDefinition对象),并将BeanDefinition注册到BeanDefinitionRegistry容器中,parse方法定义如下:
BeanDefinition parse(Element element, ParserContext parserContext);
通过parseContext.getRegistry()方法可以获取BeanDefinitionRegistry对象,在这里其实返回的就是之前创建的DefaultListableBeanFactory.
下面看具体parse方法处理过程:
private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
private static final String FILTER_TYPE_ATTRIBUTE = "type";
private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
@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;
}
首先在ComponentScanBeanDefinitionParser类中定义了</context:component-scan>
元素的所有属性值
- base-package:为必须配置属性,指定了spring需要扫描的跟目录名称,可以使用”,” “;” “\t\n(回车符)”来分割多个包名
- resource-pattern:配置扫描资源格式.默认”
**/*.class
” - use-default-filters:是否使用默认扫描策略,默认为”true”,会自动扫描指定包下的添加了如下注解的类,@Component, @Repository, @Service,or @Controller
- annotation-config:是否启用默认配置,默认为”true”,该配置会在BeanDefinition注册到容器后自动注册一些BeanPostProcessors对象到容器中.这些处理器用来处理类中Spring’s @Required and
@Autowired, JSR 250’s @PostConstruct, @PreDestroy and @Resource (如果可用),
JAX-WS’s @WebServiceRef (如果可用), EJB 3’s @EJB (如果可用), and JPA’s
@PersistenceContext and @PersistenceUnit (如果可用),但是该属性不会处理Spring’s @Transactional 和 EJB 3中的@TransactionAttribute注解对象,这两个注解是通过<tx:annotation-driven>
元素处理过程中对应的BeanPostProcessor来处理的. - include-filter:如果有自定义元素可以在该处配置
- exclude-filter:配置哪些类型的类不需要扫描,如上面指定了类中添加了”Controller”元素的类不扫描.
注意:</context:component-scan>
元素中默认配置了annotation-config,所以不需要再单独配置</annotation-config>
元素.
接下来进入parse方法:
该方法首先解析base-package属性值,因为该属性可以配置多个包名,然后根据各个属性配置值来生成一个ClassPathBeanDefinitionScanner对象,然后调用scanner.doScan方法来扫描符合条件的类.最后调用registerComponents(XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element)
方法来注册组件.
从上面可以看出扫描工作是通过ClassPathBeanDefinitionScanner类中的doScan方法来完成的.
ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner主要用来完成类路径下符合条件的bean扫描功能,并将扫描出来的bean注册到指定的BeanFactory中。
- 默认过滤器主要扫描@Component @Repository @Service @Controller注解的类,同样可以通过配置类扫描过滤器来扫描自定义注解的类。
- 当类路径下有javax.annotation.ManagedBean和javax.inject.Named类库时支持这2个注解扫描。
扫描过程:
- 首先构造一个ClassPathBeanDefinitionScanner对象,需要传递一个BeanDefinitionRegistry对象。
- 根据配置文件配置属性设置scanner的扫描属性,比如”resource-pattern”, “name-generator”, “scope-resolver”等。
- 调用scanner.doScan(String… basePackages)方法完成候选类的自动扫描。
注意: doScan(String… basePackages)为protected类型,故只能在同一包下面的类或者子类调用,如果外部类调用可以调用scan(String… basePackages)方法,scan方法为public.
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;
}
在doScan方法中首先通过Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
来获取类路径下符合条件的候选findCandidateComponents(basePackage)源码如下:
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory =
new CachingMetadataReaderFactory(this.resourcePatternResolver);
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;
}
/**
* Resolve the specified base package into a pattern specification for
* the package search path.
* <p>The default implementation resolves placeholders against system properties,
* and converts a "."-based package path to a "/"-based resource path.
* @param basePackage the base package as specified by the user
* @return the pattern specification to be used for package searching
*/
protected String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
}
从上面代码可以看到,核心扫描功能主要时通过PathMatchingResourcePatternResolver类的getResources的方法来扫描resource-pattern下面所有的类,然后通过MetadataReader获取资源文件是否符合类要求。