美文网首页
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缓存中

    相关文章

      网友评论

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

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