Spring3.2 资源(Resource)管理
2016 年 01 月 30 日
spring

    在Spring容器中,有一个基础的抽象概念:资源(Resource),其主要是对文件或类路径等资源的抽象,当对Spring容器进行初始化等操作时,会将配置文件以Resource参数对象传入给Spring容器,内部再通过ResourceLoader加载该资源,本文将比较探讨下Spring对资源的处理细节。

  • Resource定义

  • 在Spring中,资源通过Resource接口作了基础定义:

    public interface Resource extends InputStreamSource {
    
    	/**
    	 * 检查资源是否物理存在
    	 */
    	boolean exists();
    
    	/**
    	 * 资源是否可读(通过getInputStream()或getFile())
    	 */
    	boolean isReadable();
    
    	/**
    	 * 资源是否已被打开
    	 */
    	boolean isOpen();
    
    	/**
    	 * 获取资源的URL
    	 */
    	URL getURL() throws IOException;
    
    	/**
    	 * 获取资源的URI
    	 */
    	URI getURI() throws IOException;
    
    	/**
    	 * 获取资源的File对象
    	 */
    	File getFile() throws IOException;
    
    	/**
    	 * 获取资源内容长度
    	 */
    	long contentLength() throws IOException;
    
    	/**
    	 * 获取资源的最近修改时间戳
    	 */
    	long lastModified() throws IOException;
    
    	/**
    	 * 创建一个相对于当前资源的资源对象
    	 */
    	Resource createRelative(String relativePath) throws IOException;
    
    	/**
    	 * 资源的文件名称
    	 */
    	String getFilename();
    
    	/**
    	 * 资源的描述信息
    	 */
    	String getDescription();
    }
        

    可以看到Resource继承自InputStreamSource,该接口定义了getInputStream()方法,用于获取资源对应的输入流对象。Spring中也提供了多种Resource实现,这可以从Resource继承树中看出:

    其中有我们比较熟悉的ClasspathResource(类路径资源,Main应用程序常用)FileSystemResource(文件系统资源)等,所有Resource的实现均继承自AbstractResource,其实现了一些基本功能:

    public abstract class AbstractResource implements Resource {
    
    	public boolean exists() {
    		try {
    			// 先从文件系统查看文件是否存在
    			return getFile().exists();
    		} catch (IOException ex) {
    			// Fall back to stream existence: can we open the stream?
    			try {
    				InputStream is = getInputStream();
    				is.close();
    				return true;
    			}
    			catch (Throwable isEx) {
    				return false;
    			}
    		}
    	}
    
    	public boolean isReadable() {
    		return true;
    	}
    
    	public boolean isOpen() {
    		return false;
    	}
    
    	/**
    	 * 需由具体子类实现
    	 */
    	public URL getURL() throws IOException {
    		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
    	}
    
    	public URI getURI() throws IOException {
    		URL url = getURL();
    		try {
    			// URL -> URI
    			return ResourceUtils.toURI(url);
    		} catch (URISyntaxException ex) {
    			throw new NestedIOException("Invalid URI [" + url + "]", ex);
    		}
    	}
    
    	/**
    	 * 需由具体子类实现
    	 */
    	public File getFile() throws IOException {
    		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
    	}
    
    	/**
    	 * 通过输入流对象读取资源内容长度
    	 */
    	public long contentLength() throws IOException {
    		InputStream is = this.getInputStream();
    		Assert.state(is != null, "resource input stream must not be null");
    		try {
    			long size = 0;
    			byte[] buf = new byte[255];
    			int read;
    			while ((read = is.read(buf)) != -1) {
    				size += read;
    			}
    			return size;
    		} finally {
    			try {
    				is.close();
    			} catch (IOException ex) {
    			}
    		}
    	}
    
    	/**
    	 * 最近修改时间
    	 */
    	public long lastModified() throws IOException {
    		long lastModified = getFileForLastModifiedCheck().lastModified();
    		if (lastModified == 0L) {
    			throw new FileNotFoundException(getDescription() +
    					" cannot be resolved in the file system for resolving its last-modified timestamp");
    		}
    		return lastModified;
    	}
    
    	protected File getFileForLastModifiedCheck() throws IOException {
    		return getFile();
    	}
    
    	/**
    	 * 需由具体子类实现
    	 */
    	public Resource createRelative(String relativePath) throws IOException {
    		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
    	}
    
    	/**
    	 * 需由具体子类实现
    	 */
    	public String getFilename() {
    		return null;
    	}
    
    	@Override
    	public String toString() {
    		return getDescription();
    	}
    
    	/**
    	 * 通过description作比较
    	 */
    	@Override
    	public boolean equals(Object obj) {
    		return (obj == this ||
    			(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
    	}
    
    	@Override
    	public int hashCode() {
    		return getDescription().hashCode();
    	}
    
    }
        

    AbstractResource似乎并没有作太多事情,只提过一些默认实现。

  • Resource实现

  • ByteArrayResource:

  • /**
     * 字节数组资源
     */    
    public class ByteArrayResource extends AbstractResource {
    
    	private final byte[] byteArray;
    
    	private final String description;
    
    	public ByteArrayResource(byte[] byteArray) {
    		this(byteArray, "resource loaded from byte array");
    	}
    
    	public ByteArrayResource(byte[] byteArray, String description) {
    		if (byteArray == null) {
    			throw new IllegalArgumentException("Byte array must not be null");
    		}
    		this.byteArray = byteArray;
    		this.description = (description != null ? description : "");
    	}
    
    	public final byte[] getByteArray() {
    		return this.byteArray;
    	}
    
    	@Override
    	public boolean exists() {
    		return true;
    	}
    
    	@Override
    	public long contentLength() {
    		return this.byteArray.length;
    	}
    
    	public InputStream getInputStream() throws IOException {
    		// 构建一个ByteArrayInputStream
    		return new ByteArrayInputStream(this.byteArray);
    	}
    
    	public String getDescription() {
    		return this.description;
    	}
    
    
    	@Override
    	public boolean equals(Object obj) {
    		return (obj == this ||
    			(obj instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
    	}
    
    	@Override
    	public int hashCode() {
    		return (byte[].class.hashCode() * 29 * this.byteArray.length);
    	}
    
    }
        

    ByteArrayResource通过内部封装一个byte[],比较简单地实现了Resource。

  • FileSystemResource:

  • /**
     * 系统文件资源
     */    
    public class FileSystemResource extends AbstractResource implements WritableResource {
    
    	private final File file;
    
    	private final String path;
    
    	public FileSystemResource(File file) {
    		Assert.notNull(file, "File must not be null");
    		this.file = file;
    		this.path = StringUtils.cleanPath(file.getPath());
    	}
    
    	public FileSystemResource(String path) {
    		Assert.notNull(path, "Path must not be null");
    		this.file = new File(path);
    		this.path = StringUtils.cleanPath(path);
    	}
    
    	public final String getPath() {
    		return this.path;
    	}
    
    	@Override
    	public boolean exists() {
    		return this.file.exists();
    	}
    
    	@Override
    	public boolean isReadable() {
    		return (this.file.canRead() && !this.file.isDirectory());
    	}
    
    	public InputStream getInputStream() throws IOException {
    		return new FileInputStream(this.file);
    	}
    
    	@Override
    	public URL getURL() throws IOException {
    		return this.file.toURI().toURL();
    	}
    
    	@Override
    	public URI getURI() throws IOException {
    		return this.file.toURI();
    	}
    
    	@Override
    	public File getFile() {
    		return this.file;
    	}
    
    	@Override
    	public long contentLength() throws IOException {
    		return this.file.length();
    	}
    
    	@Override
    	public Resource createRelative(String relativePath) {
    		String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
    		return new FileSystemResource(pathToUse);
    	}
    
    	@Override
    	public String getFilename() {
    		return this.file.getName();
    	}
    
    	public String getDescription() {
    		return "file [" + this.file.getAbsolutePath() + "]";
    	}
    
    
    	// WritableResource 接口实现
    	
    	/**
    	 * 是否可写
    	 */
    	public boolean isWritable() {
    		return (this.file.canWrite() && !this.file.isDirectory());
    	}
    
    	/**
    	 * 获取文件输出流
    	 */
    	public OutputStream getOutputStream() throws IOException {
    		return new FileOutputStream(this.file);
    	}
    
    
    	/**
    	 * This implementation compares the underlying File references.
    	 */
    	@Override
    	public boolean equals(Object obj) {
    		return (obj == this ||
    			(obj instanceof FileSystemResource && this.path.equals(((FileSystemResource) obj).path)));
    	}
    
    	/**
    	 * This implementation returns the hash code of the underlying File reference.
    	 */
    	@Override
    	public int hashCode() {
    		return this.path.hashCode();
    	}
    
    }
        

    FileSystemResource内部包装了一个File对象,并实现了WritableResource接口,可以获取到资源的输出流进行写操作等。

  • AbstractFileResolvingResource:

  • AbstractFileResolvingResource作为UrlResourceClassPathResource等的基类,主要负责将这类资源转换为File对象:

    public abstract class AbstractFileResolvingResource extends AbstractResource {
    
    	@Override
    	public File getFile() throws IOException {
    		// 由子类获取资源URL
    		URL url = getURL();
    		if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    		    // 如果是JBOSS的vfs资源
    			return VfsResourceDelegate.getResource(url).getFile();
    		}
    		// 解析URL为File对象
    		return ResourceUtils.getFile(url, getDescription());
    	}
    
    	@Override
    	protected File getFileForLastModifiedCheck() throws IOException {
    		URL url = getURL();
    		// 是否为Jar文件,包括jar, zip, vfszip, wsjar or code-source
    		if (ResourceUtils.isJarURL(url)) {
    			URL actualUrl = ResourceUtils.extractJarFileURL(url);
    			if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    				return VfsResourceDelegate.getResource(actualUrl).getFile();
    			}
    			return ResourceUtils.getFile(actualUrl, "Jar URL");
    		}
    		else {
    			return getFile();
    		}
    	}
    
    	protected File getFile(URI uri) throws IOException {
    		if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    			return VfsResourceDelegate.getResource(uri).getFile();
    		}
    		return ResourceUtils.getFile(uri, getDescription());
    	}
    
    
    	@Override
    	public boolean exists() {
    		try {
    			URL url = getURL();
    			if (ResourceUtils.isFileURL(url)) {
    				// 物理文件资源
    				return getFile().exists();
    			} else {
    				// Try a URL connection content-length header...
    				URLConnection con = url.openConnection();
    				customizeConnection(con);
    				HttpURLConnection httpCon =
    						(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
    				// 若是HTTP资源		
    				if (httpCon != null) {
    					int code = httpCon.getResponseCode();
    					if (code == HttpURLConnection.HTTP_OK) {
    						return true;
    					} else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
    						return false;
    					}
    				}
    				// 资源内容长度>0
    				if (con.getContentLength() >= 0) {
    					return true;
    				}
    				if (httpCon != null) {
    					// no HTTP OK status, and no content-length header: give up
    					httpCon.disconnect();
    					return false;
    				} else {
    					// 尝试是否可以打开输入流
    					InputStream is = getInputStream();
    					is.close();
    					return true;
    				}
    			}
    		} catch (IOException ex) {
    			return false;
    		}
    	}
    
    	@Override
    	public boolean isReadable() {
    		try {
    			URL url = getURL();
    			if (ResourceUtils.isFileURL(url)) {
    				// 处理物理文件
    				File file = getFile();
    				return (file.canRead() && !file.isDirectory());
    			} else {
    				return true;
    			}
    		}
    		catch (IOException ex) {
    			return false;
    		}
    	}
    
    	@Override
    	public long contentLength() throws IOException {
    		URL url = getURL();
    		if (ResourceUtils.isFileURL(url)) {
    			// 处理物理文件
    			return getFile().length();
    		} else {
    			// 尝试获取URL连接中的content-length头
    			URLConnection con = url.openConnection();
    			customizeConnection(con);
    			return con.getContentLength();
    		}
    	}
    
    	@Override
    	public long lastModified() throws IOException {
    		URL url = getURL();
    		if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) {
    			// 处理物理文件
    			return super.lastModified();
    		} else {
    			// 尝试获取URL连接中的last-modified头
    			URLConnection con = url.openConnection();
    			customizeConnection(con);
    			return con.getLastModified();
    		}
    	}
    
    	protected void customizeConnection(URLConnection con) throws IOException {
    		ResourceUtils.useCachesIfNecessary(con);
    		if (con instanceof HttpURLConnection) {
    			customizeConnection((HttpURLConnection) con);
    		}
    	}
    
    	protected void customizeConnection(HttpURLConnection con) throws IOException {
    		// 设置请求方法为HEAD
    		con.setRequestMethod("HEAD");
    	}
    
    	/**
    	 * JBOSS vfs资源处理类
    	 */
    	private static class VfsResourceDelegate {
    
    		public static Resource getResource(URL url) throws IOException {
    			return new VfsResource(VfsUtils.getRoot(url));
    		}
    
    		public static Resource getResource(URI uri) throws IOException {
    			return new VfsResource(VfsUtils.getRoot(uri));
    		}
    	}
    
    }
        
  • ClassPathResource:

  • public class ClassPathResource extends AbstractFileResolvingResource {
    
    	private final String path;
    
    	private ClassLoader classLoader;
    
    	private Class<?> clazz;
    
    	public ClassPathResource(String path) {
    		this(path, (ClassLoader) null);
    	}
    
    	public ClassPathResource(String path, ClassLoader classLoader) {
    		Assert.notNull(path, "Path must not be null");
    		String pathToUse = StringUtils.cleanPath(path);
    		if (pathToUse.startsWith("/")) {
    			pathToUse = pathToUse.substring(1);
    		}
    		this.path = pathToUse;
    		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    	}
    
    	public ClassPathResource(String path, Class<?> clazz) {
    		Assert.notNull(path, "Path must not be null");
    		this.path = StringUtils.cleanPath(path);
    		this.clazz = clazz;
    	}
    
    	protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
    		this.path = StringUtils.cleanPath(path);
    		this.classLoader = classLoader;
    		this.clazz = clazz;
    	}
    
    	public final String getPath() {
    		return this.path;
    	}
    
    	public final ClassLoader getClassLoader() {
    		return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
    	}
    
    
    	@Override
    	public boolean exists() {
    		return (resolveURL() != null);
    	}
    
    	/**
    	 * 解析path为URL
    	 */
    	protected URL resolveURL() {
    		if (this.clazz != null) {
    			return this.clazz.getResource(this.path);
    		} else if (this.classLoader != null) {
    			return this.classLoader.getResource(this.path);
    		} else {
    			return ClassLoader.getSystemResource(this.path);
    		}
    	}
    
    	public InputStream getInputStream() throws IOException {
    		InputStream is;
    		if (this.clazz != null) {
    			is = this.clazz.getResourceAsStream(this.path);
    		} else if (this.classLoader != null) {
    			is = this.classLoader.getResourceAsStream(this.path);
    		} else {
    			is = ClassLoader.getSystemResourceAsStream(this.path);
    		} if (is == null) {
    			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
    		}
    		return is;
    	}
    
    	@Override
    	public URL getURL() throws IOException {
    		URL url = resolveURL();
    		if (url == null) {
    			throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
    		}
    		return url;
    	}
    
    	@Override
    	public Resource createRelative(String relativePath) {
    		String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
    		return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
    	}
    
    	@Override
    	public String getFilename() {
    		return StringUtils.getFilename(this.path);
    	}
    
    	/**
    	 * This implementation returns a description that includes the class path location.
    	 */
    	public String getDescription() {
    		StringBuilder builder = new StringBuilder("class path resource [");
    		String pathToUse = path;
    		if (this.clazz != null && !pathToUse.startsWith("/")) {
    			builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
    			builder.append('/');
    		}
    		if (pathToUse.startsWith("/")) {
    			pathToUse = pathToUse.substring(1);
    		}
    		builder.append(pathToUse);
    		builder.append(']');
    		return builder.toString();
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (obj == this) {
    			return true;
    		}
    		if (obj instanceof ClassPathResource) {
    			ClassPathResource otherRes = (ClassPathResource) obj;
    			return (this.path.equals(otherRes.path) &&
    					ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
    					ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
    		}
    		return false;
    	}
    
    	/**
    	 * This implementation returns the hash code of the underlying
    	 * class path location.
    	 */
    	@Override
    	public int hashCode() {
    		return this.path.hashCode();
    	}
    
    }
        

    ClassPathResource作为较常用的资源类型,比如在初始化Spring容器时,我们通常会传入一个classpath:开头的资源,SpringSpring容器则根据这个构建一个ClassPathResource对象

  • ResourceLoader定义

  • 既然有了Resource,则还需要一个能加载Resource的组件,因为我们并不希望每次都都通过new的方式去构建一个Resource,这个组件叫ResourceLoader,其定义了加载资源的策略:

    public interface ResourceLoader {
    
    	// classpath:前缀
    	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
    
    	/**
    	 * 根据location获取资源对象,支持:
    	 * 1. 全路径:file:C:/test.dat
    	 * 2. 伪URL:classpath:test.dat
    	 * 3. 相对路径:WEB-INF/test.dat
    	 */
    	Resource getResource(String location);
    
    	/**
    	 * 暴露该ResourceLoader使用的ClassLoader
    	 */
    	ClassLoader getClassLoader();
    
    }
        

    同样Spring已经提供一些ResourceLoader的实现:

    其中DefaultResourceLoaderPathMatchingResourcePatternResolver是比较常见的,也可看到AbstractApplicationContext继承自DefaultResourceLoader,继承自AbstractApplicationContext的IoC容器就已经具备加载资源的能力。

  • ResourceLoader实现

  • DefaultResourceLoader

  • DefaultResourceLoader作为最普遍的资源加载器,可看其实现也相对简单:

     /**
      * 默认的资源加载器
      */   
     public class DefaultResourceLoader implements ResourceLoader {
    
    	private ClassLoader classLoader;
    
    	public DefaultResourceLoader() {
    		// 使用当前线程的类加载器
    		this.classLoader = ClassUtils.getDefaultClassLoader();
    	}
    
    	public DefaultResourceLoader(ClassLoader classLoader) {
    		this.classLoader = classLoader;
    	}
    
    	public void setClassLoader(ClassLoader classLoader) {
    		this.classLoader = classLoader;
    	}
    
    	public ClassLoader getClassLoader() {
    		return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    	}
    
    
    	public Resource getResource(String location) {
    		Assert.notNull(location, "Location must not be null");
    		// 如果以classpath:开头
    		if (location.startsWith(CLASSPATH_URL_PREFIX)) {
    			// 构建ClassPathResource对象
    			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    		}
    		else {
    			try {
    				// 尝试解析为URL资源
    				URL url = new URL(location);
    				return new UrlResource(url);
    			} catch (MalformedURLException ex) {
    				// 解析为上下文资源
    				return getResourceByPath(location);
    			}
    		}
    	}
    
    	protected Resource getResourceByPath(String path) {
    		return new ClassPathContextResource(path, getClassLoader());
    	}
    
    	/**
    	 * 类路径上下文资源: 如Servlet上下文或Portlet上下文
    	 */
    	private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
    
    		public ClassPathContextResource(String path, ClassLoader classLoader) {
    			super(path, classLoader);
    		}
    
    		public String getPathWithinContext() {
    			return getPath();
    		}
    
    		@Override
    		public Resource createRelative(String relativePath) {
    			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
    			return new ClassPathContextResource(pathToUse, getClassLoader());
    		}
    	}
    
    }    
        
  • ResourcePatternResolver

  • ResourcePatternResolver是对ResourceLoader的扩展,具备匹配资源模式(如Ant-Style)的功能,即将某一模式的资源location转为Resource数据,并增加了classpath*:前缀的资源解析:

    public interface ResourcePatternResolver extends ResourceLoader {
    
    	// classpath*: 包括jar中的资源,这将有利于组件化
    	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    
    	/**
    	 * 根据模式获取Resource数组
    	 */
    	Resource[] getResources(String locationPattern) throws IOException;
    
    }
        
  • PathMatchingResourcePatternResolver

  • PathMatchingResourcePatternResolver作为ResourcePatternResolver的实现,被很多地方使用,如ClassPathXmlApplicationContext容器初始化AbstractBeanDefinitionReader时,就是使用PathMatchingResourcePatternResolver作为资源加载器:

    // 实例化BeanDefinitionReader
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
    	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    	this.registry = registry;
    
    	// 初始化资源加载器
    	if (this.registry instanceof ResourceLoader) {
    		this.resourceLoader = (ResourceLoader) this.registry;
    	} else {
    		this.resourceLoader = new PathMatchingResourcePatternResolver();
    	}
        ...
    }
        

    PathMatchingResourcePatternResolver如何实现资源加载:

    public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    
    	private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
    
    	private static Method equinoxResolveMethod;
    
    	static {
    		try {
    			// 检测Equinox OSGi
    			Class<?> fileLocatorClass = ClassUtils.forName("org.eclipse.core.runtime.FileLocator",
    					PathMatchingResourcePatternResolver.class.getClassLoader());
    			equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
    			logger.debug("Found Equinox FileLocator for OSGi bundle URL resolution");
    		} catch (Throwable ex) {
    			equinoxResolveMethod = null;
    		}
    	}
    
    	private final ResourceLoader resourceLoader;
    
    	private PathMatcher pathMatcher = new AntPathMatcher();
    
    	public PathMatchingResourcePatternResolver() {
    		// 内部默认使用DefaultResourceLoader
    		this.resourceLoader = new DefaultResourceLoader();
    	}
    
    	public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
    		Assert.notNull(resourceLoader, "ResourceLoader must not be null");
    		this.resourceLoader = resourceLoader;
    	}
    
    	public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
    		this.resourceLoader = new DefaultResourceLoader(classLoader);
    	}
    
    	public ResourceLoader getResourceLoader() {
    		return this.resourceLoader;
    	}
    
    	public ClassLoader getClassLoader() {
    		return getResourceLoader().getClassLoader();
    	}
    
    	public void setPathMatcher(PathMatcher pathMatcher) {
    		Assert.notNull(pathMatcher, "PathMatcher must not be null");
    		this.pathMatcher = pathMatcher;
    	}
    
    	public PathMatcher getPathMatcher() {
    		return this.pathMatcher;
    	}
    
    	public Resource getResource(String location) {
    		return getResourceLoader().getResource(location);
    	}
    
    	public Resource[] getResources(String locationPattern) throws IOException {
    		Assert.notNull(locationPattern, "Location pattern must not be null");
    		// 以classpath*:开头
    		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
    			// 是否是一个pattern,包含*或者?
    			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
    				// 匹配查询资源
    				return findPathMatchingResources(locationPattern);
    			} else {
    				// 没有*或?,则不需要匹配操作,
    				// 通常是某个目录classpath或者某个类classpath,
    				// 可以是jar中的,若是jar中则会以!/作为分隔符
    				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
    			}
    		} else {
    			// 非classpath*:
    			int prefixEnd = locationPattern.indexOf(":") + 1;
    			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
    				// 匹配查询资源
    				return findPathMatchingResources(locationPattern);
    			} else {
    				// 没有*或?,则不需要匹配操作
    				return new Resource[] {getResourceLoader().getResource(locationPattern)};
    			}
    		}
    	}
    
    	/**
    	 * 查询所有类路径资源
    	 */
    	protected Resource[] findAllClassPathResources(String location) throws IOException {
    		String path = location;
    		if (path.startsWith("/")) {
    			path = path.substring(1);
    		}
    		ClassLoader cl = getClassLoader();
    		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
    		Set<Resource> result = new LinkedHashSet<Resource>(16);
    		while (resourceUrls.hasMoreElements()) {
    			URL url = resourceUrls.nextElement();
    			result.add(convertClassLoaderURL(url));
    		}
    		return result.toArray(new Resource[result.size()]);
    	}
    
    	protected Resource convertClassLoaderURL(URL url) {
    		return new UrlResource(url);
    	}
    
    	/**
    	 * 通过AntPathMacher查询类路径中匹配的资源,包括jar,zip和系统文件
    	 */
    	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    		// 获取匹配根目录
    		String rootDirPath = determineRootDir(locationPattern);
    		String subPattern = locationPattern.substring(rootDirPath.length());
    		Resource[] rootDirResources = getResources(rootDirPath);
    		Set<Resource> result = new LinkedHashSet<Resource>(16);
    		for (Resource rootDirResource : rootDirResources) {
    			rootDirResource = resolveRootDirResource(rootDirResource);
    			if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    				// Jboss VFS
    				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
    			} else if (isJarResource(rootDirResource)) {
    				// 从jar包中解析资源
    				result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
    			} else {
    				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
    			}
    		}
    		return result.toArray(new Resource[result.size()]);
    	}
    
    	/**
    	 * 获取location根目录
    	 */
    	protected String determineRootDir(String location) {
    		int prefixEnd = location.indexOf(":") + 1;
    		int rootDirEnd = location.length();
    		while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
    			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
    		}
    		if (rootDirEnd == 0) {
    			rootDirEnd = prefixEnd;
    		}
    		return location.substring(0, rootDirEnd);
    	}
    
    	/**
    	 * 解析OSGI中的bundle开头的资源
    	 */
    	protected Resource resolveRootDirResource(Resource original) throws IOException {
    		if (equinoxResolveMethod != null) {
    			URL url = original.getURL();
    			if (url.getProtocol().startsWith("bundle")) {
    				return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
    			}
    		}
    		return original;
    	}
    
    	/**
    	 * 是否是jar文件资源: jar, zip, vfszip, wsjar, code-source, !/
    	 */
    	protected boolean isJarResource(Resource resource) throws IOException {
    		return ResourceUtils.isJarURL(resource.getURL());
    	}
    
    	/**
    	 * 基于Ant-Style模式匹配找出Jar文件中配置的资源
    	 */
    	protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
    			throws IOException {
    
    		URLConnection con = rootDirResource.getURL().openConnection();
    		JarFile jarFile;
    		String jarFileUrl;
    		String rootEntryPath;
    		boolean newJarFile = false;
    
    		if (con instanceof JarURLConnection) {
    			// 如果是JAR连接
    			JarURLConnection jarCon = (JarURLConnection) con;
    			ResourceUtils.useCachesIfNecessary(jarCon);
    			jarFile = jarCon.getJarFile();
    			jarFileUrl = jarCon.getJarFileURL().toExternalForm();
    			JarEntry jarEntry = jarCon.getJarEntry();
    			rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
    		} else {
    			// 去除!/分隔符
    			String urlFile = rootDirResource.getURL().getFile();
    			int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
    			if (separatorIndex != -1) {
    				jarFileUrl = urlFile.substring(0, separatorIndex);
    				rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
    				jarFile = getJarFile(jarFileUrl);
    			} else {
    				jarFile = new JarFile(urlFile);
    				jarFileUrl = urlFile;
    				rootEntryPath = "";
    			}
    			newJarFile = true;
    		}
    
    		try {
    			
    			if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
    				// Root entry path must end with slash to allow for proper matching.
    				// The Sun JRE does not return a slash here, but BEA JRockit does.
    				rootEntryPath = rootEntryPath + "/";
    			}
    			Set<Resource> result = new LinkedHashSet<Resource>(8);
    			// 遍历jar文件中的资源,并进行匹配
    			for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
    				JarEntry entry = entries.nextElement();
    				String entryPath = entry.getName();
    				if (entryPath.startsWith(rootEntryPath)) {
    					String relativePath = entryPath.substring(rootEntryPath.length());
    					if (getPathMatcher().match(subPattern, relativePath)) {
    						result.add(rootDirResource.createRelative(relativePath));
    					}
    				}
    			}
    			return result;
    		} finally {
    			// 关闭InputStream
    			if (newJarFile) {
    				jarFile.close();
    			}
    		}
    	}
    
    	/**
    	 * 解析Jar文件URL为JarFile对象
    	 */
    	protected JarFile getJarFile(String jarFileUrl) throws IOException {
    		if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
    			try {
    				return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
    			} catch (URISyntaxException ex) {
    				return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
    			}
    		} else {
    			return new JarFile(jarFileUrl);
    		}
    	}
    
    	/**
    	 * 通过AntPathMacher查询文件系统中匹配的资源
    	 */
    	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
    			throws IOException {
    
    		File rootDir;
    		try {
    			rootDir = rootDirResource.getFile().getAbsoluteFile();
    		} catch (IOException ex) {
    			if (logger.isWarnEnabled()) {
    				logger.warn("Cannot search for matching files underneath " + rootDirResource +
    						" because it does not correspond to a directory in the file system", ex);
    			}
    			return Collections.emptySet();
    		}
    		return doFindMatchingFileSystemResources(rootDir, subPattern);
    	}
    
    	/**
    	 * 通过AntPathMacher查询文件系统中匹配的资源
    	 */
    	protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
    		if (logger.isDebugEnabled()) {
    			logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
    		}
    		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
    		Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
    		for (File file : matchingFiles) {
    			result.add(new FileSystemResource(file));
    		}
    		return result;
    	}
    
    	/**
    	 * 检索目录及子目录中匹配的文件
    	 */
    	protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
    		if (!rootDir.exists()) {
    			// 不存在跳过
    			return Collections.emptySet();
    		}
    		if (!rootDir.isDirectory()) {
    			// 非目录
    			return Collections.emptySet();
    		}
    		if (!rootDir.canRead()) {
    			// 不可读
    			return Collections.emptySet();
    		}
    		// 讲目录分隔符统一为/
    		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
    		if (!pattern.startsWith("/")) {
    			fullPattern += "/";
    		}
    		// 配置的全路径
    		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
    		Set<File> result = new LinkedHashSet<File>(8);
    		// 递归获取匹配的文件
    		doRetrieveMatchingFiles(fullPattern, rootDir, result);
    		return result;
    	}
    
    	/**
    	 * 递归获取匹配的文件
    	 */
    	protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
    		File[] dirContents = dir.listFiles();
    		if (dirContents == null) {
    			// 空目录不处理
    			return;
    		}
    		for (File content : dirContents) {
    			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
    			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
    				if (!content.canRead()) {
    					// 跳过不可读的目录
    				} else {
    					// 递归获取匹配的文件
    					doRetrieveMatchingFiles(fullPattern, content, result);
    				}
    			}
    			if (getPathMatcher().match(fullPattern, currPath)) {
    				// 匹配
    				result.add(content);
    			}
    		}
    	}
    
    	// For JBoss VFS
    	...
    }
        

    PathMatchingResourcePatternResolver中包装了一个比较重要的路径匹配器AntPathMatcher完成了路径匹配工作,匹配规范为Ant-Style,主要匹配工作可参考AntPathMatcher.doMatch方法,具体的使用可以参照下其测试用例:

    // test exact matching
    assertTrue(pathMatcher.match("test", "test"));
    assertTrue(pathMatcher.match("/test", "/test"));
    assertFalse(pathMatcher.match("/test.jpg", "test.jpg"));
    assertFalse(pathMatcher.match("test", "/test"));
    assertFalse(pathMatcher.match("/test", "test"));
    
    // test matching with ?'s
    assertTrue(pathMatcher.match("t?st", "test"));
    assertTrue(pathMatcher.match("??st", "test"));
    assertTrue(pathMatcher.match("tes?", "test"));
    assertTrue(pathMatcher.match("te??", "test"));
    assertTrue(pathMatcher.match("?es?", "test"));
    assertFalse(pathMatcher.match("tes?", "tes"));
    assertFalse(pathMatcher.match("tes?", "testt"));
    assertFalse(pathMatcher.match("tes?", "tsst"));
    
    // test matchin with *'s
    assertTrue(pathMatcher.match("*", "test"));
    assertTrue(pathMatcher.match("test*", "test"));
    assertTrue(pathMatcher.match("test*", "testTest"));
    assertTrue(pathMatcher.match("test/*", "test/Test"));
    assertTrue(pathMatcher.match("test/*", "test/t"));
    assertTrue(pathMatcher.match("test/*", "test/"));
    assertTrue(pathMatcher.match("*test*", "AnothertestTest"));
    assertTrue(pathMatcher.match("*test", "Anothertest"));
    assertTrue(pathMatcher.match("*.*", "test."));
    assertTrue(pathMatcher.match("*.*", "test.test"));
    assertTrue(pathMatcher.match("*.*", "test.test.test"));
    assertTrue(pathMatcher.match("test*aaa", "testblaaaa"));
    assertFalse(pathMatcher.match("test*", "tst"));
    assertFalse(pathMatcher.match("test*", "tsttest"));
    assertFalse(pathMatcher.match("test*", "test/"));
    assertFalse(pathMatcher.match("test*", "test/t"));
    assertFalse(pathMatcher.match("test/*", "test"));
    assertFalse(pathMatcher.match("*test*", "tsttst"));
    assertFalse(pathMatcher.match("*test", "tsttst"));
    assertFalse(pathMatcher.match("*.*", "tsttst"));
    assertFalse(pathMatcher.match("test*aaa", "test"));
    assertFalse(pathMatcher.match("test*aaa", "testblaaab"));
    
    // test matching with ?'s and /'s
    assertTrue(pathMatcher.match("/?", "/a"));
    assertTrue(pathMatcher.match("/?/a", "/a/a"));
    assertTrue(pathMatcher.match("/a/?", "/a/b"));
    assertTrue(pathMatcher.match("/??/a", "/aa/a"));
    assertTrue(pathMatcher.match("/a/??", "/a/bb"));
    assertTrue(pathMatcher.match("/?", "/a"));
    
    // test matching with **'s
    assertTrue(pathMatcher.match("/**", "/testing/testing"));
    assertTrue(pathMatcher.match("/*/**", "/testing/testing"));
    assertTrue(pathMatcher.match("/**/*", "/testing/testing"));
    assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla"));
    assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla"));
    assertTrue(pathMatcher.match("/**/test", "/bla/bla/test"));
    assertTrue(pathMatcher.match("/bla/**/**/bla", "/bla/bla/bla/bla/bla/bla"));
    assertTrue(pathMatcher.match("/bla*bla/test", "/blaXXXbla/test"));
    assertTrue(pathMatcher.match("/*bla/test", "/XXXbla/test"));
    assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test"));
    assertFalse(pathMatcher.match("/*bla/test", "XXXblab/test"));
    assertFalse(pathMatcher.match("/*bla/test", "XXXbl/test"));
    
    assertFalse(pathMatcher.match("/????", "/bala/bla"));
    assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb"));
    assertTrue(pathMatcher.match("/**/*bla", "/bla/bla/bla"));
    
    assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/"));
    assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing"));
    assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing"));
    assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg"));
    
    assertTrue(pathMatcher.match("*bla*/**/bla/**", "XXXblaXXXX/testing/testing/bla/testing/testing/"));
    assertTrue(pathMatcher.match("*bla*/**/bla/*", "XXXblaXXXX/testing/testing/bla/testing"));
    assertTrue(pathMatcher.match("*bla*/**/bla/**", "XXXblaXXXX/testing/testing/bla/testing/testing"));
    assertFalse(pathMatcher.match("*bla*/**/bla/*", "XXXblaXXXX/testing/testing/bla/testing/testing"));
    
    assertFalse(pathMatcher.match("/x/x/**/bla", "/x/x/x/"));
    
    assertTrue(pathMatcher.match("", ""));
    
    assertTrue(pathMatcher.match("/{bla}.*", "/testing.html"));
        

    以上则是一些有关Spring中资源管理的小部分细节,平时还比较常用的地方就是在Spring配置文件中使用import标签:

    <import resource="classpath:one-context.xml" />
    <import resource="classpath*:two-context.xml" />
        

    import时,Spring IoC容器会进一步加载resource资源,但classpath:one-context.xml会加载类路径中的查询到的第一个one-context.xml,而classpath*:two-context.xml还会加载类路径中多个jar中的two-context.xml,本质则是因为classpath:开头的资源,最终调用ClassLoader.getResource()方法,classpath*:开头的资源,最终调用ClassLoader.getResources()方法,可以将组件通过在jar包中提供一个配置文件(名字应规范,避免冲突),外部只需通过classpath:前缀import就能使用该组件。

好人,一生平安。