Java-利用Spring提供的Resource/ResourceLoader接口操作资源文件

简介: Java-利用Spring提供的Resource/ResourceLoader接口操作资源文件

背景


JDK提供的访问资源的类(如java.net.URL、File等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器上下文中获取资源的操作类。

Spring提供了Resource接口,为应用提供了更强的底层资源访问能力,该接口拥有对应不同资源类型的实现类。


资源访问接口

主要方法

20170708100333662.jpgboolean exists() 资源是否存在

boolean isOpen() 资源是否打开

URL getURL() throws IOException 如果底层资源可以表示成URL,则该方法放回对应的URL对象

File getFile() throws IOException 如果底层资源对应一个文件,这返回对应的File对象

Spring框架使用Resource装载各种资源,包括配置文件资源、国际化属性文件资源等。


主要实现类

20170708103443809.jpg


WritableResource : 可写资源接口,Spring3.1新增的接口,有2个实现类: FileSystemResource和PathResource。 其中PathResource是Spring4.0提供的实现类

ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造。

ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示,一般是以相对于根路径的方式

FileSystemResouce:文件系统资源,资源以文件系统路径的方式表示

InputStreamResource:以输入流返回标识的资源

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

UrlResource:封装了java.net.URL,它使用户能够访问任何可以通过URL表示的资源,如文件系统的资源,HTTP资源,FTP资源

PathResource : Spring4.0提供的读取资源文件的新类。Ptah封装了java.net.URL、java.nio.file.Path(Java 7.0提供)、文件系统资源,它四用户能够访问任何可以通过URL、Path、系统文件路径标识的资源,如文件系统的资源,HTTP资源,FTP资源

有了这个抽象的资源类后,就可以将Spring配置文件放在任何地方(如数据库、LDAP中),只要最终通过Resource接口返回配置信息即可。


Spring的Resource接口及其实现类可以在脱离Spring框架的情况下适用,比JDK更方便更强大.


例子


假设一个Web应用下有一个文件,用户可以通过以下几种方式对这个资源文件进行访问:

  1. 通过FileSystemResource以文件绝对路径的方式进行访问
  2. 通过ClassPathResource以类路径的方式进行访问
  3. 通过ServletContextResource以相对Web应用根目录的方式进行访问


WritableResource / ClassPathResource


20170708113748065.jpg

package com.xgj.service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.WritableResource;
/**
 * 
 * @ClassName: ResourceLoadTest
 * @Description: 跟这个模块无关,仅仅是为了测试 Resource接口操作文件
 * @author: Mr.Yang
 * @date: 2017年7月7日 下午11:38:19
 */
public class ResourceLoadTest {
    public static void main(String[] args) {
        try {
            String filePath = "D:/workspace/workspace-jee/HelloSpring/hello-spring4/src/test/resources/resourcefiletest.txt";
            // (1)使用系统文件路径加载文件
            WritableResource res = new FileSystemResource(filePath);
            // PathResource  @since 4.0
            //WritableResource res = new PathResource(filePath);
            System.out.println(res.getFilename());
            // (2)使用类路径方式加载spring-context.xml文件
            ClassPathResource classPathResource = new ClassPathResource("spring-context.xml");
            System.out.println(classPathResource.getFilename());
            // (3)使用WritableResource接口写资源文件
            OutputStream os = res.getOutputStream();
            os.write("小工匠的使用Resource接口测试".getBytes());
            os.close();
            // (4)使用Resource接口读取资源文件
            InputStream ins = res.getInputStream();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int i;
            while ((i = ins.read()) != -1) {
                bos.write(i);
            }
            System.out.println("读取的文件:" + res.getFilename() + ",内容:" + bos.toString());
            // 读取spring-context.xml的内容
            InputStream ins2 = classPathResource.getInputStream();
            int j;
            while ((j = ins2.read()) != -1) {
                bos.write(j);
            }
            //System.out.println("读取的文件:" + classPathResource.getFilename() + ",内容:" + bos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


输出:

resourcefiletest.txt
spring-context.xml
读取的文件:resourcefiletest.txt,内容:小工匠的使用Resource接口测试


ServletContextResource

20170708120652238.jpg

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<jsp:directive.page
    import="org.springframework.web.context.support.ServletContextResource" />
<jsp:directive.page import="org.springframework.core.io.Resource" />
<jsp:directive.page import="org.springframework.web.util.WebUtils" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>ResourceTest,nothing to do with this module</title>
</head>
<body>
    <%
        Resource res3 = new ServletContextResource(application, "/WEB-INF/classes/spring-context.xml");
        out.print(res3.getFilename() + "<br/>");
        out.print(WebUtils.getTempDir(application).getAbsolutePath());
    %>
</body>
</html>


运行:


20170708120740259.jpg

对资源文件编码

// (2)使用类路径方式加载spring-context.xml文件
ClassPathResource classPathResource = new ClassPathResource("spring-context.xml");
System.out.println(classPathResource.getFilename());
// 以UTF-8编码
EncodedResource ens = new EncodedResource(classPathResource ,"UTF-8");
String content = FileCopyUtils.copyToString(ens.getReader());
System.out.println("编码后的内容:\n" +content);


资源加载


通过上面的例子,是不是发现 ,为了访问不同类型的资源,必须使用相应的Resource实现类。


是否可以在不显式使用Resource实现类的情况下,仅仅通过资源地址的特殊标示符就可以访问相应的资源? 答案是肯定的,Spring提供了一个强大的加载资源的方式,不仅能通过“classpath:”、“file:”等资源地址前缀识别不同的资源类型,还支持Ant风格带通配符的资源地址。


资源地址表达式

Spring支持的资源类型的地址前缀


image.png


注意事项 classpath: 和 classpath*:

举个例子: 假设有多个Jar包或者文件系统类路径下拥有一个相同包名(com.xgj)


classpath: 只会加载第一个加载的com.xgj包的类路径下查找

classpath*: 会扫描到所有的这些jar包及类路径下出下的com.xgj类路径。


使用场景:


一般情况下,我们的应用都是有各个模块组成的,对于分模块打包的应用,假设我们有一个应用,分为N个模块,一个模块对应一个配置文件,分别为module1.xml 、module2xml、module3.xml….等,都放在了com.xgj的目录下,每个模块单独打成jar包。


我们可以使用 classpath*:com/xgj/module*.xml加载所有模块的配置文件。


如果使用classpath:com/xgj/module*.xml 只会加载一个模块的配置文件


Ant风格的资源地址

Ant风格的资源地址支持三种匹配符

  • ? 匹配文件名中的一个字符
  • * 匹配文件名中的任意字符
  • ** 匹配多层路径


示例:

classpath:com/t?st.xml


匹配com类路径下的 com/test.xml com/tast.xml等


file:D:/conf/*.xml

匹配文件系统D:/conf/目录下所有以.xml为后缀的文件


classpath:com/**/test.xml


匹配com类路径下(当前目录及子孙目录)的test.xml


classpath:org/springframework/**/*.xml


匹配类路径org/springframework/下是有的以.xml为后缀的文件


classpath:org/**/servlet/bla.xml


匹配类路径org任意层级的 /servlet/bla.xml的文件


资源加载器

介绍

Spring定义了一套资源加载的接口,并提供了实现类


20170710060722315.jpg

其中


20170710060928475.jpg


ResourceLoader中的方法Resource getResource(String location);

可以根据一个资源地址加载文件资源, 不过ResourceLoader这个接口方法中的资源地址仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式。


不过 ResourcePatternResolver 扩展了 ResourceLoader接口,


20170710061234419.jpg

ResourcePatternResolver 的getResource方法支持带资源类型前缀以及Ant风格的资源路径表达式。

PathMatchingResourcePatternResolver 是Spring提供的标准实现类。

20170710061508609.jpg


示例

package com.xgj.service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
/**
 * 
 * 
 * @ClassName: ResourceLoaderTest
 * 
 * @Description: 跟这个模块无关,仅仅是为了测试 ResourceLoa接口操作文件
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月9日 下午7:51:37
 */
public abstract class ResourceLoaderTest {
    static Logger logger = Logger.getLogger(ResourceLoaderTest.class);
    static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    public static void main(String[] args) {
        try {
            readFromClasspath();
            readFromHttp();
            readFromFile();
            readFromFTP();
            readFromNoPreFix();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 
     * 
     * @Title: readFromClasspath
     * 
     * @Description: 读取 classpath: 地址前缀的文件
     * 
     * @throws IOException
     * 
     * @return: void
     */
    public static void readFromClasspath() throws IOException {
        Resource[] resources = resourcePatternResolver.getResources("classpath*:com/xgj/conf/**/*.xml");
        for (Resource resource : resources) {
            System.out.println(resource.getDescription());
            readContent(resource);
        }
    }
    public static void readFromNoPreFix() throws IOException {
        Resource resource = resourcePatternResolver.getResource("spring-context.xml");
        System.out.println(resource.getDescription());
        readContent(resource);
    }
    /**
     * 
     * 
     * @Title: readFromFile
     * 
     * @Description: 使用UrlResource从文件系统目录中装载资源,可以采用绝对路径或者相对路径
     * 
     * @throws IOException
     * 
     * @return: void
     */
    public static void readFromFile() throws IOException {
        Resource resource = resourcePatternResolver.getResource(
                "file:/D:/workspace/workspace-jee/HelloSpring/hello-spring4/src/main/java/com/xgj/conf/conf2/test2.xml");
        readContent(resource);
    }
    /**
     * 
     * 
     * @Title: readFromHttp
     * 
     * @Description: 使用UrlResource从web服务器中加载资源
     * 
     * @throws IOException
     * 
     * @return: void
     */
    public static void readFromHttp() throws IOException {
        Resource resource = resourcePatternResolver.getResource("http://127.0.0.1:8080/hello-spring4/index.jsp");
        System.out.println(resource.getDescription());
        readContent(resource);
    }
    /**
     * 
     * 
     * @Title: readFromFTP
     * 
     * @Description: 这里只演示写法,因为这个服务器要求用户名和密码,其实是无法读取的。
     * 
     * @throws IOException
     * 
     * @return: void
     */
    public static void readFromFTP() throws IOException {
        Resource resource = resourcePatternResolver
                .getResource("ftp://172.25.243.81/webserver/config/logback.xml");
    }
    /**
     * 
     * 
     * @Title: readContent
     * 
     * @Description: 读取获取到的资源文件的内容
     * 
     * @param resource
     * @throws IOException
     * 
     * @return: void
     */
    public static void readContent(Resource resource) throws IOException {
        InputStream ins = resource.getInputStream();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int i;
        while ((i = ins.read()) != -1) {
            bos.write(i);
        }
        logger.debug("读取的文件:" + resource.getFilename() + ",/n内容:/n" + bos.toString());
    }
}


注意事项


使用Resource操作文件时,如果资源的配置文件在项目发布的时候会打包到jar中,那么就不能使用Resource.getFile()方法,否则会抛出FileNotFoundException异常。


推荐使用 Resource.getInputStream()读取。

错误的方式

(new DefaultResourceLoader()).getResource("classpath:conf/sys.properties").getFile();


正确的方式

(new DefaultResourceLoader()).getResource("classpath:conf/sys.properties").getInputStream();


建议尽量使用流的方式读取,避免环境不同造成问题

相关文章
|
4天前
|
Java
Java——抽象类和接口
抽象类是一种不能被实例化的类,至少包含一个抽象方法(无实现体的方法),常用于定义一组相关类的共同特征,并强制子类实现特定方法。抽象方法不能被 `static` 或 `final` 修饰,且必须被重写。 接口则是一个完全抽象的类,用于规范类的行为。接口使用 `interface` 关键字定义,不能实例化,并且类与接口之间是实现关系。 内部类是在一个类内定义的类,分为成员内部类、静态内部类、局部内部类和匿名内部类。成员内部类可被修饰符修饰,静态内部类只能访问外部类的静态成员,局部内部类定义在方法内,匿名内部类则隐藏了名字,直接通过 `new` 关键字定义并实现接口或继承类。
12 5
Java——抽象类和接口
|
4天前
|
Java
Java——接口的使用实例
Comparable接口用于自定义类的对象比较。通过实现此接口并重写`compareTo`方法,可以定义自定义类型的比较规则。 接下来介绍了Comparator接口,它提供了一种更灵活的比较方式。通过实现Comparator接口并重写`compare`方法,可以根据不同属性定义不同的比较规则。例如,定义一个`BrandComparator`类来比较汽车的品牌。 最后,介绍了Cloneable接口,用于实现对象的克隆。实现该接口并重写`clone`方法后,可以创建对象的浅拷贝或深拷贝。浅拷贝仅复制对象本身,深拷贝则会递归复制所有成员变量。
12 4
Java——接口的使用实例
|
4天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
4天前
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
10天前
|
Java 数据库连接 数据库
Java服务提供接口(SPI)的设计与应用剖析
Java SPI提供了一种优雅的服务扩展和动态加载机制,使得Java应用程序可以轻松地扩展功能和替换组件。通过合理的设计与应用,SPI可以大大增强Java应用的灵活性和可扩展性。
42 18
|
7天前
|
存储 数据采集 Java
Spring Boot 3 实现GZIP压缩优化:显著减少接口流量消耗!
在Web开发过程中,随着应用规模的扩大和用户量的增长,接口流量的消耗成为了一个不容忽视的问题。为了提升应用的性能和用户体验,减少带宽占用,数据压缩成为了一个重要的优化手段。在Spring Boot 3中,通过集成GZIP压缩技术,我们可以显著减少接口流量的消耗,从而优化应用的性能。本文将详细介绍如何在Spring Boot 3中实现GZIP压缩优化。
26 6
|
7天前
|
Java 开发者
Java的接口详解
Java接口是编程中的一种重要特性,用于定义方法签名而不提供具体实现,作为类之间的契约,使不同类能以统一方式交互。接口使用`interface`关键字定义,可包含方法声明和常量。类通过`implements`关键字实现接口,并可同时实现多个接口,解决多重继承问题。接口中的方法默认为抽象方法,变量默认为`public static final`。Java 8引入了默认方法和静态方法,增强接口功能。接口广泛应用于回调机制和多态性实现,有助于构建更灵活和可维护的代码结构。
|
5天前
|
Java 数据库连接 API
【Java笔记+踩坑】Spring Data JPA
从常用注解、实体类和各层编写方法入手,详细介绍JPA框架在增删改查等方面的基本用法,以及填充用户名日期、分页查询等高级用法。
【Java笔记+踩坑】Spring Data JPA
|
5天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
17天前
|
Java
盘点java8 stream中隐藏的函数式接口
`shigen`是一位坚持更新文章的博客作者,记录成长历程,分享认知见解,留住感动瞬间。本文介绍了函数式接口的概念及其在Java中的应用,包括`Comparator`、`Runnable`、`Callable`等常见接口,并详细讲解了`Function`、`Predicate`、`Consumer`、`Supplier`和`Comparator`等函数式接口的使用方法及应用场景,展示了如何利用这些接口简化代码并提高编程效率。**个人IP:shigen**,与shigen一起,每天进步一点点!
29 0
盘点java8 stream中隐藏的函数式接口