Spring中的资源及资源定位

作者: Coding小聪 | 来源:发表于2019-05-05 00:32 被阅读31次

    Java中的资源

    在Java语言中,使用URL来表示不同来源的资源,URL可以根据不同的协议定义不同类型的资源,常见资源类型有(URL支持的协议):

    • file:
    • http/https:
    • jar:

    资源通过URL定义好了之后,如何对不同类型的资源进行解析呢?可以通过注册不同的handler实现,这里的handler指的是java.net.URLStreamHandler。在URL的API中有个重要的获取Connection的方法:URL#openConnection(),而该方法的实现又委托给URLStreamHandler#openCon nection。而URLStreamHandler是个抽象类,恰好其openConnection方法又是抽象方法,即留给子类自行实现。

    在JDK中,URLStreamHandler的子类如下图所示:

    JDK中URLStreamHandler的子类

    classpath

    在日常编程中,我们会常遇到classpath,但classpath究竟指的是哪个目录呢?
    无论是maven项目还是普通的java工程都会有个src目录,这个目录中存放着项目的java文件和配置文件。web项目src目录下的文件在编译后会放到WEB-INF/classes,非web项目src目录下的文件在编译后会放到classes/。classpath所对应的路径指的就是WEB-INF/classes(或者classes/)

    classpath 和 classpath* 区别:
    classpath:只会到你的class路径中查找找文件;
    classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找.

    用classpath*:需要遍历所有的classpath,所以加载速度是很慢的,因此,在规划的时候,应该尽可能规划好资源文件所在的路径,尽量避免使用 classpath*

    Java读取资源文件

    在讲资源文件的读取之前,我们先来看看绝对路径和相对路径。

    在读取某个文件时,我们常会遇到FileNotFoundException,然而这个文件明明是存在的,导致这个异常的原因往往是由于路径定位得不对。在Java编程中我们可以使用以下两种方式读取资源:

    • 使用java.lang.Class#getResourceAsStream()
    • 使用java.lang.ClassLoader#getResourceAsStream()

    使用Class进行资源加载,支持相对路径和绝对路径。相对路径以当前类所在路径为参考点;绝对路径以"/"开头,classpath为根目录。
    使用ClassLoader进行资源加载,不支持相对路径,都是以classpath为根目录的绝对路径,需要注意的一点是千万不能以"/"开头。
    下面看看例子:

    public class ResourceReadDemo {
        public static void main(String[] args) {
            ResourceReadDemo resourceRead = new ResourceReadDemo();
            resourceRead.readFromRelativePathByClass();
            resourceRead.readFromAbsolutePathByClass();
            // classloader加载资源以classpath为相对路径,且不能以"/"开头
            resourceRead.readByClassLoader();
        }
    
        private void readFromRelativePathByClass(){
            try (InputStream is = this.getClass().getResourceAsStream("resourceDemo.properties");) {
                Properties prop = new Properties();
                prop.load(is);
                // output : package
                System.out.println(prop.getProperty("key"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private void readFromAbsolutePathByClass(){
            try (InputStream is = this.getClass().getResourceAsStream("/resourceDemo.properties");) {
                Properties prop = new Properties();
                prop.load(is);
                // output : resources
                System.out.println(prop.getProperty("key"));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            try (InputStream is = this.getClass().getResourceAsStream("/io/zgc/spring/features/resource/resourceDemo.properties");) {
                Properties prop = new Properties();
                prop.load(is);
                // output : package
                System.out.println(prop.getProperty("key"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private void readByClassLoader() {
            try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("resourceDemo.properties");) {
                Properties prop = new Properties();
                prop.load(is);
                // output : resources
                System.out.println(prop.getProperty("key"));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("io/zgc/spring/features/resource/resourceDemo.properties");) {
                Properties prop = new Properties();
                prop.load(is);
                // output : package
                System.out.println(prop.getProperty("key"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    Spring中的资源

    Spring对资源单独进行了抽象表示,而没有直接使用URL,原因如下:

    1. URL没有定义Classpath或ServletContext资源(这点可以通过扩展URL并定义相应的handler来解决);
    2. URL没有提供资源是否存在、当前资源是否可读等基本判断方法。

    故此,Spring将资源对象抽象成Resource,Spring会将配置文件都封装成Resource,它的接口定义如下:

    public interface Resource extends InputStreamSource {
    
        /**
         * 资源是否存在.
         */
        boolean exists();
    
        /**
         * 资源是否可读
         */
        default boolean isReadable() {
            return exists();
        }
    
        /**
         * 资源是否处于打开状态
         */
        default boolean isOpen() {
            return false;
        }
    
        /**
         * 是否为文件资源
         */
        default boolean isFile() {
            return false;
        }
    
        /**
         * 获取资源对应的URL
         */
        URL getURL() throws IOException;
    
        /**
         * 获取URI
         */
        URI getURI() throws IOException;
    
        /**
         * 转换成文件
         */
        File getFile() throws IOException;
    
        /**
         * 返回一个读的channel
         */
        default ReadableByteChannel readableChannel() throws IOException {
            return Channels.newChannel(getInputStream());
        }
    
        /**
         * 资源内容的长度
         */
        long contentLength() throws IOException;
    
        /**
         * 最后修改的时间
         */
        long lastModified() throws IOException;
    
        /**
         * 创建相对位置的资源
         */
        Resource createRelative(String relativePath) throws IOException;
    
        /**
         * 获取文件名称
         */
        @Nullable
        String getFilename();
    
        /**
         * 获取资源描述符
         */
        String getDescription();
    
    }
    

    Resource的部分继承类图

    Spring中资源的定位

    在Spring中,使用ResourceLoader对资源进行定位、加载,主要作用为:根据给定的资源文件地址返回对应的Resource。

    public interface ResourceLoader {
    
        /** Pseudo URL prefix for loading from the class path: "classpath:" */
        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    
    
        /**
         * 通过资源路径返回Resource对象
         */
        Resource getResource(String location);
    
        ClassLoader getClassLoader();
    
    }
    

    作为 Spring 统一的资源加载器,ResourceLoader提供了统一的抽象,具体的实现则由相应的子类来负责实现,其类的部分类结构图如下:

    ResourceLoader部分继承类图

    DefaultResourceLoader

    org.springframework.core.io.DefaultResourceLoaderResourceLoader的默认实现,并且实现了资源加载器中重要的getResource(String)方法,源码如下:

    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        //通过ProtocolResolver加载资源
        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }
    
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }
    
    protected Resource getResourceByPath(String path) {
        return new ClassPathContextResource(path, getClassLoader());
    }
    

    可以看到默认的资源加载逻辑:

    1. 通过自定义的ProtocolResolver加载资源;
    2. 如果资源路径以"/"开头,将资源构造成ClassPathContextResource
    3. 如果资源路径以"classpath:"开头,将资源构造成ClassPathResource
    4. 资源路径既不以"/"开头又不以"classpath:"开头,则将资源构造成UrlResource。

    ProtocolResolver接口是资源加载的一个扩展点,允许用户自定义资源加载策略。Spring默认没有提供实现类,需要用户自行实现,并调用org.springframework.core.io.DefaultResourceLoader#addProtocolResolver进行添加。

    ResourcePatternResolver

    ResourceLoader#getResource(String location)仅支持单个资源的加载,ResourcePatternResolver为我们提供了一次加载多个资源的功能。

    public interface ResourcePatternResolver extends ResourceLoader {
    
        String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    
        Resource[] getResources(String locationPattern) throws IOException;
    
    }
    

    ResourcePatternResolver支持Ant模式通配符匹配,具体规则为:

    “?”:匹配一个字符,如“config?.xml”将匹配“configA.xml”;
    “*”:匹配零个或多个字符串,如“io/*/config.xml”将匹配“io/zgc/config.xml”,但不匹配匹配“io/config.xml”;而“io/config-*.xml”将匹配“io/config-dao.xml”;
    “**”:匹配路径中的零个或多个目录,如“io/**/config.xml”将匹配“io/config.xml”,也匹配“io/zgc/spring/config.xml”;而“io/zgc/config-**.xml”将匹配“io/zgc/config-dao.xml”,即把“**”当做两个“*”处理。

    PathMatchingResourcePatternResolverResourcePatternResolver最常用的一个子类,它加载资源的方法如下所示:

    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        //是否以"classpath*:"开头
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // 字符串中是否含有"*"或者"?"
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // a class path resource pattern
                return findPathMatchingResources(locationPattern);
            } else {
                // all class path resources with the given name
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        } else {
            // Generally only look for a pattern after a prefix here,
            // and on Tomcat only after the "*/" separator for its "war:" protocol.
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
                    locationPattern.indexOf(':') + 1);
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            } else {
                // 根据ResourceLoader加载资源
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }
    

    参考(扩展阅读)

    【死磕 Spring】—– IOC 之 Spring 统一资源加载策略

    【第四章】 资源 之 4.4 Resource通配符路径 ——跟我学spring3

    相关文章

      网友评论

        本文标题:Spring中的资源及资源定位

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