跟我读Spring——Spring源码浅析(二)

目录

Spring配置文件的解析逻辑

整个流程概览

  1. 首先新建一个Maven项目(参照序),配置好spring依赖,maven下载好源码和doc。
  2. 随便建一个类TestBean

    public class TestBean {
         private String name;
    
         public String getName() {
             return name;
         }
    
         public void setName(String name) {
             this.name = name;
         }
    }
  3. resources里建一个fullflow.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
        <bean class="TestBean" id="testBean"/>
    </beans>
  4. 测试类

    public class TestFullFlow {
        public static void main(String[] args) {
            BeanFactory beanFactory = new XmlBeanFactory(
                    new ClassPathResource("fullflow.xml"));
            System.out.println(beanFactory.getBean("testBean") != null);
        }
    }
  5. 跟进XmlBeanFactory构造器

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

    第二行this.reader.loadBeanDefinitions(resource);就是我们主要要看的。
    跟进代码XmlBeanDefinitionReader

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

    这里有个类叫EncodedResource,目的是把资源封装为有编码的,就是把编码和资源绑定在一块。

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
        //获得正在加载的资源文件
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
        //第一次为空,初始化
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        //如果添加当前资源文件不成功,因为是Set(不可重复集合),代表已经有了这个文件
        //抛出异常(循环加载)
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //这个才是主要的逻辑,spring的源码风格,一般以do开头的才是主要做事的。
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        ……
    }

    跟进doLoadBeanDefinitions方法:

    int validationMode = getValidationModeForResource(resource);
    Document doc = this.documentLoader.loadDocument(
                    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
    return registerBeanDefinitions(doc, resource);

    下面我们分别看这几个方法的逻辑

XML Validator模式的判断

  1. 跟踪代码到XmlBeanDefinitionReader类

    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // Hmm, we didn't get a clear indication... Let's assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document's root tag).
        return VALIDATION_XSD;
    }

    validationMode定义为private int validationMode = VALIDATION_AUTO;

  2. 跟踪到XmlValidationModeDetector类的detectValidationMode方法

    public int detectValidationMode(InputStream inputStream) throws IOException {
        // Peek into the file to look for DOCTYPE.
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            boolean isDtdValidated = false;
            String content;
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
            // Choked on some character encoding...
            // Leave the decision up to the caller.
            return VALIDATION_AUTO;
        }
        finally {
            reader.close();
        }
    }
  3. consumeCommentTokens方法的作用是消耗注释,让我们一起看看这个逻辑

    private String consumeCommentTokens(String line) {
        //首先如果没有带<!-- 或 -->的直接返回内容代表没有再注释里面
        if (line.indexOf(START_COMMENT) == -1 && line.indexOf(END_COMMENT) == -1) {
            return line;
        }
        while ((line = consume(line)) != null) {
            //没有在注释中或者不是由注释开头的返回内容,inComment的这个标识位表示当前是否在注释中
            if (!this.inComment && !line.trim().startsWith(START_COMMENT)) {
                return line;
            }
        }
        return line;
    }
  4. 跟踪consume方法

    private String consume(String line) {
        //如果当前在注释中,消费注释结尾标记-->,否则消耗<!--
        int index = (this.inComment ? endComment(line) : startComment(line));
        return (index == -1 ? null : line.substring(index));
    }
    //注:inComment这个标识位主要是为了多行注释处理。
    private int startComment(String line) {
        //如果找到注释开头标记,把inComment设为true
        return commentToken(line, START_COMMENT, true);
    }
    
    private int endComment(String line) {
        //如果找到注释结尾标记,把inComment设为false,代表这个注释块结束
        return commentToken(line, END_COMMENT, false);
    }
    
    /**
     * Try to consume the supplied token against the supplied content and update the
     * in comment parse state to the supplied value. Returns the index into the content
     * which is after the token or -1 if the token is not found.
     */
    private int commentToken(String line, String token, boolean inCommentIfPresent) {
        //如果找到注释标记
        int index = line.indexOf(token);
        if (index > - 1) {
            this.inComment = inCommentIfPresent;
        }
        //得到去掉注释后内容的开头index
        return (index == -1 ? index : index + token.length());
    }

    消耗注释的逻辑就是这样,接下来看主方法detectValidationMode

    private static final String DOCTYPE = "DOCTYPE";
    private boolean hasDoctype(String content) {
        return (content.indexOf(DOCTYPE) > -1);
    }

    如果找到了DOCTYPE定义,那么就是DTD的,否则就是Schema模式。
    关于xml验证模式的推定逻辑到此结束。

XML验证文件的URL解析

  1. 回到XmlBeanDefinitionReader的doLoadBeanDefinitions方法

    Document doc = this.documentLoader.loadDocument(inputSource,getEntityResolver(),this.errorHandler,validationMode,isNamespaceAware());

    这里可以知道spring的xml解析使用的Java xml api里的DOM。
    让我们看getEntityResolver

    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            }
            else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

    EntityResolver是个什么概念呢?我们定义的XML文件里有引用相关的DTD和XSD,它们都是一个URL。那么一般会从网络上下载这些DTD和XSD,但是当网络不好的时候或者断网的时候就体验不好或无法做到。这里使用了EntityResolver解析本地的DTD和XSD(Schema)。
    这里有两种EntityResolver:ResourceEntityResolver和DelegatingEntityResolver,而实际上前者是后者的子类。前者的作用是如果DelegatingEntityResolver加载不到资源,那么再使用已设置的ResourceLoader尝试加载资源。让我们看一看DelegatingEntityResolver的实现。

    public DelegatingEntityResolver(ClassLoader classLoader) {
        //这里有两种Resolver,一个是DTD的一个是Schema的,spring会根据参数systemId(一个XML定义的参数)来选择使用。
        this.dtdResolver = new BeansDtdResolver();
        this.schemaResolver = new PluggableSchemaResolver(classLoader);
    }

    BeansDtdResolver内部在jar包里找spring-beans-2.0.dtd和spring-beans.dtd。
    而PluggableSchemaResolver内部则是找jar包里的META-INF/spring.schemas文件,里面是Schema文件(xsd)对应在jar包里的路径。通过这个来解析我们再spring配置文件里定义的xsd。

注册BeanDefinition

  1. 上述把获取验证方式和spring xml的验证文件的获取逻辑讲完了,也就是下面代码的第一二句。

    int validationMode = getValidationModeForResource(resource);
    Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(),this.errorHandler, validationMode,isNamespaceAware());
    return registerBeanDefinitions(doc, resource);
  2. 下面到了registerBeanDefinitions(doc, resource);这一句。跟进

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //获取BeanDefinitionDocumentReader ,默认是DefaultBeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //设置当前环境到Reader里
        documentReader.setEnvironment(this.getEnvironment());
        //获取当前的BeanDefinition数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        //注册此次的BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //得到此次注册的数量
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
  3. 跟进到DefaultBeanDefinitionDocumentReader

    protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
    }

    这一段是解析spring的profile属性,关于profile的用法,请自行查找资料。总的来说是和maven的profile差不多,也是切换各个环境的配置(dev、product)等。

    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);
        }
    }

    在spring中有两种标签,一个是beans命名空间下的默认标签:<bean id="bean1" class="TestBean"/>,第二种是自定义标签<aop:aspect id="myAspect" ref="aBean">像这种的,又如像dubbo的自定义标签等。parseCustomElement都是用来处理这种标签的

自带命名空间标签的解析

  1. 自带默认命名空间的解析

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            //解析import标签
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            //解析alias标签
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            //解析bean标签
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            //解析嵌套beans标签
            doRegisterBeanDefinitions(ele);
        }
    } 
  2. import标签的解析

    protected void importBeanDefinitionResource(Element ele) {
        ……
        //解析占位符
        location = environment.resolveRequiredPlaceholders(location);
        boolean absoluteLocation = false;
        ……
        //判断是否是绝对路径
            absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
        ……
        else {
            // No URL -> considering resource location as relative to the current file.
            try {
            //相对路径加载
                int importCount;
                Resource relativeResource = getReaderContext().getResource().createRelative(location);
                if (relativeResource.exists()) {
                //这里的loadBeanDefinitions又回到了先前的方法里,是一个环形调用
                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                }
                else {
                    String baseLocation = getReaderContext().getResource().getURL().toString();
                    importCount = getReaderContext().getReader().loadBeanDefinitions(
                            StringUtils.applyRelativePath(baseLocation, location), actualResources);
                }
    }
  3. 其他解析alias等的就不介绍了,最重要的方法是processBeanDefinition,解析bean标签的

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate){
            //委托给了BeanDefinitionParserDelegate 加载
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    }
  4. BeanDefinitionParserDelegateparseBeanDefinitionElement方法

    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    
    parseMetaElements(ele, bd);
    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    
    parseConstructorArgElements(ele, bd);
    parsePropertyElements(ele, bd);
    parseQualifierElements(ele, bd);
    

Bean标签的解析过程

  1. 首先看上述第一句

     //解析Bean标签的属性
     parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    

    跟进

    if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
            // Spring 2.x "scope" attribute
            bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
            if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
            //这一句表示不能同时有scope和singleton
                error("Specify either 'scope' or 'singleton', not both", ele);
            }
        }
        else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
            // Spring 1.x "singleton" attribute
            bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?
                    BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);
        }
    

    这里提一句,spring的singleton属性是1.x版本的,在2.x标准的是使用scope="singleton"。3.x版本的需要兼顾以上两者,但是这两个属性是互斥的,不能同时指定。

    下面的代码就是对spring的bean标签的各个属性的解析了,如abstract,depends-on,lazy-init,dependency-check,autowire等等的解析了。对spring各属性不熟的同学可以查一查资料,不在本文讲述范围内。

外部标签的解析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值