Spring之所以抽象Resource接口,是因为传统的java使用URL和标准的handler来处置资源。但是有个局限是不能直接读取classpath下的资源。因此Spring定义了一套接口。这套接口分为两部分,Resource和ResourceLoader,其中是对资源的抽象,后者负责加载资源。Spring的ApplicationContext实现了ResourceLoader接口。
Resource
首先看Resource的接口定义。
public interface Resource extends InputStreamSource { boolean exists(); boolean isReadable(); /** * Return whether this resource represents a handle with an open * stream. If true, the InputStream cannot be read multiple times, * and must be read and closed to avoid resource leaks. * <p>Will be {@code false} for typical resource descriptors. */ boolean isOpen(); /** * Return a URL handle for this resource. * @throws IOException if the resource cannot be resolved as URL, * i.e. if the resource is not available as descriptor */ URL getURL() throws IOException; /** * Return a URI handle for this resource. * @throws IOException if the resource cannot be resolved as URI, * i.e. if the resource is not available as descriptor */ URI getURI() throws IOException; File getFile() throws IOException; long contentLength() throws IOException; */ long lastModified() throws IOException; /** * Create a resource relative to this resource. * @param relativePath the relative path (relative to this resource) * @return the resource handle for the relative resource * @throws IOException if the relative resource cannot be determined */ Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); } public interface InputStreamSource { InputStream getInputStream() throws IOException; }
以下接口简单明了,唯一需要注意的就是方法isOpen(),返回true表示此资源可以被多次读取,且需要使用者关闭。典型实现返回的都是false.
Resource的接口有子接口和几个实现类,其基本结构继承如下:
首先是包含两个子接口WritableResource 扩展了资源的可写性,ContextResource扩展,在某个上下文中读取资源。
所有的具体实现类都继承AbstractResource。从名称上可以明显的看出,不同的实现类负责不同形式的资源。
其中FileSystemResource extendsAbstractResource implements WritableResource ,这是一种很常见的扩展方式。
ResourceLoader
Spring提供了很多个不同类型的Resource,ResourceLoader则负责根据资源描述符(通常为字符串(数组),可带通配符),根据不同的情况,建立不同的Resource实例。
接口定义如下:
public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:" */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; Resource getResource(String location); ClassLoader getClassLoader(); }
定义了加载资源的方法,并暴露ResourceLoader的类加载器,其实现类和子接口集成结构如下:
我们使用的ApplicationContex都继承ResourceLoader,其中DefaultResourceLoader的实现getResource如下,分三种情况进行资源的加载:
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
下面看在Spring构建ApplicationContext的时候,是如何使用ResourceLoader的,这里单摘出加载xml配置的部分代码。在AbstractBeanDefinitionReader以下方法:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { //支持通配符 try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 。。。。。 else { // Can only load single resources by absolute URL. Resource resource = resourceLoader.getResource(location); 。。。。 } return loadCount; } } @Override
//这里返回的就是ApplicationContext public ResourceLoader getResourceLoader() { return this.resourceLoader; }
结束
本文简单的分析了Spring的Resource框架,并单独摘出了Spring在加载bean定义的时使用Resource的代码,理解这点,为了解spring容器的启动过程打下一个小基础。本文依照spring源码写成,由于此部分相当简单,故很多部分(如AbstractResource的基本实现)都没有列出来。