目录

  • 资源处理和使用
  • 1. 资源接口
  • 2. Spring内置资源实现类
  • 3. ResourceLoader接口
  • 4. 资源依赖


资源处理和使用

URI(Universal Resource Identifier) :统一资源标志符

URL(Universal Resource Locator):统一资源定位符

URN(Universal Resource Name):统一资源名称

URI可以是URL、URN,或者是(URL + URN),所以可以说URI是其他两个的父级。

资源包括XML、Properties、File、二进制流等等。但Java的标准java.net.URL类库和用于各种URL前缀的标准处理器并不足以访问所有的底层资源,同时Spring如果直接使用这些底层API,工作量将会是巨大的,所以Spring对于资源访问进行了统一的抽象处理。

1. 资源接口

Spring提供一个Resource接口,用于统一的访问底层资源。它实质上是继承自InputStreamSource 接口。

public interface Resource extends InputStreamSource {

	boolean exists();

	default boolean isReadable() {
		return exists();
	}

	default boolean isOpen() {
		return false;
	}

	default boolean isFile() {
		return false;
	}

	URL getURL() throws IOException;

	URI getURI() throws IOException;

	File getFile() throws IOException;

	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接口一些重要的方法如下:

  • exists():返回一个布尔值,表示该资源是否以物理形式实际存在。
  • isOpen():返回一个布尔值,表示该资源是否进行了开启流的处理。如果是true,则InputStream仅能读取一次,不能多次读取,然后关闭以防止资源泄漏。除InputStreamResource外,对所有通常的资源实现都返回false。
  • getInputStream():父类方法,定位并打开此资源,返回从该资源读取的InputStream,每次调用都会返回新的InputStream,需要调用者手动关闭。
  • getDescription():返回当前资源的描述信息,通常是该资源的完全限定文件名或URL。

其他方法:

  • isReadable():返回一个布尔值,表示该资源是否可读。
  • isFile():返回一个布尔值,表示该资源是否为文件。
  • getURL():如果该资源能解析为URL,则返回URL,否则抛出异常。
  • getURI():如果该资源能解析为URI,则返回URI,否则抛出异常。
  • getFile():如果该资源能解析为File,则返回File,否则抛出异常。
  • contentLength():返回该资源的长度。
  • lastModified():返回该资源的最后修改时间。
  • createRelative(String relativePath):创建相对于此资源路径的资源,并返回该资源。
  • getFilename():返回该资源的文件名。
2. Spring内置资源实现类
  • URLResource

该资源实现类通过对java.net.URL进行包装,简化URL的资源访问,可用来访问通过URL直接访问的任何对象,如文件、HTTP、FTP等。所有URL都有标准化的字符串表示,因此可以使用标准化的前缀表示不同类型的URL(file:http:ftp:等)。

UrlResource urlResource = new UrlResource("file:///C:/xxx/xxx/xxx/123.txt");

URLResource是通过Java代码显式使用构造方法创建的,一般情况下,我们在调用字符串路径作为参数的某种API方法创建特定资源时,URLResource会隐式地创建。对于后者,则是通过PropertyEditor来最终决定创建哪种类型的资源实现,如果字符串包含前缀,它将通过该前缀创建合适类型的资源实现(如果是classpath:前缀将会创建ClassPathResource),如果不能识别该前缀,则假定该字符串式标准URL字符串,从而确定创建URLResource

PropertyEditor接口可以实现字符串和对象之间的转换,如转换为日期、URL、集合、类、数组、地区、文件等,这些都是通过内置好的PropertyEditor实现类完成,当然也可以通过接口自定义实现类并注册使用。

  • ClassPathResource

该资源实现类通过ClassLoader或给定类来加载资源,简化类路径下的资源访问。ClassPathResource支持java.io.File的解析,但仅支持存在于文件系统中的资源,不支持存在于Jar包且扩展到文件系统中的类路径资源。并且,所有的资源实现类也都支持java.net.URL的解析,即能使用getURL()

ClassPathResource resource = new ClassPathResource("123.txt");

ClassPathResource是通过Java代码显式使用构造方法创建的。一般情况下,我们在调用字符串路径作为参数的某种API方法创建特定资源时,ClassPathResource会隐式地创建。对于后者,则是通过PropertyEditor识别该字符串参数上的前缀classpath:,从而确定并创建ClassPathResource

classpath: 和 classpath: 区别*

  • classpath: 表示从类路径下加载资源,但只限于第一个类路径。
  • classpath*:表示从多个类路径下加载相同名称的资源(也包括jar包中的),因此加载效率会很慢,请谨慎使用。
  • FileSystemResource

java.io.Filejava.nio.file.Path句柄的资源实现,支持解析FileURL

  • ServletContextResource

针对于web下ServletContext资源访问的资源实现(入参资源位置相对于WEB-INF文件夹下)。它支持IO和URL资源访问。

  • InputStreamResource

通过给定InputStream的资源实现,只有在没有适合的资源实现类时才使用。由于isOpen()返回为true,所以如需保存资源描述符或者需多次开启流,请不要使用它(一般考虑使用ByteArrayResource)。

  • ByteArrayResource

**通过给定字节数组的资源实现,创建一个ByteArrayInputStream。**它可以从重复读取数组资源,所以不必使用一次性的InputStreamResource

3. ResourceLoader接口

通过ResourceLoader接口可以返回资源实例和类加载器。

public interface ResourceLoader {

    //实际为字符串 classpath:
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

    //返回指定资源路径的资源句柄,支持URL、类路径和相对路径
	Resource getResource(String location);

    //返回该这个ResourceLoader使用的类加载器
	@Nullable
	ClassLoader getClassLoader();

}

再来看看它的子接口及实现类:

resources 里面的 array 如何调用 resource-_spring

可以看出,所有的容器上下文都实现了ResourceLoader,因此,我们可以通过容器上下文对象获取资源实例。

当你使用特定类型的容器上下文来获取资源时,若指定路径没有前缀类型,则会返回一个适合于该容器上下文的资源类型,如通过ClassPathXmlApplicationContext会得到ClassPathResource类型的资源,WebApplicationContext会得到ServletContextResource类型的资源。因此,你可以根据需求使用特定类型的容器上下文来加载资源。

ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ac.getResource("org/data/123.txt");

从容器上下文的构造方法也可以看出,路径字符串若没有前缀,也是根据其特定类型的容器上下文来加载资源的,资源类型即为ClassPathResource

你也可以加入前缀来强制使用某种资源实现,从而取代容器上下文的默认资源加载类型。

ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ac.getResource("file:///data/123.txt");

前缀

例子

描述

classpath:

classpath:org/config/myConfig.xml

从类路径加载

file:

file:///D:/…/…/myConfig.xml

从文件系统中通过URL加载

http:

https://…/…/xxx.xml

从网络通过URL加载


/config/myConfig.xml

由容器上下文判断加载方式

获取ResourceLoader方式

  • 组件可通过实现ResourceLoaderAware接口来获取其引用。
  • 通过自动装配注入ResourceLoader(推荐更加灵活的注解式自动装配)

实际上,由于容器上下文本身就已经实现了ResourceLoader接口,所以我们还可以使用ApplicationContextAware来获取其引用。对于只想专门使用ResourceLoader的话还是建议使用ResourceLoaderAware,代码只会耦合到该接口,而不是更大的ApplicationContext接口。

4. 资源依赖
  • 若你的需求是通过动态确定路径来加载资源,那么使用ResourceLoader接口是最好的选择。
public class MyResourceLoaderBean implements ResourceLoaderAware {

    private ResourceLoader loader;

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.loader = resourceLoader;
    }

    public Resource getResource(String path){
        return loader.getResource(path);
    }
}

//主入口方法
public static void main(String[] args) throws IOException {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
    MyResourceLoaderBean myResourceLoaderBean = (MyResourceLoaderBean) ac.getBean("myResourceLoaderBean");
    System.out.println(myResourceLoaderBean.getResource("classpath:123.txt"));
}
  • 如果你的资源是静态不变的,那么只需提供其路径让它被直接注入即可。PropertyEditor通过其字符串路径及前缀(若无前缀,会根据其容器上下文来选择合适的资源实现),隐式的创建ClassPathResource并注入。
@Component
public class MyBean {

    @Value("classpath:123.txt")
    private Resource resource;
    
    public void setResource(Resource resource) {
        this.resource = resource;
    }
}