Spring如何处理资源以及如何在Spring中使用资源

Java标准的java.net.URL主要用于各种URL前缀的类和标准处理程序,但是不足以满足对低级资源的所有访问。例如,没有标准化的URL实现可以用来访问类路径或相对于ServletContext的资源。虽然可以为专门的URL前缀注册新的处理程序(类似于http:)等前缀的现有处理程序,但这通常相当复杂,并且URL接口仍然缺少一些需要的功能,例如检查所指向的资源是否存在的方法。

Resource接口

Spring的Resource接口是一个更强大的接口,用于抽象对低级资源的访问。下表显示了资源接口定义:

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}

Resource接口扩展了InputStreamSource接口

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

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

  • getInputStream():定位并打开资源,从资源中读取,返回InputStream。预期每次调用都会返回一个新的InputStream。调用者有责任关闭流。
  • exists():返回一个布尔值,标识此资源是否实际物理存在。
  • isOpen():返回一个布尔值,表示此资源是否表示具有开放流的句柄。如果为true,则无法多次读取InputStream,必须只读取一次,然后关闭以避免资源泄漏。对于所有常见的资源实现返回false,InputStreamResource除外。
  • getDescription():返回此资源的说明,在使用该资源时用于错误输出。这通常是完全限定的文件名或资源的实际URL。

其他方法允许你获得表示资源的实际URL或File对象(如果底层实现兼容并支持该功能)。

Spring本身广泛地使用Resource抽象,它是许多方法签名中的参数。一些spring api中的其他方法(例如各种ApplicationContext实现的构造函数)采用一个字符串,该字符串以简单的形式用于创建与该上下文实现相适应的资源,或者通过字符串路径上的特殊前缀,让调用者指定必须创建和使用特定的Resource实现。

内置Resource实现

Spring包含以下这些实现:

  • UrlResource
  • ClassPathResource
  • FileSystemResource
  • ServletContextResource
  • InputStreamResource
  • ByteArrayResource
UrlResource

UrlResource包装了java.net.URL,通过URL访问任何对象,例如文件、HTTP目标、FTP目标等。所有的URL都有一个标准化的字符串表示,因此使用适当的标准化前缀来表示一个URL类型和另一个URL类型。这包括file:用于访问文件系统路径,http:用于通过http协议访问资源,ftp:用于通过ftp访问资源,等等。

UrlResource是由Java代码通过显式使用UrlResource构造函数创建的,但通常是在调用一个API方法时隐式创建的,该方法采用字符串参数来表示路径。对于后一种情况,JavaBeans属性编辑器最终决定要创建哪种类型的资源。如果路径字符串包含已知的前缀(例如,classpath:),它将为该前缀创建适当的专用资源。但是,如果它不识别前缀,它将假定该字符串是标准的URL字符串并创建一个UrlResource。

ClassPathResource

此类表示从类路径获取的资源。它使用线程上下文类加载器、给定类加载器或给定类来加载资源。

此Resource实现支持解析在类路径下的java.io.File文件,而不是驻留在jar中且尚未扩展到文件系统(通过servlet引擎或其他环境)的类路径资源。为了解决这个问题,各种Resource实现总是支持解析作为java.net.URL.

ClassPathResource是由Java代码通过显式使用ClassPathResource构造函数创建的,但通常是在调用一个API方法时隐式创建的,该方法采用字符串参数表示路径。对于后一种情况,JavaBeans属性编辑器识别字符串路径上的特殊前缀classpath:,并在这种情况下创建一个ClassPathResource。

FileSystemResource

支持java.io.File 和java.nio.file.Path的一个Resource实现。

ServletContextResource

ServletContext资源的Resource实现,它解析相关web应用程序根目录中的相对路径。

它始终支持流访问和URL访问,但允许java.io.File文件仅当web应用程序存档扩展并且资源实际位于文件系统上时才进行访问。不管在文件系统上还是直接从JAR或其他地方(比如数据库)访问,实际上取决于Servlet容器。

InputStreamResource

InputStreamResource是给定InputStream的Resource实现。只有在没有具体的资源实现适用的情况下才应该使用它。在可能的情况下,首选ByteArrayResource或任何基于文件的资源实现。

与其他资源实现不同,这是一个已打开资源的描述符。因此,它从isOpen()返回true。如果需要将资源描述符保存在某个地方,或者需要多次读取一个流,请不要使用它。

ByteArrayResource

字节数组的Resource实现。它为给定的字节数组创建一个ByteArrayInputStream。

它对于从任何给定的字节数组加载内容非常有用,而不必求助于一次使用的InputStreamResource。

以下是几个Resource实现的实例代码:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
        @ContextConfiguration(classes = SpringConfig.class),
        @ContextConfiguration(classes = SpringMVCConfig.class)
})
public class ResourceTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Test
    public void testUrlResource() throws IOException {
        UrlResource resource = new UrlResource("http://www.baidu.com");
        InputStream inputStream = resource.getInputStream();
        System.out.println(getStringFromInputStream(inputStream));
    }

    @Test
    public void testClassPathResource() throws IOException {
        ClassPathResource resource = new ClassPathResource("dmd.properties");
        InputStream inputStream = resource.getInputStream();
        System.out.println("文件描述:" + resource.getDescription());
        System.out.println(getStringFromInputStream(inputStream));
    }

    @Test
    public void testFileSystemResource() throws IOException {
        FileSystemResource resource =  new FileSystemResource("C:\\large.txt");
        InputStream inputStream = resource.getInputStream();
        System.out.println(getStringFromInputStream(inputStream));
    }

    @Test
    public void testServletContextResource() throws IOException {
        // 读取webapp下的文件
        ServletContextResource resource =  new ServletContextResource(webApplicationContext.getServletContext(), "WEB-INF/views/index.jsp");
        InputStream inputStream = resource.getInputStream();
        System.out.println(getStringFromInputStream(inputStream));
    }

    public static String getStringFromInputStream(InputStream inputStream) {
        String resultData = null;      //需要返回的结果
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int len = 0;
        try {
            while ((len = inputStream.read(data)) != -1) {
                byteArrayOutputStream.write(data, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        resultData = new String(byteArrayOutputStream.toByteArray());
        return resultData;
    }
}
ResourceLoader

ResourceLoader接口可以返回Resource实例

public interface ResourceLoader {

    Resource getResource(String location);

}

所有应用程序上下文都实现ResourceLoader接口。因此,可以使用所有应用程序上下文来获取资源实例。

当你对特定的应用程序上下文调用getResource(),而指定的位置路径没有特定的前缀时,你将获得一个适合该特定应用程序上下文的资源类型。例如,假设对ClassPathXmlApplicationContext实例执行以下代码片段:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

对于ClassPathXmlApplicationContext,该代码返回ClassPathResource。如果对FileSystemXmlApplicationContext实例执行相同的方法,它将返回一个FileSystemResource。对于WebApplicationContext,它将返回一个ServletContextResource。类似地,它将为每个上下文返回适当的对象。

因此,您可以以适合特定应用程序上下文的方式加载资源。

另一方面,您也可以通过指定特殊的classpath:prefix来强制使用ClassPathResource,而不考虑应用程序上下文类型:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

类似地,你可以通过指定任何标准来强制使用UrlResourcejava.net.URL前缀。以下两个示例使用文件前缀和http前缀:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");

Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

下表总结了将字符串对象转换为资源对象的策略:

PrefixExampleExplanation

classpath:

classpath:com/myapp/config.xml

从类路径加载。

file:

file:///data/config.xml

从文件系统查找。

http:

https://myserver/logo.png

从网络URL查找

(none)

/data/config.xml

取决于基础应用程序上下文。

ResourceLoaderAware 接口

ResourceLoaderAware是一个特殊的接口(感知接口),获取资源加载器,可以获得外部资源。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现ResourceLoaderware并部署到应用程序上下文(作为Spring托管bean)中时,应用程序上下文将其识别为ResourceLoaderware。然后应用程序上下文调用setResourceLoader(ResourceLoader),将自身作为参数提供(记住,Spring中的所有应用程序上下文都实现ResourceLoader接口)。

由于ApplicationContext是一个资源加载器,bean还可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来加载资源。但是,一般来说,如果你只需要专用的ResourceLoader接口,那么最好使用它。代码将只耦合到资源加载接口(可以认为是实用程序接口),而不是整个Spring ApplicationContext接口。

应用程序上下文和资源路径

本小节介绍如何使用资源创建应用程序上下文,包括使用XML的快捷方式、如何使用通配符以及其他详细信息。

构造应用程序上下文
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
应用程序上下文构造函数资源路径中的通配符

应用程序上下文构造函数值中的资源路径可以是简单路径,每个路径都有到目标资源的一对一映射,或者可以包含特殊的“classpath*:”前缀或内部Ant样式的正则表达式(通过使用Spring的PathMatcher实用程序进行匹配)。后者都是有效的通配符。

Ant形式匹配

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

当路径位置包含Ant样式的模式时,解析器遵循一个更复杂的过程来尝试解析通配符。它为到最后一个非通配符段的路径生成一个资源,并从中获取一个URL。如果这个URL不是jar:URL或容器特定的变体(例如WebLogic中的zip:,WebSphere中的wsjar等等),则从java.io.File文件中获取,并通过遍历文件系统来解析通配符。对于jar URL,解析程序将获取java.net.JarURLConnection或者手动解析jar URL,然后遍历jar文件的内容来解析通配符。

对可移植性的影响

如果指定的路径已经是一个文件URL(隐式地因为基ResourceLoader是文件系统URL,也可能是显式的),那么通配符保证以完全可移植的方式工作。

classpath*:前缀

在构造基于XML的应用程序上下文时,位置字符串可以使用特殊的classpath*:前缀,如下例所示:

ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

这种特殊前缀指定必须获得与给定名称匹配的所有类路径资源(在内部,这实际上是通过调用类加载器.getResources(…)然后合并以形成最终的应用程序上下文定义。

通配符类路径依赖于底层类加载器的getResources()方法。由于现在大多数应用程序服务器都提供自己的类加载器实现,因此行为可能有所不同,尤其是在处理jar文件时。检查classpath*是否有效的一个简单测试是使用classloader从类路径上的jar中加载文件:getClass().getClassLoader().getResources(“<someFileInsideTheJar>”)。尝试使用具有相同名称但位于两个不同位置的文件进行此测试。如果返回了不适当的结果,请检查应用服务器文档中可能影响类加载器行为的设置。

 

posted @ 2020-07-14 21:50  codedot  阅读(371)  评论(0编辑  收藏  举报