【小家Spring】资源访问利器---Spring提供的Resource接口以及它的常用子类源码分析

前言

资源是一个抽象的概念,什么是资源?我们已知Spring中有很多xml配置文件,同时还可能自建各种properties资源文件,还有可能进行网络交互,收发各种文件、二进制流等。

资源粗略的可以分为(这里以Spring的分类为例):

  1. URL资源
  2. File资源
  3. ClassPath相关资源
  4. 服务器相关资源(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.getResourcesClassLoader.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相关篇章中会着重解释

子接口ContextResource和WritableResource

这两个接口继承于Resource,拥有Resource的全部方法。其中,ContextResource接口增加了一个方法:

	String getPathWithinContext(); //  返回上下文内的路径  

这个方法使得它的实现类有了返回当前上下文路径的能力。

WritableResource接口增加了2个方法:

    boolean isWritable();  //  是否可写
    OutputStream getOutputStream() throws IOException; //返回资源的写入流

这个方法使得它的实现类拥有了写资源的能力

可以看到Spring为我们提供了非常多的实现类。

  • ByteArrayResource
  • InputStreamResource
  • FileSystemResource
  • UrlResource
  • ClassPathResource
  • ServletContextResource
  • VfsResource(这个和Jboss有关,本文不讨论)

抽象类AbstractResource

对于任何的接口而言,这个直接抽象类是重中之重,里面浓缩了接口的大部分公共实现,所以这里直接拿源码开刀:

public abstract class AbstractResource implements Resource {

	// File或者流  都从此处判断
	// 这里属于通用实现,子类大都会重写这个方法的~~~~~~
	@Override
	public boolean exists() {
		// Try file existence: can we find the file in the file system?
		try {
			return getFile().exists();
		} catch (IOException ex) {
			// Fall back to stream existence: can we open the stream?
			try {
				InputStream is = getInputStream();
				is.close();
				return true;
			} catch (Throwable isEx) {
				return false;
			}
		}
	}

	// 默认都是可读得。大多数子类都会复写
	@Override
	public boolean isReadable() {
		return true;
	}
	// 默认不是打开的。 比如InputStreamResource就会让他return true
	@Override
	public boolean isOpen() {
		return false;
	}
	// 默认不是一个File
	@Override
	public boolean isFile() {
		return false;
	}

	// 可议看到getURI方法一般都是依赖于getURL的
	@Override
	public URL getURL() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}
	@Override
	public URI getURI() throws IOException {
		URL url = getURL();
		try {
			return ResourceUtils.toURI(url);
		} catch (URISyntaxException ex) {
			throw new NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	@Override
	public File getFile() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}
	// 调用此方法,也相当于吧流的read了一遍,请务必注意
	@Override
	public long contentLength() throws IOException {
		InputStream is = getInputStream();
		try {
			long size = 0;
			byte[] buf = new byte[255];
			int read;
			while ((read = is.read(buf)) != -1) {
				size += read;
			}
			return size;
		} finally {
			try {
				is.close();
			} catch (IOException ex) {
			}
		}
	}
	@Override
	public long lastModified() throws IOException {
		long lastModified = getFileForLastModifiedCheck().lastModified();
		if (lastModified == 0L) {
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for resolving its last-modified timestamp");
		}
		return lastModified;
	}
	// 只有一个子类:`AbstractFileResolvingResource`覆盖了此方法
	protected File getFileForLastModifiedCheck() throws IOException {
		return getFile();
	}
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}
	@Override
	@Nullable
	public String getFilename() {
		return null;
	}
	// 这些基础方法,很多子类也都有重写~~~~ 但是一般来说关系不大
	@Override
	public String toString() {
		return getDescription();
	}
	// 比较的就是getDescription()
	@Override
	public boolean equals(Object obj) {
		return (obj == this ||
			(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
	}
	// getDescription()的hashCode
	@Override
	public int hashCode() {
		return getDescription().hashCode();
	}

}

那么接下来就以AbstractResource为主要分支,分析它的实现类们:

PathResource

它是基于@since 4.0,也是基于JDK7提供的java.nio.file.Path的。实现原理也非常的简单,更像是对java.nio.file.Path进行了包装(java.nio.file.Files

另外它还实现了WritableResource,证明它拥有对资源写的能力~~~

ByteArrayResource:获取字节数组封装的资源

ByteArrayResource代表byte[]数组资源,对于“getInputStream”操作将返回一个ByteArrayInputStream

// @since 1.2.3
public class ByteArrayResource extends AbstractResource {
	private final byte[] byteArray;
	private final String description;
	...	
	@Override
	public InputStream getInputStream() throws IOException {
		return new ByteArrayInputStream(this.byteArray);
	}
}

它可多次读取数组资源,即isOpen()永远返回false ByteArrayResource因为入参可以是byte[]类型,所以用途比较广泛,可以把从网络或者本地资源都转换为byte[]类型,然后用ByteArrayResource转化为资源

Demo:

    public static void main(String[] args) {
        Resource resource = new ByteArrayResource("Hello!Spring!你好!".getBytes());
        if (resource.exists()) {
            dumpStream(resource); //Hello!Spring!你好!
        }
    }

    // 这个其实可以把这个resource写到本地文件,本处就不麻烦了,直接sout输出看一看即可~~~~
    private static void dumpStream(Resource resource) {
        InputStream is = null;
        try {
            //1.获取文件资源
            is = resource.getInputStream();
            //2.读取资源
            byte[] descBytes = new byte[is.available()];
            is.read(descBytes);
            System.out.println(new String(descBytes));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //3.关闭资源
                is.close();
            } catch (IOException e) {
            }
        }
    }

TransformedResource是继承此类的一个扩展。在web中使用较多,实现非常简单,就是多了两个参数:filename和lastModified

FileSystemResource:通过文件系统获取资源

代表java.io.File资源,对于getInputStream操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可多次读取底层文件的字节流。

这个实现类就大名鼎鼎了。此类在Spring5以后,就使用NIO.2的API比如ReadableByteChannel等来操作读写了。提高了效率。这点就和PathResource特别的像了

public class FileSystemResource extends AbstractResource implements WritableResource {
	private final File file;
	private final String path;
	...
	// 使用了Files 基于JDK1.7了
	@Override
	public InputStream getInputStream() throws IOException {
		try {
			return Files.newInputStream(this.file.toPath());
		}
		catch (NoSuchFileException ex) {
			throw new FileNotFoundException(ex.getMessage());
		}
	}
	@Override
	public boolean isReadable() {
		return (this.file.canRead() && !this.file.isDirectory());
	}
	
	// 读写
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		try {
			return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
		}
		catch (NoSuchFileException ex) {
			throw new FileNotFoundException(ex.getMessage());
		}
	}
	@Override
	public WritableByteChannel writableChannel() throws IOException {
		return FileChannel.open(this.file.toPath(), StandardOpenOption.WRITE);
	}
}

它的工作主要是构建一个File对象出来,此处我就省略了~~~

InputStreamResource:获取输入流封装的资源

InputStreamResource代表java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true。

public class InputStreamResource extends AbstractResource {

	private final InputStream inputStream;
	private final String description;
	private boolean read = false;

	@Override
	public InputStream getInputStream() throws IOException, IllegalStateException {
		if (this.read) {
			throw new IllegalStateException("InputStream has already been read - " +
					"do not use InputStreamResource if a stream needs to be read multiple times");
		}
		this.read = true;
		return this.inputStream;
	}
}

Demo:

	// dumpStream方法请参照上文
    public static void main(String[] args) {
        ByteArrayInputStream bis = new ByteArrayInputStream("Hello World!".getBytes());
        Resource resource = new InputStreamResource(bis);
        if (resource.exists()) {
            dumpStream(resource); //Hello World!
        }
    }

DescriptiveResource

这个类更简单,仅仅一个不可变的描述字符串的包装

// @since 1.2.6
public class DescriptiveResource extends AbstractResource {
	@Override
	public InputStream getInputStream() throws IOException {
		throw new FileNotFoundException(
				getDescription() + " cannot be opened because it does not point to a readable resource");
	}
}

这个类实际只是对资源描述的定义,既不是可读,实际文件也是不存在的,其他Resource接口中的方法也并未实现。当一个方法需要你传递一个资源对象,但又不会在方法中真正读取该对象的时候,如果没有合适的资源对象作为参数,就创建一个 DescriptiveResource 资源做参数。比如ConfigurationClass就有使用~~~

另外BeanDefinitionResourceDescriptiveResource有点像。但它持有的是一个BeanDefinition,也不能对它进行读、写。一般也是占位用的


AbstractFileResolvingResource

它复写了AbstractResource大多数方法,是一个比较重要的分支。有不少非常好用的实现类

// @since 3.0
public abstract class AbstractFileResolvingResource extends AbstractResource {
}
UrlResource:通过URL地址获取资源

可以从网络里获取资源

    public static void main(String[] args) throws IOException {
        UrlResource resource = new UrlResource("http://www.springframework.org/schema/beans/spring-beans.xsd");
        if (resource.exists()) {
            File file = resource.getFile();
            System.out.println(file); //报错 java.io.FileNotFoundException: URL [xxx] cannot be resolved to absolute file path because it

            dumpStream(resource); //输出这个.xsd文件的所有的内容...
        }
    }
FileUrlResource
//@since 5.0.2 显然它出现得很晚。 并且还实现了WritableResource接口
public class FileUrlResource extends UrlResource implements WritableResource {
	@Nullable
	private volatile File file;
	
	public FileUrlResource(URL url) {
		super(url);
	}
	// 注意:若使用此构造函数,此处使用的是file协议  而UrlResource采用的是http协议,此处需注意
	// 若想http,请用上面构造。自己构造一个URL对象吧
	public FileUrlResource(String location) throws MalformedURLException {
		super(ResourceUtils.URL_PROTOCOL_FILE, location);
	}
	@Override
	public File getFile() throws IOException {
		File file = this.file;
		if (file != null) {
			return file;
		}
		file = super.getFile();
		this.file = file;
		return file;
	}

	@Override
	public boolean isWritable() {
		try {
			URL url = getURL();
			if (ResourceUtils.isFileURL(url)) {
				// Proceed with file system resolution
				File file = getFile();
				return (file.canWrite() && !file.isDirectory());
			}
			else {
				return true;
			}
		}
		catch (IOException ex) {
			return false;
		}
	}
	@Override
	public OutputStream getOutputStream() throws IOException {
		return Files.newOutputStream(getFile().toPath());
	}

	@Override
	public WritableByteChannel writableChannel() throws IOException {
		return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
	}
}

它提供了我们访问网络资源能像访问本地文件一样的能力~~~

    public static void main(String[] args) throws IOException {
        //FileUrlResource resource = new FileUrlResource("http://www.springframework.org/schema/beans/spring-beans.xsd");
        FileUrlResource resource = new FileUrlResource(new URL("http://www.springframework.org/schema/beans/spring-beans.xsd"));
        if (resource.exists()) {
            dumpStream(resource); //输出这个.xsd文件的所有的内容...
        }
    }
ClassPathResource:通过类路径获取资源文件

听这名字就知道,它是直接去读取类路径下的资源文件的。

其实它的底层都是依赖于我们上面说得clazz.getResourceAsStream或者classLoader.getResourceAsStream。掌握了上面之后,这个其实就非常简单了

public class ClassPathResource extends AbstractFileResolvingResource {
	private final String path;'
	@Nullable
	private ClassLoader classLoader;
	@Nullable
	private Class<?> clazz; // 它还可以自己指定clazz

	@Nullable
	public final ClassLoader getClassLoader() {
		return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
	}
	@Override
	public boolean exists() {
		return (resolveURL() != null);
	}
	// 这是它最重要的一个方法,依赖于JDK的实现嘛
	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}
	@Override
	public URL getURL() throws IOException {
		URL url = resolveURL();
		if (url == null) {
			throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
		}
		return url;
	}
	// 非常简单 直接解析path即可
	@Override
	@Nullable
	public String getFilename() {
		return StringUtils.getFilename(this.path);
	}
}

Demo:

    public static void main(String[] args) {
        //ClassPathResource resource = new ClassPathResource("spring.properties");

        // 如果是要获取指定类所在包下的文件,建议指定class
        ClassPathResource resource = new ClassPathResource("demo.properties",Main.class);
        if (resource.exists()) {
            dumpStream(resource); //name=fangshixiang  或者 demo=value
        }
    }
ServletContextResource:获取ServletContext环境下的资源

这个在web包里面。org.springframework.web.context.support

为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用程序根目录的路径加载资源,它支持以流和URL的方式访问,在WAR解包的情况下,也可以通过File的方式访问,还可以直接从JAR包中访问资源

public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {
	// 持有servletContext的引用
	private final ServletContext servletContext;
	private final String path;
	
	// 只提供这一个构造函数,来构造一个资源
	public ServletContextResource(ServletContext servletContext, String path) {
		// check ServletContext
		Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
		this.servletContext = servletContext;

		// check path
		Assert.notNull(path, "Path is required");
		String pathToUse = StringUtils.cleanPath(path);
		if (!pathToUse.startsWith("/")) {
			pathToUse = "/" + pathToUse;
		}
		this.path = pathToUse;
	}

	// 我们发现,它底层都是依赖于servletContext.getResource  getResourceAsStream这些方法去找到资源的
	@Override
	public boolean isFile() {
		try {
			URL url = this.servletContext.getResource(this.path);
			if (url != null && ResourceUtils.isFileURL(url)) {
				return true;
			}
			else {
				return (this.servletContext.getRealPath(this.path) != null);
			}
		}
		catch (MalformedURLException ex) {
			return false;
		}
	}
	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is = this.servletContext.getResourceAsStream(this.path);
		if (is == null) {
			throw new FileNotFoundException("Could not open " + getDescription());
		}
		return is;
	}
	// 这个有点意思。如果URL就是File类型。就ok
	// 如果不是file类型,就根据绝对路径 new一个出来
	@Override
	public File getFile() throws IOException {
		URL url = this.servletContext.getResource(this.path);
		if (url != null && ResourceUtils.isFileURL(url)) {
			// Proceed with file system resolution...
			return super.getFile();
		}
		else {
			String realPath = WebUtils.getRealPath(this.servletContext, this.path);
			return new File(realPath);
		}
	}
	
}

总结

Spring内部,针对于资源文件有一个统一的接口Resource表示。 因为我们现在绝大部分应用都构建在Spring的基础上,因此它提供的这些便捷的获取资源的工具,我们也是可以使用的。而不用去使用源生JDK的获取了~~~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法与编程之美

深入理解浏览器内核 - 概述

浏览器是平时学习工作中使用的一个最为平凡的软件,同时也是最重要的一款软件,它是我们了解世界的窗户,所以有时候也称之为世界之窗。但是我们真的了解浏览器是什么吗?了...

8860
来自专栏老码农的一亩三分地

IT兄弟连 HTML5教程 HTML和CSS的关系

HTML是描述网页的标记语言,是将内容放到网页上,虽然HTML本身也自带一些样式功能,通过自身的属性,来实现一些特定的效果,制作出来的只能是一个网页,而不是一个...

7720
来自专栏老码农的一亩三分地

IT兄弟连 HTML5教程 HTML5和JavaScript的关系

JavaScript可是实现HTML5重要语言。长久以来,JavaScript一直都是在HTML中实现动态效果的不二之选,而JavaScript在一些程序员眼里...

7010
来自专栏Super 前端

高性能网站建设指南-前端性能优化(一)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

9820
来自专栏Super 前端

JS常用代码块

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

40920
来自专栏前端迷

contenteditable跟style标签可真是天生一对

contenteditable是html的一个全局属性,可以大致理解为"可以使一个元素处于可编辑状态",像极了textarea,不过还是存在许多不足跟问题,比如...

16010
来自专栏咻咻ing

微信引流黑科技:手机浏览器直接唤起微信方案调研

链接是个微博短链:http://t.cn/RTqAzl8。放浏览器里访问,解析出完整的地址:http://r.jpwx.kim/wb/0e7d51958ac79...

39930
来自专栏AI科技大本营的专栏

最前沿:堪比E=mc2,Al-GA才是实现AGI的指标性方法论?

导读:在读完Uber AI Lab发表的一篇关于AGI的论文之后,本文作者恍然觉得有一种道破天机的感觉。他评价道,虽然这篇论文是泛泛之谈,却揭示了一些真正实现A...

8410
来自专栏老码农的一亩三分地

IT兄弟连 HTML5教程 HTML5和CSS3的关系

HTML5是第五版HTML的标准,CSS3则是第三版CSS,新增一些非常实用的选择器和样式属性,并且CSS3语言开发是朝着模块化发展的。以前的规范作为一个模块实...

8710
来自专栏Super 前端

现代前端技术解析:现代前端交互框架

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

10220

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励