SpringContext(3)-配置文件读取成Resource数组

10/2/2019

# SpringContext(3)-配置文件读取成Resource数组

调用getResource(location)方法获取Resource数组时,程序中判断resourceLoader是否是ResourcePatternResolver实例,是则调用ResourcePatternResolver实例的方法,否则调用resourceLoader的方法来加载,在本例中,由于AbstractApplicationContext实现ResourcePatternResolver接口,因此调用了实现方法:return this.resourcePatternResolver.getResources(locationPattern);,在这里this.resourcePatternResolver为PathMatchingResourcePatternResolver对象,实际调用PathMatchingResourcePatternResolver实例方法:getResources(locationPattern)

此方法根据传入的字符串的特征来调用相应的策略,将传入的字符串解析成Resource数组,策略按顺序如下:

  1. "classpath*:"开头的,首先检测是否有通配符('*','?',{})等,如果有,则遍历所有的classpath上的文件包括jar和zip文件来匹配并返回,如果没有通配符,则调用内部ClassLoader的getResources方法匹配相关路径,将结果包裹在UrlResource对象数组中返回

  2. 非"classpath*:"开头的,首先检测是否有通配符('*','?',{})等,如果有,则遍历所有的classpath上的文件包括jar和zip文件来匹配并返回,否则就是当前实例的情况,调用内部ClassLoader的getResources方法匹配相关路径,将结果包裹在UrlResource对象数组中返回,当前实例的ResourceLoader是当前的ClassPathXmlApplicationContext,其中resourceLoader相关的方法在父类DefaultResourceLoader中实现,此方法同样是对传入值进行各种模式判断,再根据判断结果使用不同的策略来返回结果,

    1. ”/“开头的,将参数包裹为ClassPathContextResource对象返回
    2. ”classpath:"开头的,去掉”classpath:"后将参数包裹为ClassPathResource对象返回
    3. 上述都不符合,首先尝试包装参数成URL对象,再根据Url对象的protocol包装成FileUrlResource或者UrlResource对象
    4. 无法解析成URL对象的,如本例的入参,则将参数包装成ClassPathContextResource对象返回,本例符合这种情况

至此所有的configLocation字符串已经解析成Resource数组,当前实例中为一个ClassPathContextResource对象的数组,返回后,程序将继续进行Bean定义的读取

# AbstractBeanDefinitionReader从Resource对象中加载Bean定义

AbstractBeanDefinitionReader获取到Resource数组后,会迭代数组元素,对每个Resource执行loadBeanDefinitions(Resource resource)方法来加载Bean定义,并将读取到的Bean定义数目加和后返回,具体的读取流程见下一节AbstractBeanDefinitionReader从Resource对象中加载Bean定义

每个configLocation的每个Resource对象的Bean全部加载完成后,beanFactory被存入ApplicationContext,继续进行refresh操作

第二节:规整configLocation成Resource数组中,在初始化完BeanFactory和XmlBeanDefinitionReader对象后,XmlBeanDefinitionReader对象将每一个configLocation字符串规整为Resource数组完成后,开始遍历获取到的Resource数组,并在每个对象上调用的loadBeanDefinitions(Resource resource)方法来加载BeanDefinition到reader内部的BeanFactory中,代码如下:

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

调用下面的方法

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        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());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

代码逻辑如下:

  1. 判断是当前reader中是否有相同的resource正在加载,如果有说明配置错误,报错
  2. 获取resource的inputStream,并包裹成sax类型的InputSource
  3. 调用doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法来读取xml文件并加载BeanDefinition

其中第三步的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中,先加载xml到Document对象实例中,再调用public int registerBeanDefinitions(Document doc, Resource resource)方法进行bean的注册,并处理过程中的异常与打印,后者是核心方法

# 1.1 ClassPathResource获取inputStream

首先,底层调用ClassLoader类中getResource(String name)方法获取文件对应的URL,其内部执行的顺序为:

  1. 调用父ClassLoader的getResource(name)的方法,如果返回值不为空则返回
  2. 在虚拟机的内置BootStrap ClassLoader里寻找name对应的url,有则返回结果
  3. 调用本对象的findResource(name)方法(默认是返回null,可能被子类复写),无论是否返回调用结果

在本例中,实际上是在第三步中,调用当前的UrlClassLoader的方法获取到URL,获取到的URL为file协议,指向项目target/classes目录下的application-context.xml目录,形式上类似"file:/myproject/target/classes/application-context.xml"文件,获取到文件后,通过调用URL类的openConnection方法返回相关的文件流InputStream

# 1.2 XmlBeanDefinitionReader从xml流中加载Bean定义:doLoadBeanDefinitions方法

  1. 调用doLoadDocument(inputSource, resource)方法将xml加载为Document对象
  2. 调用registerBeanDefinitions(doc, resource)方法,将上一步加载的Document对象作为第一个参数传入来注册Bean定义

当然,无论是文件读取,xml解析,bean定义解析都会碰到各种各样的异常,比如SAX解析异常,bean定义错误,IO异常等,在这个方法中将碰到的所有异常全部包裹为BeanDefinitionStoreException并抛出,下面一步步解析这两步具体的过程

# 1.2.1 xml文件流读取为Document对象

Spring采用SAX方式读取xml配置文件,实际调用的是DefaultDocumentLoader对象的Document loadDocument(InputSource inputSource,EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware)throws Exception;方法,各参数如下:

  • entityResolver使用ResourceEntityResolver
  • xml校验模式调用XmlValidationModeDetector对象的detectValidationMode方法进行探测,具体策略为假如xml开头有"DOCTYPE"字样证明是DTD校验,没有则为XSD校验,在本例使用的xml配置文件没有"DOCTYPE"字段,因此校验模式为XSD校验
  • namespaceAware为false

具体简化为

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
1
2
3

第一步构建DocumentBuilderFactory,第二步构建DocumentBuilder,第三步调用parse方法来加载文件流到Document对象

# 1.2.2 DOM文件解析成beanDefinition

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
1
2
3
4
5
6

第一步初始化了BeanDefinitionDocumentReader类型的对象,在此是DefaultBeanDefinitionDocumentReader类,然后调用其registerBeanDefinitions方法来进行bean的注册,调用完成后通过检查BeanFactory内部Bean的数量变化,返回实际初始化的bean,此方法第二个参数是XmlReaderContext对象,调用的构造函数为

    public XmlReaderContext(
            Resource resource, ProblemReporter problemReporter,
            ReaderEventListener eventListener, SourceExtractor sourceExtractor,
            XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) {

        super(resource, problemReporter, eventListener, sourceExtractor);
        this.reader = reader;
        this.namespaceHandlerResolver = namespaceHandlerResolver;
    }
1
2
3
4
5
6
7
8
9

其中参数:

  • problemReporter为FailFastProblemReporter对象,这个类中碰到error和fatal均包裹成BeanDefinitionParsingException并抛出
  • eventListener为EmptyReaderEventListener对象,不做任何操作
  • sourceExtractor允许自定义如何从定义中提取额外的bean的meta data并存入beanFactory,这里为了节约内存一律不提取
  • namespaceHandlerResolver为DefaultNamespaceHandlerResolver对象

# 1.2.2.1 DefaultBeanDefinitionDocumentReader实例方法:protected void doRegisterBeanDefinitions(Element root)

此方法负责初始化delegate,校验处理profile,并调用方法来处理DOM对象,代码简化如下:

    protected void doRegisterBeanDefinitions(Element root) {
        // <beans>元素的嵌套会导致递归调用,为保证<beans>元素的default-*属性能正确传导,因此对于每个子元素的delegate,
        // 均记录父delegate以便fallback和回溯
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // We cannot use Profiles.of(...) since profile expressions are not supported
                // in XML config. See SPR-12458 for details.
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

处理过程一共5步:

  1. 创建delegate并与父delegate合并
  2. 校验profile
  3. DOM对象前处理:preProcessXml(root)
  4. DOM对象解析成BeanDefinition:parseBeanDefinitions(root, this.delegate);
  5. DOM对象后处理:preProcessXml(root)

在DefaultBeanDefinitionDocumentReader对象中DOM对象前处理和后处理方法均为空方法体

# 1.2.2.1.1 创建delegate

调用createDelegate(getReaderContext(), root, parent)方法来创建解析代理,此方法首先初始化BeanDefinitionParserDelegate实例,并将readerContext传入,然后将一些beans元素的默认属性从父节点扩散到当前节点,这些属性列举如下:

    @Nullable
    private String lazyInit;

    @Nullable
    private String merge;

    @Nullable
    private String autowire;

    @Nullable
    private String autowireCandidates;

    @Nullable
    private String initMethod;

    @Nullable
    private String destroyMethod;

    @Nullable
    private Object source;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1.2.2.1.2 校验profile

如果当前处理的是默认命名空间,并且根元素有定义profile属性,则判断运行时参数"spring.profiles.active"是否包含当前的profile,如果不包含,则记录并返回,不进行下一步操作

# 1.2.2.1.3 DOM对象解析成BeanDefinition:preProcessXml(root)
    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);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在此方法中,沿传入的root节点遍历DOM树,对于每个节点,判断namespace,如果是Spring的默认命名空间,则沿树向下遍历到类型为Element的叶子节点,调用delegate的parseDefaultElement(ele,delegate)方法,如果某个节点namespace非默认,则调用delegate的parseCustomElement(ele)方法

# 1.2.2.3.4 parseDefaultElement(ele,delegate)方法
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

通过判断DOM节点的name或localname,比对预定义的三种bean类型(import,alias,bean)来调用对应的process方法,如果发现是嵌套的<beans>对象,则递归调用doRegisterBeanDefinitions(ele)方法。

对于当前实例,xml定义了一个<bean>节点,因此调用processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法,此方法的具体加载过程参见第四节

所有节点parse完毕后,由于处理完毕后没有操作,因此,将当前reader的delegate回溯到parent,如此循环到整个DOM树处理完毕,此时,当前Resource处理完毕,计算Resource处理前后beanDefinition数量的变化,返回变化值,此时整个XmlBeanDefinitionReader的loadBeanDefinitions方法执行完成

Last Updated: 1/22/2024, 8:56:53 AM