前言
资源是一个抽象的概念,什么是资源?我们已知Spring中有很多xml配置文件,同时还可能自建各种properties资源文件,还有可能进行网络交互,收发各种文件、二进制流等。
资源粗略的可以分为(这里以Spring的分类为例):
- URL资源
- File资源
- ClassPath相关资源
- 服务器相关资源(JBoss AS 5.x上的VFS资源)
JDK操纵底层资源基本就是java.net.URL 、java.io.File 、java.util.Properties这些:取资源基本是根据绝对路径或当前类的相对路径来取。从类路径或Web容器上下文中获取资源的时候也不方便。若直接使用这些方法,需要编写比较多的额外代码,例如前期文件存在判断、相对路径变绝对路径等。
而Spring提供的Resource接口提供了更强大的访问底层资源的能力。
JDK提供的资源访问简单案例
Class类提供的获取资源的方法
它有两个获取资源的方法:
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); } public InputStream getResourceAsStream(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. return ClassLoader.getSystemResourceAsStream(name); } return cl.getResourceAsStream(name); } private String resolveName(String name) { if (name == null) { return name; } if (!name.startsWith("/")) { Class<?> c = this; while (c.isArray()) { c = c.getComponentType(); } String baseName = c.getName(); int index = baseName.lastIndexOf('.'); if (index != -1) { name = baseName.substring(0, index).replace('.', '/') +"/"+name; } } else { name = name.substring(1); } return name; }
我们发现它采用的是当前的ClassLoader,并且是当前类的路径相关的,也是支持以/开头的绝对路径的。
说明,下面的例子基于:spring.properties在类路径下(也就是maven工程的resources目录下) demo.properties位于maven工程的java目录下,包名为:com/fsx/maintest/demo.properties
Demo
public static void main(String[] args) { // 此处用相对路径,那就是相对Main所在的路径。因此此处需要demo.properties和Main.class文件在同一个包里面 否则请用对应的../../等 // 这里Main所在包为:com.fsx.maintest 因此最终找的文件地址为:com/fsx/maintest/demo.properties 会去这里找文件 URL resource = Main.class.getResource("demo.properties"); System.out.println(resource); //file:/E:/work/remotegitcheckoutproject/myprojects/java/demo-war/target/classes/com/fsx/maintest/demo.properties // 若采用绝对路径 /就代表当前项目名~~~~ 所以此处的效果同上~~~ resource = Main.class.getResource("/com/fsx/maintest/demo.properties"); System.out.println(resource); // 同上 // 关于getResourceAsStream的使用,路径处理上和上面一致,此处就不做过多解释了 InputStream resourceAsStream = Main.class.getResourceAsStream("demo.properties"); System.out.println(resourceAsStream); //java.io.BufferedInputStream@33e5ccce // ==================最后如果你想直接加载Classpath类路径下的配置文件(此处以类路径下的spring.properties为例)=================== // 这个/ 不能省略,否则classpathResource为null URL classpathResource = Main.class.getResource("/spring.properties"); System.out.println(classpathResource); //file:/E:/work/remotegitcheckoutproject/myprojects/java/demo-war/target/classes/spring.properties System.out.println(Main.class.getResource("")); //.../demo-war/target/classes/com/fsx/maintest/ 它定位到的是Main这个类所在的路径 System.out.println(Main.class.getResource("/")); //.../demo-war/target/classes/ 它定位到的是类路径 }
ClassLoader提供的获取资源
getSystemResource和getSystemResourceAsStream
这种方式也是我们平时非常常用的
public static void main(String[] args) { // 这个大体上和class.getResource()类似。但是它没有class对路径的一个提前处理。所以它这里需要把路径写全了:com/fsx/maintest/demo.properties // 需要注意的是,因为它没有对路径处理的,所以不支持 `/`打头的这种绝对路径 URL systemResource = ClassLoader.getSystemResource("com/fsx/maintest/demo.properties"); System.out.println(systemResource); ///demo-war/target/classes/com/fsx/maintest/demo.properties //后面已经有一个/了,说明就是直接从classpath/目录下查找~~~~ 所以我们自己不需要再写/ 了 systemResource = ClassLoader.getSystemResource(""); System.out.println(systemResource); // .../demo-war/target/classes/ 获取类路径的地址 InputStream systemResourceAsStream = ClassLoader.getSystemResourceAsStream("com/fsx/maintest/demo.properties"); System.out.println(systemResourceAsStream); //java.io.BufferedInputStream@46f5f779 // 若要加载类路径下的文件,显然这个就更加的方便些~~~ 不需要 "/"了 System.out.println(ClassLoader.getSystemResourceAsStream("spring.properties")); //java.io.BufferedInputStream@5a42bbf4 }
备注像getSystemResources或者getResources相当于不仅在本类加载器中找,还会去父加载器中去找~~~~(一般目前而言,我们使用不着)
需要注意的是:把java项目打包成jar包,如果jar包中存在资源文件需要访问,必须采取stream的形式访问。可以调用getResourceAsStream()方法,而不能采用路径的方式访问(文件已经被打到jar里面了,不符合路径的)。
另外大多数情况下classLoader.getResources和ClassLoader.getSystemResources方法是等价的。比如下面两种方式是等价的:
// 实例方法 classLoader.getResources(META-INF/spring.factories) // 静态方法 ClassLoader.getSystemResources(META-INF/spring.factories)
他俩的输出结果一样
File方式读取
public static void main(String[] args) throws FileNotFoundException { // 显然通过这种间接的方式去构建一个File对象也是可行的 只是比较绕 String filePath = ClassLoader.getSystemResource("spring.properties").getFile(); System.out.println(new File(filePath).exists()); //true }
new File()路径名的两种方式
全路径名(带盘符的):
public static void main(String[] args) { // 使用带盘符的绝对路径 显然这个把项目名以及target都暴露出来了 File file = new File("E:\\work\\remotegitcheckoutproject\\myprojects\\java\\demo-war\\target\\classes\\com\\fsx\\maintest\\demo.properties"); System.out.println(file.exists()); //true 这里是返回true,表示找到了这个文件 // 另外一种方式,也可以这么写 file = new File(System.getProperty("user.dir") + "\\target\\classes\\com\\fsx\\maintest\\demo.properties"); System.out.println(file.exists()); // true }
相对路径名:
File的相对路径,请务必注意。它是相对于项目名的。也就是说和项目名平级的才能直接获取。
这个my.properties显然是是项目外层,和项目平级的,我们就可以这样直接获取:
public static void main(String[] args) { // 采用相对路径 很显然,这里相对的是工程~~~ File file = new File("my.properties"); System.out.println(file.exists()); //true }
那么如果我们想要采用相对路径去获取工程内部的资源呢?
public static void main(String[] args) { // 采用相对路径 很显然,这里相对的是工程~~~(这里也得把/demo-war/target/classes这些写出来,非常不优雅) File file = new File("../demo-war/target/classes/com/fsx/maintest/demo.properties"); System.out.println(file.exists()); //true }
个人建议:一般都不太建议直接使用File,或者不建议直接new File()
JDK加载资源注意
不管是类对象的getResource()还是类加载器的getSystemResouce(),都是走的类加载器的getResource(),类加载器会搜索自己的加载路径来匹配寻找项。而最常用的类加载器就是AppClassLoader,又因为APPClassLoader的加载路径是classpath,所以网上文章一般都会说getClass().getResouce()是返回classpath,这是不够准确的。
整体来说,JDK提供的一些获取资源的方式,还是比较难用的。如果你处在Spring环境中,强烈建议使用它提供的资源访问接口,下面着重介绍
Spring提供的资源访问 Resource接口
它位于的包为org.springframework.core.io,属于Spring Framework的核心内容
可能很多用了Spring多年的程序员对Resource都了解有限,毕竟访问资源一般是搭建web工程框架的时候的事情。不过了解它也是非常有好处的。特别是你自己想基于Spring构建自己的框架的时候,就显得特别的有必要了~
public interface Resource extends InputStreamSource { //返回Resource所指向的底层资源是否存在 boolean exists(); //返回当前Resource代表的底层资源是否可读 default boolean isReadable() { return true; } //返回Resource资源文件是否已经打开,**如果返回true,则只能被读取一次然后关闭以避免内存泄漏;**常见的Resource实现一般返回false default boolean isOpen() { return false; } //@since 5.0 参见:getFile() default boolean isFile() { return false; } //如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IO异常 URL getURL() throws IOException; //如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IO异常 URI getURI() throws IOException; //如果当前Resource代表的底层资源能由java.io.File代表,则返回该File,否则抛出IO异常 File getFile() throws IOException; //@since 5.0 用到了nio得Channel相关的 default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } // 返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度 long contentLength() throws IOException; //返回当前Resource代表的底层资源的最后修改时间 long lastModified() throws IOException; // 用于创建相对于当前Resource代表的底层资源的资源 // 比如当前Resource代表文件资源“d:/test/”则createRelative(“test.txt”)将返回表文件资源“d:/test/test.txt”Resource资源。 Resource createRelative(String relativePath) throws IOException; //返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://d:/test.txt”将返回“d:/test.txt”,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径。 @Nullable String getFilename(); //返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址) String getDescription(); }
Resouce接口并不是一个根接口,它继承了一个简单的父接口 InputStreamSource,它只提供一个方法用以返回一个输入流:
InputStream getInputStream() throws IOException;
它还有如下几大分支:
文末会解释一下EncodedResource,至于Web中的MultipartFile,在Spring MVC相关篇章中会着重解释