美文网首页
Spring 配置资源文件的加载过程

Spring 配置资源文件的加载过程

作者: 农民工进城 | 来源:发表于2019-09-26 12:08 被阅读0次

本章重点

  • ant方式的资源定位
  • 加载XML文件
  • 解析XML文件

1. 资源定位

org.springframework.core.io.Resource 为 Spring 框架所有资源的抽象和封装,它继承 org.springframework.core.io.InputStreamSource接口。Spring装载bean的过程也就是:xml---->Resource---->解析xml---->注册bean。

常见的代码片段有如下几种:

  • 单个文件加载:
        ClassPathResource resource = new ClassPathResource("mybean.xml"); // 获取配置资源
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // 实例化BeanFactory
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // 实例化资源解析器
        reader.loadBeanDefinitions(resource); // 加载资源
        MyBean myBean = (MyBean) factory.getBean("mybean");//获取自己定义的bean
  • 多个文件加载
PathMatchingResourcePatternResolver patternResolver=new PathMatchingResourcePatternResolver();
            Resource[]  resources=patternResolver.getResources("classpath*:*.xml");//后面会讲为啥不是classpath:*.xml
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // <2>
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // <3>
            reader.loadBeanDefinitions(resources);

            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mybean.xml");
            MyBean myBean = (MyBean) applicationContext.getBean("mybean");

单个文件容易理解,主要看下多个文件是如何定位追踪的。

路径 跟路径 子路径
classpath:config/spring/spring-*.xml classpath*:config spring/spring-.xml
classpath:config/springmvc/spring-.xml classpath*:config/springmvc spring-*.xml

Spring 源码实现过程其实就是两个函数的递归调用:


public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {// 1)常量,其实就是  "classpath*:";
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {// 2)匹配器是AntPathMatcher;判断是否含有*和?通配符
                return findPathMatchingResources(locationPattern);
            }
            else {
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));//3) 返回该目录下的所有文件,封装为Resource数组
            }
        }
        else {
            int prefixEnd = locationPattern.indexOf(":") + 1;
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                return findPathMatchingResources(locationPattern);
            }
            else {
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }
  • 1)常量CLASSPATH_ALL_URL_PREFIX,固定值 "classpath*:";
  • 2)getPathMatcher()返回AntPathMatcher,isPattern判断字符串中是否含有 *和?通配符
  • 3) findAllClassPathResources 返回该目录下的所有文件,并封装为Resource数组
    获取匹配路径的配置文件:
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);//递归调用
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));// 1)虚拟文件系统里的文件
            }
            else if (isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));// 2) jar包里的文件
            }
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));// 3)拿到所有匹配的路径表达式的文件
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }
  • 1 ) 加载虚拟文件系统里的配置文件
  • 2 ) 拿到jar包里的所有匹配的路径表达式的文件
  • 3 ) doFindPathMatchingFileResources拿到当前根目录下所有匹配的路径表达式的文件

2.加载XML文件

加载XML文件:

     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));//1)
    }
    1. loadBeanDefinitions 执行加载bean操作
          // 1)当前线程,正在加载Resource的集合
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");

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);
        }
        if (!currentResources.add(encodedResource)) {// 2)将正在加载的文件放入缓存中
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream(); //3)获取文件字节流
            try {
                InputSource inputSource = new InputSource(inputStream);//4)封装
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());// 5)设置字符集
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//6)加载 BeanDefinition,真正加载配置文件的方法
            }
            finally {
                inputStream.close();// 7)关闭流
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove(); 8)处理完移除
            }
        }
    }


  • 1)当前线程,正在加载Resource的集合
  • 2)将正在加载的文件放入缓存中,放入失败则报异常
  • 3)获取需要加载文件的字节流
  • 4)将字节流封装称为InputSource
  • 5)设置字符集
  • 6)加载 BeanDefinition,真正加载配置文件的方法
  • 7)关闭流
  • 8)移除处理完Resource
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource); //1) 获取Document对象
            return registerBeanDefinitions(doc, resource);// 2) 注册bean
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }
  • 1)调用 #doLoadDocument(InputSource inputSource, Resource resource) 方法,根据 xml 文件,获取 Document 实例。
  • 2)调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//1)解析xml并注册bean
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
  • 1)createBeanDefinitionDocumentReader,实例化 BeanDefinitionDocumentReader 对象
  • 2)调用 createReaderContext(Resource resource) 方法,创建 XmlReaderContext 对象。
  • 3)调用 BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,解析xml并注册bean
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();//1)获取xml根节点
        doRegisterBeanDefinitions(root);
    }
  • 1)获取xml根节点
  • 2)执行注册bean
protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);//1)创建BeanDefinitionParserDelegate

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);//2)获取激活环境
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        preProcessXml(root);//3)解析xml前的处理,没有进行任何逻辑处理
        parseBeanDefinitions(root, this.delegate);//4)解析xml
        postProcessXml(root);//5)解析xml后的处理,没有进行任何逻辑处理

        this.delegate = parent;
    }
  • 1)创建BeanDefinitionParserDelegate,并解析beans标签下的一些默认配置
  • 2)获取自定义激活的环境
  • 3)、5)这两处没有进行任何处理,开发者可以自由扩展
  • 4) 真正解析xml

3.解析XML

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {// 1)
            NodeList nl = root.getChildNodes();// 2)
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {//3)
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);//(4 解析
                    }
                    else {
                        delegate.parseCustomElement(ele);//5)
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
  • 1)判断节点是否是默认namespace,http://www.springframework.org/schema/beans
  • 2)获取子节点
  • 3)只处理带标签的元素节点,过滤掉注释和文本节点等
  • 4)解析import、alias、bean、beans等标签,并且注册到bean缓存中
  • 5)解析其他标签,并且注册到bean缓存中

相关文章

  • 006.资源文件properties的配置

    Spring简化了加载资源文件的配置,可以通过

  • Spring 配置资源文件的加载过程

    本章重点 ant方式的资源定位 加载XML文件 解析XML文件 1. 资源定位 org.springframewo...

  • Spring容器初始化过程简析

    1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源...

  • spring 整合web项目原理

    1、加载spring配置文件 new对象,功能可以实现,效率低 2、实现思想:把加载配置文件和创建对象过程,在服务...

  • Spring整合web项目原理

    1 加载spring核心配置文件 (1)new对象,功能可以实现,效率很低2实现思想把加载配置文件和创建对象的过程...

  • J2EE进阶学习——Spring框架(三):Spring整合we

    1.加载Spring核心配置文件 new对象,功能可以实现,效率很低 2.实现思想:把加载配置文件和创建对象过程,...

  • Spring常用接口

    ApplicationContextAware接口: 加载Spring配置文件时,如果Spring配置文件中所定义...

  • spring构建bean

    源码分析基于spring 4.3.x 前面已经描述了spring加载配置文件的过程,现在来分析一下spring构建...

  • Spring学习-1

    一:Spring中配置文件的加载原理: 二:常用的Spring配置文件的加载方式: 有三种分别是: 1:使用我们当...

  • spring加载配置

    源码分析基于spring 4.3.x 本文通过阅读源码,分析spring加载配置文件的过程 小栗子 bean类 s...

网友评论

      本文标题:Spring 配置资源文件的加载过程

      本文链接:https://www.haomeiwen.com/subject/sjbkyctx.html