正确使用Java获取资源文件

正确使用Java获取资源文件

java中获取文件时,经常会因为搞不懂路径该怎么写,而导致出现各种获取文件失败的问题。首先对常见获取文件方式进行归类:

第一种,使用Java提供的IO类File、Path获取文件:

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Demo {
    
    
    // 例如 idea下存在resources/test文件
    public static void main(String[] args) {
    
    
        // 使用File或Path进行获取文件时,主要收到相对路径和绝对路径的影响
        String path = "src/main/resources/test";
        File file = new File(path);
        boolean exists = file.exists();
        System.out.println(exists);
        
        
        exists = Files.exists(Paths.get(path));
        System.out.println(exists);
    }
}

使用相对路径进行获取文件时,以System.getProperty("user.dir")路径为起始路径,这样会存在idea与服务器运行时不一致问题。

第二种,使用ClassLoader获取资源文件:

import java.io.File;
import java.io.InputStream;
import java.net.URL;

public class Demo {
    
    
    // 例如 idea下存在resources/test文件
    public static void main(String[] args) {
    
    
        String path = "test";

        // 1 Class获取
        URL classResource = Demo.class.getResource("/test");   // URL
        InputStream classStream = Demo.class.getResourceAsStream("/test"); // InputStream
        File classFile = new File(classResource.getPath());

        // 2 ClassLoader获取
        URL clResource = Demo.class.getClassLoader().getResource("test"); // URL
        InputStream clStream = Demo.class.getClassLoader().getResourceAsStream("test");   // InputStream
        File clFile = new File(clResource.getPath());

    }
}

需要注意的是,使用ClassLoader相对路径获取时,起始路径是classpath(也约等于resources目录下),而使用class相对路径获取时,起始目录则是当前类的目录,如com.zsl0.Demo,使用Demo.class.getResource("test")时,是获取com/zsl0/test文件,这则会导致获取resources/test文件失败,而绝对路径则都是从classpath开始。

因此建议获取resources目录下的资源文件时,使用当前类.class.getClassLoader().getResourceAsStream()方法进行获取资源文件。

为什么class.getResource()方法获取的相对路径是从当前类开始的:

public final class Class<T> implements java.io.Serializable,
        GenericDeclaration,
        Type,
        AnnotatedElement {
    
    
    // 省略...
    
    public java.net.URL getResource(String name) {
    
    
        name = resolveName(name); // <--- 将相对路径转化为当前类路径作为起始目录
        ClassLoader cl = getClassLoader0();
        if (cl == null) {
    
    
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }
}

使用当前类的类加载器获取系统类加载器都是相同的,Demo.class.getClassLoader().getResource()等同于ClassLoader.getSystemResource()

public abstract class ClassLoader {
    
    
    public URL getResource(String name) {
    
    
        URL url;
        if (parent != null) {
    
    
            url = parent.getResource(name); // 1. 这里会获取到上级的类加载
        } else {
    
    
            url = getBootstrapResource(name); // 2. 最终会执行到这里
        }
        if (url == null) {
    
    
            url = findResource(name);
        }
        return url;
    }
}

学过Java的类加载器应该知道,默认的三种类加载是继承关系(Bootstrap ClassLoader引导类加载器、Extension ClassLoader扩展类加载器、Application ClassLoader应用类加载器、Custom ClassLoader自定义类加载器),上面代码parent.getResource(name),也就是实现双亲委派机制的源头。

通过Debug得出最终会使用sun.misc.Launcher$ExtClassLoader进行加载资源。

总结:

  • 获取本地磁盘路径时使用new File()
  • 获取项目资源文件时使用当前类.class.getClassLoader().getResourceAsStream()或者ClassLoader.getSystemResourceAsStream()

SpringBoot获取资源扩展

SpringBoot提供工具ResourceUtils.getURL("classpath:test")获取资源文件,当携带classpath:前缀时,使用类加载器获取,其它情况使用URL获取:

public abstract class ResourceUtils {
    
    

    /** Pseudo URL prefix for loading from the class path: "classpath:". */
    public static final String CLASSPATH_URL_PREFIX = "classpath:";
    
    // 省略其它代码...

    public static URL getURL(String resourceLocation) throws FileNotFoundException {
    
    
        Assert.notNull(resourceLocation, "Resource location must not be null");
        if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
    
            // 携带classpath:时进来
            String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
            ClassLoader cl = ClassUtils.getDefaultClassLoader();
            URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));    // 使用类加载获取资源文件
            if (url == null) {
    
    
                String description = "class path resource [" + path + "]";
                throw new FileNotFoundException(description +
                        " cannot be resolved to URL because it does not exist");
            }
            return url;
        }
        try {
    
       // 其它情况
            // try URL
            return new URL(resourceLocation);
        } catch (MalformedURLException ex) {
    
    
            // no URL -> treat as file path
            try {
    
    
                return new File(resourceLocation).toURI().toURL();
            } catch (MalformedURLException ex2) {
    
    
                throw new FileNotFoundException("Resource location [" + resourceLocation +
                        "] is neither a URL not a well-formed file path");
            }
        }
    }
}

从上面可以看到携带classpath:时,会使用类加载器获取资源,否则使用URL(全局资源定位符,主要由Protocol协议、Destination目的地构成)获取。

猜你喜欢

转载自blog.csdn.net/qq_19152901/article/details/130531716