SpringMVC配置文件的解析(二)

本文深入解析了Spring框架中context:component-scan节点的工作原理,详细介绍了如何通过该节点指定base-package属性来扫描并注册带有特定注解的Bean。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

上一篇我们已经将配置文件中的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节点的注入初始化的步骤都是一样的了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值