红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

从零开始 Spring Boot 28:资源

2023年5月14日 925点热度 0人点赞 0条评论

spring boot

图源:简书 (jianshu.com)

Resource 接口

Spring中的资源被抽象为一个Resource接口:

public interface Resource extends InputStreamSource {
​
    boolean exists();
​
    boolean isReadable();
​
    boolean isOpen();
​
    boolean isFile();
​
    URL getURL() throws IOException;
​
    URI getURI() throws IOException;
​
    File getFile() throws IOException;
​
    ReadableByteChannel readableChannel() throws IOException;
​
    long contentLength() throws IOException;
​
    long lastModified() throws IOException;
​
    Resource createRelative(String relativePath) throws IOException;
​
    String getFilename();
​
    String getDescription();
}

Resource 接口中最重要的一些方法是。

  • getInputStream(): 定位并打开资源,返回一个用于读取资源的 InputStream。我们期望每次调用都能返回一个新的 InputStream。关闭该流是调用者的责任。

  • exists(): 返回一个 boolean 值,表示该资源是否以物理形式实际存在。

  • isOpen(): 返回一个 boolean,表示该资源是否代表一个具有开放流的句柄。如果为 true,InputStream 不能被多次读取,必须只读一次,然后关闭以避免资源泄漏。对于所有通常的资源实现,除了 InputStreamResource 之外,返回 false。

  • getDescription(): 返回该资源的描述,用于处理该资源时的错误输出。这通常是全路径的文件名或资源的实际URL。

内置的Resource实现

Spring内置了一些Resource接口的实现类:

  • UrlResource

  • ClassPathResource

  • FileSystemResource

  • PathResource

  • ServletContextResource

  • InputStreamResource

  • ByteArrayResource

这里介绍几个常见的Resource实现类。

ClassPathResource

ClassPathResource是最常见的,通过它我们可以访问ClassPath中的文件。

举例说明,假如在Spring的静态资源目录resources下有一个文件override.properties:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

可以通过下面的示例代码将其内容打印到控制台:

package com.example.resource.controller;
// ...
@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("")
    public String hello() throws IOException {
        Resource resource = new ClassPathResource("override.properties");
        printContent(resource);
        return Result.success().toString();
    }
​
    private void printContent(Resource resource) throws IOException {
        File file;
        try {
            file = resource.getFile();
        } catch (FileNotFoundException e) {
            String content = resource.getContentAsString(StandardCharsets.UTF_8);
            System.out.println(content);
            return;
        }
        printContent(file);
    }
​
    private void printContent(File file) throws IOException {
        FileReader fr;
        fr = new FileReader(file);
        BufferedReader br = new BufferedReader(fr);
        String line;
        do {
            line = br.readLine();
            if (line == null) {
                break;
            }
            System.out.println(line);
        }
        while (true);
        br.close();
    }
}

这其中下面这行代码,明确创建了一个到resources/override.properties文件的资源:

Resource resource = new ClassPathResource("override.properties");

如果Spring项目是通过IDE运行的,那么resources这个静态资源目录会被加入ClassPath,因此自然可以通过ClassPathResource正确访问到,如果项目是打包成Jar包运行,该目录同样会被打包的Jar包中的/BOOT-INF/classes目录:

image-20230513172429162

该目录同样会被加入ClassPath中,所以同样可以获取到正确的文件。

正是因为这样的特性,所以在开发中通常都会用ClassPath的方式引用静态资源,而非文件路径。因为后者可能导致部署的目标服务器上缺少相应的资源而出错。

FileSystemResource

FileSystemResource是通过文件系统来访问资源,具体来说就是文件的相对路径和绝对路径。

同样是上面的示例,只需要稍微修改:

    // ...
    @GetMapping("")
    public String hello() throws IOException {
        Resource resource = new FileSystemResource("src/main/resources/override.properties");
        printContent(resource);
        return Result.success().toString();
    }
    // ...

当然也可以使用绝对路径:

Resource resource = new FileSystemResource("D:/workspace/learn_spring_boot/ch28/resource/src/main/resources/override.properties");

UrlResource

通过UrlResource可以访问用URL定义的资源,当然最常见的是网络资源:

Resource resource = new UrlResource("https://blog.icexmoon.cn/");

要说明的是,通过UrlResource创建的Resource,是无法通过调用Resource.getFile()方法获取文件的,会产生FileNotFoundException异常。因此只能是以Resource.getInputStream()方法获取输入流,然后再打印内容。不过Resource接口其实已经提供了一个getContentAsString()方法:

public interface Resource extends InputStreamSource {
    // ...
    default String getContentAsString(Charset charset) throws IOException {
        return FileCopyUtils.copyToString(new InputStreamReader(this.getInputStream(), charset));
    }
    // ...
}

URL实际上也可以指定本地文件系统:

Resource resource = new UrlResource("file://D:/workspace/learn_spring_boot/ch28/resource/src/main/resources/override.properties");

其它的Resource实现可以阅读核心技术 (springdoc.cn)。

ResourceLoader 接口

可以通过接口ResourceLoader来获取Resource:

public interface ResourceLoader {
​
    Resource getResource(String location);
​
    ClassLoader getClassLoader();
}

所有的application context都实现了这个接口,可以当做ResourceLoader来使用:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private ApplicationContext ctx;
    @GetMapping("")
    public String hello() throws IOException {
        Resource resource = ctx.getResource("classpath:override.properties");
        printContent(resource);
        return Result.success().toString();
    }
}

实际上在这个示例中,直接注入ResourceLoader更为合适:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private ResourceLoader resourceLoader;
    @GetMapping("")
    public String hello() throws IOException {
        Resource resource = resourceLoader.getResource("classpath:override.properties");
        printContent(resource);
        return Result.success().toString();
    }
    // ...
}

作为参数传入getResource方法的classpath:override.properties这样的被称作资源字符串:

前缀 示例 说明
classpath: classpath:com/myapp/config.xml 从classpath加载。
file: file:///data/config.xml 作为 URL 从文件系统加载。另请参见FileSystemResource 注意事项.
https: https://myserver/logo.png 以 URL 形式加载。
(none) /data/config.xml 取决于底层的 `ApplicationContext'。

如果资源字符串不带前缀(比如classpath:),ResourceLoader获取资源的行为根据ApplicationContext的类型的不同而不同,比如ClassPathXmlApplicationContext默认会以ClassPathResource的方式获取资源,FileSystemXmlApplicationContext默认会以FileSystemResource的方式获取资源。

ResourcePatternResolver 接口

ResourcePatternResolver接口是ResourceLoader的扩展:

public interface ResourcePatternResolver extends ResourceLoader {
​
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
​
    Resource[] getResources(String locationPattern) throws IOException;
}

在ResourceLoader的基础上,getResources()方法增加了对通配符的支持:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private ResourcePatternResolver resourcePatternResolver;
​
    @GetMapping("")
    public String hello() throws IOException {
        Resource[] resources = resourcePatternResolver.getResources("classpath:*.properties");
        for(Resource r: resources){
            System.out.println(r.getFilename());
        }
        if (resources == null || resources.length == 0){
            return Result.fail("没有获取到文件").toString();
        }
        Resource resource = resources[0];
        printContent(resource);
        return Result.success().toString();
    }
    // ...
}

这个示例中,resourcePatternResolver.getResources("classpath:*.properties")可以匹配到resource目录下所有以.properties为后缀的文件作为资源对象返回。

如果需要从多个jar中检索同样的包名下的资源,可以使用classpath*:这样的前缀配合通配符检索。

ResourceLoaderAware 接口

可以让bean通过实现ResourceLoaderAware接口的方式获取ResourceLoader:

@RestController
@RequestMapping("/hello")
public class HelloController implements ResourceLoaderAware {
    // ...
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Resource resource = resourceLoader.getResource("classpath:override.properties");
        try {
            printContent(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当然,相比直接注入ResourceLoader,这样做并没有什么优势。

注入Resource

可以借助@Value注解直接注入Resource:

@RestController
@RequestMapping("/hello")
public class HelloController implements ResourceLoaderAware {
    // ...
    @Value("${my.properties}")
    @Autowired
    private Resource resource;
​
    @GetMapping("")
    public String hello() throws IOException {
        printContent(resource);
        return Result.success().toString();
    }
    // ...
}

Spring Boot默认的配置文件application.properties:

my.properties=classpath:override.properties

当然,通过setter或构造器注入也是可以的,这里不再演示。

资源字符串中使用了通配符,可以注入所有匹配的资源:

my.all.properties=classpath:*.properties
@RestController
@RequestMapping("/hello")
public class HelloController implements ResourceLoaderAware {
    // ...
    @Value("${my.all.properties}")
    @Autowired
    private Resource[] resources;
​
    @GetMapping("")
    public String hello() throws IOException {
        for (Resource r : resources) {
            System.out.println(r.getFilename());
        }
        if (resources == null || resources.length == 0) {
            return Result.fail("没有获取到文件").toString();
        }
        Resource resource = resources[0];
        printContent(resource);
        return Result.success().toString();
    }
    // ...
}

本文所有的示例代码可以通过learn_spring_boot/ch28/resource获取。

谢谢阅读。

参考资料

  • 核心技术 (springdoc.cn)

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: resource spring
最后更新:2023年5月14日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号