Chapter 4. Abstracting Access to Low-Level Resources

4.1. Overview

Java's standard java.net.URL interface and istandard handlers for vairous URL prefixes are unfortunately not quite adequate enough for all access to low-level resources. There is for example no standardized URL implementation which may be used to access a resource that needs to be obtained from somewhere on the classpath, or relative to a ServletContext, for example. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desireable functionality, such as a method to check the existence of the resource being pointed to.

4.2. The Resource interface

Spring's Resource interface is meant to be a more capable interface for abstracting access to low-level resources.

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();
}

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

Some of the most important methods are:

  • getInputStream(): locates and opens the resource, returning an InputStream for reading it. It is expected that each invocation returns a fresh InputStream. It is the responsibility of the caller to close the stream.

  • exists(): returns a boolean indicating whether this resource actually exists in physical form

  • isOpen(): returns a boolean indicating whether this resource represents a handle with an open stream. If true, the InputStream cannot be read multiple times, and must be read once only and then closed to avoid resource leaks. Will be false for all usual resource implementations, with the exception of InputStreamResource.

  • getDescription(): returns a description for this resource, to be used for error output when working with the resource. This is often the fully qualified file name or the actual URL

Other methods allow you to obtain an actual URL or File object representing the resource, if the underlaying implementation is compatible, and supports that functionality.

Resource is used extensively in Spring itself, as an argument type in many method signatures when a resource is needed. Other methods in some Spring APIs (such as the constructors to various ApplicationContext implementations), take a String which in unadorned or simple form is used to create a Resource appropriate to that context implementation, or via special prefixes on the String path, allow the caller to specify that a specific Resource implementation should be created and used. Internally, a JavaBeans PropertyEditor is used to convert the String to the appropriate Resource type, but this is irrelevant to the user.

While Resource is used a lot with Spring and by Spring, it's actually very useful to use as a general utility class by itself in your own code, for access to resources, even when your code doesn't know or care about any other parts of Spring. While this couples your code to Spring, it really only couples it to this small set of utility classes, which are serving as a more capable replacement for URL, and can be considered equivalent to any other library you would use for this purpose.

It's important to note that Resource doesn't replace functionality, it wraps it where possible. For example, a UrlResource wraps a URL, and uses the wrapped URL to do its work.

4.3. Built-in Resource implementations

There are a number of built-in Resource implementations.

4.3.1. UrlResource

This wraps a java.net.URL, and may be used to access any object that is normally accessible via a URL, such as files, an http target, an ftp target, etc. All URLs have a standardized String representation, such that appropriate standardized prefixes are used to indicate one URL type vs. another. This includes file: for accessing filesystem paths, http: for accessing resources via the HTTP protocol, ftp: for accessing resources via ftp, etc.

A UrlResource is created by Java code explicitly using the UrlResource constructor, but will often be created implicitly when you call an API method which takes a String argument which is meant to represent a path. For the latter case, a JavaBeans PropertyEditor will ultimately decide which type of Resource to create. If the path string contains a few well-known (to it, that is) prefixes such as classpath:, it will create an appropriate specialized Resource for that prefix. However, if it doesn't recognize the prefiix, it will assume the this is just a standard URL string, and will create a UrlResource.

4.3.2. ClassPathResource

This class represents a resource which should be obtained from the classpath. This uses either the thread context class loader, a given class loader, or a given class for loading resources.

This implementation of Resource supports resolution as java.io.File if the class path resource resides in the file system, but not for classpath resources which reside in a jar and have not been expanded (by the servlet engine, or whatever the environment is) to the filesystem. It always supports resolution as java.net.URL.

A ClassPathResource is created by Java code explicitly using the ClassPathResource constructor, but will often be created implicitly when you call an API method which takes a String argument which is meant to represent a path. For the latter case, a JavaBeans PropertyEditor will recognize the special prefix classpath:on the string path, and create a ClassPathResource in that case.

4.3.3. FileSystemResource

This is a Resource implementation for java.io.File handles. It obviously supports resolution as a File, and as a URL.

4.3.4. ServletContextResource

This is a Resource implementation for ServletContext resources, interpreting relative paths within the web application root directory.

This always supports stream access and URL access, but only allows java.io.File access when the web application archive is expanded and the resource is physically on the filesystem. Whether or not it's expanded and on the filesystem like this, or accessed directly from the JAR or somewhere else like a DB (it's conceivable) is actually dependent on the Servlet container.

4.3.5. InputStreamResource

A Resource implementation for a given InputStream. This should only be used if no specific Resource implementation is applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible..

In contrast to other Resource implementations, this is a descriptor for an already opened resource - therefore returning "true" from isOpen(). Do not use it if you need to keep the resource descriptor somewhere, or if you need to read a stream multiple times.

4.3.6. ByteArrayResource

This is a Resource implementation for a given byte array. It creates ByteArrayInputStreams for the given byte array.

It's useful for loading content from any given byte array, without having to resort to a single-use InputStreamResource.

4.4. The ResourceLoader Interface

The ResourceLoader interface is meant to be implemented by objects that can return (i.e load) Resources.

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

All application contexts implement ResourceLoader therefore all application contexts may be used to obtain Resources.

When you call getResource() on a specific application context, and the location path specified doesn't have a specific prefix, you will get back a Resource type that is appropriate to that particular application context. For example, if you ask a ClassPathXmlApplicationContext

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

you'll get back a ClassPathResource, but if the same method is called on a FileSystemXmlApplicationContext, you'd get back a FileSystemResource. For a WebApplicationContext, you'd get a ServletContextResource, and so on.

As such, you can load resources in a fashion appropriate to the particular application context.

On the other hand, you may also force ClassPathResource to be used, regardless of the application context type, by specifying the special classpath: prefix:

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

or force a UrlResource to be used by specifyng any of the standard java.net.URL prefixes:

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

4.5. The ResourceLoaderAware interface

The ResourceLoaderAware interface is a special marker interface, for objects that expect to be provided with a ResourceLoader:

public interface ResourceLoaderAware {
   void setResourceLoader(ResourceLoader resourceLoader);
}

When a bean implements ResourceLoaderAware and is deployed into an application context, it is recognized by the application context and called back by it, with the application context itself passed in as the ResourceLoader argument.

Of course, since an ApplicationContext is a ResourceLoader, the bean could also implement ApplicationContextAware and use the passed in context directly to load resources, but in general, it's better to use the specialized ResourceLoader interface if that's all that's needed, as there is less of a degree of coupling to Spring. The code would just be coupled to the resource loading interface, which can be considered a utility interface, not the whole context interface.

4.6. Setting Resources as properties

If the bean itself is going to determine and supply the resource path through some sort of dynamic process it probably makes sense for the bean to use the ResourceLoader interface to load resources. Consider as an example the loading of a template of some sort, where the specific one needed that depends on the role of the user. If on the other hand the resources are static, it makes sense to eliminate the use of the ResourceLoader interface completely, and just have the bean expose the Resource properties it needs, and expect that they will be injected into it.

What makes it trivial to then inject these properties, is that all application contexts register and use a special JavaBeans PropertyEditor which can convert String paths to Resource objects. So if myBean has a template property of type Resource, it can be configured with a text string for that resource, as follows:

<bean id="myBean" class="...">
  <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

Note that the resource path has no prefix, so because the application context itself is going to be used as the ResourceLoader, the resource itself will be loaded via a ClassPathResource, FileSystemResource, ServletContextResource, etc., as appropriate depending on the type of the context.

If there is a need to force a specifc Resource type to be used, then a prefix may be used. The following two examples show how to force a ClassPathResource and a UrlResource (the latter being used to access a filesystem file).

  <property name="template" value="classpath:some/resource/path/myTemplate.txt"/>
  <property name="template" value="file:/some/resource/path/myTemplate.txt"/>

4.7. Application contexts and Resource paths

4.7.1. Constructing application contexts

An application context constuctor (for a specific application context type) generally takes a string or array of strings as the location path(s) of the resource(s) such as XML files that make up the definition of the context.

When such a location path doesn't have a prefix, the specific Resource type built from that path and used to load the definiton, depends on and is appropriate to the specific application context. For example, if you create a ClassPathXmlApplicationContext as follows:

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

then the definition will be loaded from the classpath, as a ClassPathResource will be used. But if you create a FilleSystemXmlApplicationContext as follows:

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

then the definition will be loaded from a filesystem location, in this case relative to the current working directory.

Note that the use of the special classpath prefix or a standard URL prefix on the location path will override the default type of Resource created to load the definition. So this FileSystemXmlApplicationContext

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

will actually load its definition from the classpath. However, it's still a FileSystemXmlApplicationContext. If it's subsequently used as a ResourceLoader, any unprefixed paths are still treated as filesystem paths.

4.7.2. The classpath*: prefix

When constructing an XML-based application context, a location string may use the special classpath*: prefix:

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

This special prefix specifies that all classpath resources that match the gven name should be obtained (internally, this essentially happens via a ClassLoader.getResources(...) call), and then merged to form the final application context definition.

One use for this mechanism is when doing component-style application assembly. All components can 'publish' context definition fragments to a well-known location path, and when the final application context is created using the same path prefixed via classpath*:, all component fragments will be picked up automatically.

Note that this special prefix is specific to application contexts, and is resolved at construction time. It has nothing to do with the Resource type itself. It's not possible to use the classpath*: prefix to construct an actual Resource, as a resource points to just one resource at a time.

4.7.3. Unexpected application context handling of FileSystemResource absolute paths

A FileSystemResource that is not attached to a FileSystemApplicationContext (that is, a FileSystemApplicationContext is not the actual ResourceLoader) will treat absolute vs. relative paths as you would expect. Relative paths are relative to the current working directory, while absolute paths are relative to the root of the filesystem.

For backwards compatibility (historical) reasons however, this changes when the FileSystemApplicationContext is the ResourceLoader. FileSystemApplicationContext simply forces all attached FileSystemResources to treat all location paths as relative, whether they start with a leading slash or not. In practice, this means the following are equivalent:

ApplicationContext ctx =
    new FileSystemClassPathXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
    new FileSystemClassPathXmlApplicationContext("/conf/context.xml");

as well as the following

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

Even though it would make sense for them to be different, as one case being relative vs. one being absolute.

In practice, if true absolute filesystem paths are needed, it is better to forgo the use of absolute paths with FileSystemResource/FileSystemXmlApplicationContext, and just force the use of a UrlResource, by using the file: URL prefix.

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:/some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load it's definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:/conf/context.xml");