前言
上一篇我们已经将配置文件中的bean节点的注册化和实例化讲解了。接下来我们来看下下面这种扩展节点是什么解析的。
<context:component-scan base-pacakge="" />
在Spring的配置节点中分为两种,一种就是bean,import这种的原生节点(个人臆造),另一种就是上面举例的这种扩展节点。还记得我们讲解解析bean节点的代码吗。
将目光投向
DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(root, this.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)) {
//解析原生节点,bean import等
parseDefaultElement(ele, delegate);
}
else {
//解析拓展节点
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这段代码并不难理解,就是解析节点,我们分析bean的解析注册时,走的是 parseDefaultElement(ele, delegate);
那么到了拓展节点,自然就是走 delegate.parseCustomElement(ele);了。
我们就以这个方法为切入点来分析。
context:component-scan base-pacakge=”” 节点的解析
delegate.parseCustomElement(ele);
这里的delegate是一个BeanDefinitionParserDelegate实例。
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
//得到handler
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));
}
这个方法代码行数很少:
1.先得到NamespaceHandler实例
2.使用这个实例来解析扩展元素。
我们先看我们这个要得到的NamespaceHandler实例
NamespaceHandler在这里到底起到了什么作用,这个实例又是根据什么得来的呢?
首先通过Element的NameSpace得到相应的命名空间。(这个属于XML的知识啦)
然后根据不同的Namespace获取对应的NamespaceHandler,这个是怎么匹配上的呢,是因为我们在Beans标签配置了命名空间,然后就可以配置对应的标签,解析标签时,我们就有相应命名空间所对应的NamespaceHandler来解析。
我们回想下我们编写Spring的配置文件时,都需要在Beans标签上配置命名空间。比如我们的配置文件就有如下配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd" default-autowire="byName">
其中我们就看到有context标签。这个就对应了ContextNamespaceHandler。
这个ContextNamespaceHandler是NamespaceHandlerSupport的直接子类(NamespaceHandlerSupport直接实现了NamespaceHandler接口)。我们捎带手的来看下这个类
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());
}
}
我们看到这个类就是注册了一些BeanDefinitionParser,其中就包括\节点对应的ComponentScanBeanDefinitionParser()
以\节点为例,我们已经在第一步里得到了ContextNamespaceHandler实例。
接下来便来到第二步,去调用handler.parse()方法。
这个方法很明显具体实现是在NamespaceHandlerSupport类中。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
我们通过findParserForElement来找到对应的BeanDefinitionParser,很显然这里是ComponentScanBeanDefinitionParser实例,这个实例的注入时机已经在上面说明了。
然后再调用ComponentScanBeanDefinitionParser的parse(element, parserContext);方法来进行解析。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
//先得到basepacakge,可能有多个
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;
}
还记得我们首次使用\节点的时候,文档告诉我们这个节点的作用吗?
使用这个节点的标配是指定他的base-package=”xxxx”这个属性。一旦指定了这个base-package就意味着这个包下面的所有类都会被扫描,然后会将带有@Component,@Controller,@Service,@Respository的类都会被注册到Spring容器中,而这个功能的实现就是靠的上面的这个方法。这里多说一句,@Component是通用注解,而后面的三个注解是子注解,将各种功能的Bean分类,不信你去看这几个注解的源码,你会发现,后面的三个注解都被@Component标注了。
我们来分析一下这个方法。
(1)获取context:component-scan 配置的属性base-package的值,然后放到数组。
(2)创建扫描对应包下的class和jar文件的对象ClassPathBeanDefinitionScanner ,由这个类来实现扫描包下的class和jar文件并把注解的Bean包装成BeanDefinition。
(3)BeanDefinition注册到Bean工厂。
其中扫描的操作是由ClassPathBeanDefinitionScanner的doScan方法完成的。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//在这里终于看到了我们很熟悉的BeanDefinitionHolder了,这里新建了一个Set集合,为什么是Set,不允许重复元素呀,看到这里,在看看方法的返回类型,我们就能知道下面的代码的作用啦,其实就是得到相应的BeanDefinitionHolder集合,那么具体怎么得到的呢,我们需要继续往下看。
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;
}
1.新建一个BeanDefinitionHolder集合,看到这里,在看看方法的返回类型,我们就能知道下面的代码的作用啦,其实就是得到相应的BeanDefinitionHolder集合,那么具体怎么得到的呢,我们需要继续往下看。
2.调用父类ClassPathScanningCandidateComponentProvider的findCandidateComponents方法扫描包并且得到BeanDefinitionHolder集合
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
//base-package中的值替换为classpath*:cn/test/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
//获取所有base-package下的资源
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);
//对context:exclude-filter进行过滤
if (isCandidateComponent(metadataReader)) {
//包装BeanDefinition
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;
}
(1)先根据context:component-scan 中属性的base-package=”cn.test”配置转换为classpath*:cn/test/*/.class,并扫描对应下的class和jar文件并获取类对应的路径,返回Resources
(2)根据\<context:exclude-filter>指定的不扫描包,\<context:exclude-filter>指定的扫描包配置进行过滤不包含的包对应下的class和jar。
(3)封装成BeanDefinition放到队列里。
我们通过findCandidateComponents得到了BeanDefinition集合,接下来我们就需要将Bean注册到BeanFactory中了,也就是注册到Spring容器中了。
3.通过registerBeanDefinition将Bean注册到BeanFactory工厂中。
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
看到这里就不用再分析了吧,到了这里和之前讲解bean节点的注入初始化的步骤都是一样的了。