Spring DefaultResourceLoader源码详解(详细注释版)
1. ResourceLoader接口源码
/*
* ResourceLoader接口是Spring框架中用于加载资源的核心接口
* 提供了统一的资源访问机制,屏蔽了不同资源类型的差异
* 是Spring资源抽象体系的基础接口
*
* ResourceLoader的作用:
* 1. 统一的资源访问入口
* 2. 支持多种资源类型(文件、URL、类路径等)
* 3. 资源定位和加载
* 4. 与ApplicationContext集成
*
* 注意事项:
* - ResourceLoader不保证资源的存在性
* - 需要在实际访问资源时处理IOException
* - 支持各种资源前缀(classpath:、file:、http:等)
*/
package org.springframework.core.io;
import org.springframework.lang.Nullable;
import org.springframework.util.ResourceUtils;
/**
* 用于加载资源的策略接口(例如类路径或文件系统资源)
*
* ResourceLoader在Spring框架中的作用:
* 1. 提供统一的资源加载策略
* 2. 支持多种资源访问协议
* 3. 与IoC容器集成
* 4. 支持资源路径解析
*
* 常见的资源前缀:
* - classpath: 从类路径加载资源
* - file: 从文件系统加载资源
* - http: 从HTTP URL加载资源
* - jar: 从JAR文件加载资源
*
* 执行时机:
* 1. 应用上下文初始化时
* 2. 配置文件加载时
* 3. 资源依赖注入时
* 4. 运行时资源访问时
*/
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:" */
String CLASSPATH_URL_PREFIX = "classpath:";
/**
* 根据给定的位置字符串返回Resource句柄
*
* 此方法不保证资源的实际存在
* 需要在实际访问资源内容时处理IOException
*
* @param location 资源位置路径
* @return 对应的Resource对象
*
* 使用场景:
* - 加载配置文件
* - 访问静态资源
* - 读取模板文件
* - 加载属性文件
*
* 支持的位置格式:
* - classpath:test.dat (类路径资源)
* - file:///data/test.dat (文件系统资源)
* - http://myserver/test.dat (HTTP资源)
* - /data/test.dat (相对于ResourceLoader根路径)
*/
Resource getResource(String location);
/**
* 暴露此ResourceLoader使用的ClassLoader
*
* @return ClassLoader实例,如果使用默认ClassLoader则返回null
*
* 使用场景:
* - 类加载器相关的资源加载
* - 动态类加载
* - 插件化架构
*/
@Nullable
ClassLoader getClassLoader();
}
2. DefaultResourceLoader核心实现
/*
* DefaultResourceLoader是ResourceLoader接口的默认实现
* 提供了标准的资源加载策略和协议支持
*/
package org.springframework.core.io;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* ResourceLoader接口的默认实现
*
* DefaultResourceLoader提供了以下功能:
* 1. 标准的资源加载策略
* 2. 协议处理器注册机制
* 3. 类路径和文件系统资源支持
* 4. URL资源支持
* 5. 可扩展的协议支持
*
* 支持的资源类型:
* - ClassPathResource: 类路径资源
* - FileSystemResource: 文件系统资源
* - UrlResource: URL资源
* - ByteArrayResource: 字节数组资源
* - InputStreamResource: 输入流资源
*
* 协议处理器:
* - 可以为特定协议注册自定义处理器
* - 支持扩展新的资源协议
* - 提供默认的协议处理策略
*/
public class DefaultResourceLoader implements ResourceLoader {
// 日志记录器
private static final Log logger = LogFactory.getLog(DefaultResourceLoader.class);
// 协议解析器映射表
private final Map<String, ProtocolResolver> protocolResolvers = new ConcurrentHashMap<>(4);
// 类加载器
@Nullable
private ClassLoader classLoader;
/**
* 默认构造方法
* 使用默认的类加载器
*/
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
/**
* 带类加载器的构造方法
*
* @param classLoader 要使用的ClassLoader
*/
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 设置类加载器
*
* @param classLoader 要设置的ClassLoader
*/
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 获取类加载器
*
* @return 当前使用的ClassLoader
*/
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
/**
* 根据位置获取资源
* 这是ResourceLoader的核心方法
*
* @param location 资源位置
* @return Resource对象
*/
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 首先尝试使用协议解析器
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 处理标准资源位置
return getResourceByPath(location);
}
/**
* 根据路径获取资源
* 这是获取资源的主要实现方法
*
* @param path 资源路径
* @return Resource对象
*/
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
/**
* 添加协议解析器
*
* @param protocolResolver 要添加的协议解析器
*/
public void addProtocolResolver(ProtocolResolver protocolResolver) {
Assert.notNull(protocolResolver, "ProtocolResolver must not be null");
this.protocolResolvers.put(protocolResolver.getProtocol(), protocolResolver);
}
/**
* 移除协议解析器
*
* @param protocolResolver 要移除的协议解析器
* @return 如果成功移除返回true
*/
public boolean removeProtocolResolver(ProtocolResolver protocolResolver) {
return this.protocolResolvers.remove(protocolResolver.getProtocol(), protocolResolver);
}
/**
* 获取所有协议解析器
*
* @return 协议解析器集合
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers.values();
}
/**
* 基于路径的资源实现
* 内部类,用于处理基于路径的资源
*/
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
// 路径
private final String path;
/**
* 构造方法
*
* @param path 资源路径
* @param classLoader 类加载器
*/
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
this.path = path;
}
/**
* 获取路径
*
* @return 资源路径
*/
@Override
public String getPathWithinContext() {
return this.path;
}
/**
* 创建相对资源
*
* @param relativePath 相对路径
* @return 相对资源
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
}
3. Resource接口及其实现
/*
* Resource接口及其实现类
* 提供了统一的资源访问API
*/
/**
* Resource接口
* Spring资源抽象的核心接口
*/
public interface Resource extends InputStreamSource {
/**
* 判断资源是否存在
*
* @return 如果资源存在返回true
*/
boolean exists();
/**
* 判断资源是否可读
*
* @return 如果资源可读返回true
*/
default boolean isReadable() {
return exists();
}
/**
* 判断资源是否已打开
*
* @return 如果资源已打开返回true
*/
default boolean isOpen() {
return false;
}
/**
* 判断资源是否为文件
*
* @return 如果资源是文件返回true
*/
default boolean isFile() {
return false;
}
/**
* 获取资源的URL
*
* @return 资源的URL
* @throws IOException 如果无法解析URL
*/
URL getURL() throws IOException;
/**
* 获取资源的URI
*
* @return 资源的URI
* @throws IOException 如果无法解析URI
*/
URI getURI() throws IOException;
/**
* 获取资源的文件
*
* @return 资源对应的文件
* @throws IOException 如果无法解析为文件
*/
File getFile() throws IOException;
/**
* 获取资源内容长度
*
* @return 资源内容长度
* @throws IOException 如果无法获取长度
*/
long contentLength() throws IOException;
/**
* 获取资源最后修改时间
*
* @return 最后修改时间(毫秒)
* @throws IOException 如果无法获取修改时间
*/
long lastModified() throws IOException;
/**
* 基于当前资源创建相对资源
*
* @param relativePath 相对路径
* @return 相对资源
* @throws IOException 如果无法创建相对资源
*/
Resource createRelative(String relativePath) throws IOException;
/**
* 获取资源文件名
*
* @return 资源文件名
*/
@Nullable
String getFilename();
/**
* 获取资源描述
*
* @return 资源描述
*/
String getDescription();
}
/**
* ClassPathResource类
* 用于访问类路径资源
*/
public class ClassPathResource extends AbstractFileResolvingResource {
// 路径
private final String path;
// 类加载器
@Nullable
private ClassLoader classLoader;
// 类
@Nullable
private Class<?> clazz;
/**
* 构造方法 - 使用类加载器
*
* @param path 资源路径
* @param classLoader 类加载器
*/
public ClassPathResource(String path, @Nullable 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());
}
/**
* 构造方法 - 使用类
*
* @param path 资源路径
* @param clazz 类
*/
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
/**
* 判断资源是否存在
*/
@Override
public boolean exists() {
return (resolveURL() != null);
}
/**
* 解析URL
*/
@Nullable
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);
}
}
/**
* 获取输入流
*/
@Override
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;
}
/**
* 获取URL
*/
@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 String getDescription() {
StringBuilder builder = new StringBuilder("class path resource [");
String pathToUse = this.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(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ClassPathResource)) {
return false;
}
ClassPathResource otherRes = (ClassPathResource) other;
return (this.path.equals(otherRes.path) &&
ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
}
/**
* 获取哈希码
*/
@Override
public int hashCode() {
return this.path.hashCode();
}
}
/**
* FileSystemResource类
* 用于访问文件系统资源
*/
public class FileSystemResource extends AbstractResource implements WritableResource {
// 文件
private final File file;
// 路径
private final String path;
/**
* 构造方法 - 使用文件
*
* @param file 文件
*/
public FileSystemResource(File file) {
Assert.notNull(file, "File must not be null");
this.file = file;
this.path = StringUtils.cleanPath(file.getPath());
}
/**
* 构造方法 - 使用路径
*
* @param path 文件路径
*/
public FileSystemResource(String path) {
Assert.notNull(path, "Path must not be null");
this.file = new File(path);
this.path = StringUtils.cleanPath(path);
}
/**
* 判断资源是否存在
*/
@Override
public boolean exists() {
return this.file.exists();
}
/**
* 判断资源是否可读
*/
@Override
public boolean isReadable() {
return (this.file.canRead() && !this.file.isDirectory());
}
/**
* 获取输入流
*/
@Override
public InputStream getInputStream() throws IOException {
return Files.newInputStream(this.file.toPath());
}
/**
* 判断资源是否可写
*/
@Override
public boolean isWritable() {
return (this.file.canWrite() && !this.file.isDirectory());
}
/**
* 获取输出流
*/
@Override
public OutputStream getOutputStream() throws IOException {
return Files.newOutputStream(this.file.toPath());
}
/**
* 获取URL
*/
@Override
public URL getURL() throws IOException {
return this.file.toURI().toURL();
}
/**
* 获取URI
*/
@Override
public URI getURI() throws IOException {
return this.file.toURI();
}
/**
* 获取文件
*/
@Override
public File getFile() {
return this.file;
}
/**
* 判断是否为文件
*/
@Override
public boolean isFile() {
return true;
}
/**
* 获取内容长度
*/
@Override
public long contentLength() throws IOException {
return this.file.length();
}
/**
* 获取最后修改时间
*/
@Override
public long lastModified() throws IOException {
return this.file.lastModified();
}
/**
* 创建相对资源
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new FileSystemResource(pathToUse);
}
/**
* 获取文件名
*/
@Override
@Nullable
public String getFilename() {
return this.file.getName();
}
/**
* 获取资源描述
*/
@Override
public String getDescription() {
return "file [" + this.file.getAbsolutePath() + "]";
}
/**
* 判断是否相等
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof FileSystemResource &&
this.path.equals(((FileSystemResource) other).path)));
}
/**
* 获取哈希码
*/
@Override
public int hashCode() {
return this.path.hashCode();
}
}
/**
* UrlResource类
* 用于访问URL资源
*/
public class UrlResource extends AbstractFileResolvingResource {
// 原始URI
@Nullable
private final URI uri;
// URL
private final URL url;
// 清理后的URL字符串
@Nullable
private volatile String cleanedUrl;
/**
* 构造方法 - 使用URL
*
* @param url URL
*/
public UrlResource(URL url) {
Assert.notNull(url, "URL must not be null");
this.url = url;
this.uri = null;
}
/**
* 构造方法 - 使用URI
*
* @param uri URI
*/
public UrlResource(URI uri) throws MalformedURLException {
Assert.notNull(uri, "URI must not be null");
this.url = uri.toURL();
this.uri = uri;
}
/**
* 构造方法 - 使用路径
*
* @param path 路径
*/
public UrlResource(String path) throws MalformedURLException {
Assert.notNull(path, "Path must not be null");
this.uri = null;
this.url = new URL(path);
this.cleanedUrl = path;
}
/**
* 获取清理后的URL
*/
private String getCleanedUrl(URL url, String originalPath) {
String cleanedUrl = this.cleanedUrl;
if (cleanedUrl != null) {
return cleanedUrl;
}
cleanedUrl = StringUtils.cleanPath(originalPath);
this.cleanedUrl = cleanedUrl;
return cleanedUrl;
}
/**
* 判断资源是否存在
*/
@Override
public boolean exists() {
return true; // URL资源假定存在
}
/**
* 获取输入流
*/
@Override
public InputStream getInputStream() throws IOException {
return this.url.openStream();
}
/**
* 获取URL
*/
@Override
public URL getURL() throws IOException {
return this.url;
}
/**
* 获取URI
*/
@Override
public URI getURI() throws IOException {
if (this.uri != null) {
return this.uri;
}
return super.getURI();
}
/**
* 获取资源描述
*/
@Override
public String getDescription() {
return "URL [" + this.url + "]";
}
/**
* 判断是否相等
*/
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof UrlResource)) {
return false;
}
UrlResource otherRes = (UrlResource) other;
return (this.url.equals(otherRes.url));
}
/**
* 获取哈希码
*/
@Override
public int hashCode() {
return this.url.hashCode();
}
}
4. ProtocolResolver协议解析器
/*
* ProtocolResolver接口及其实现
* 支持自定义协议解析
*/
/**
* 协议解析器接口
* 允许为特定协议注册自定义资源解析策略
*/
@FunctionalInterface
public interface ProtocolResolver {
/**
* 解析指定位置的资源
*
* @param location 资源位置
* @param resourceLoader 资源加载器
* @return 解析的资源,如果无法解析则返回null
*/
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);
/**
* 获取协议名称
*
* @return 协议名称
*/
default String getProtocol() {
return getClass().getSimpleName();
}
}
/**
* 自定义协议解析器示例
*/
public class CustomProtocolResolver implements ProtocolResolver {
private static final String CUSTOM_PROTOCOL = "custom:";
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith(CUSTOM_PROTOCOL)) {
String path = location.substring(CUSTOM_PROTOCOL.length());
// 根据自定义逻辑解析资源
return new CustomResource(path);
}
return null;
}
@Override
public String getProtocol() {
return "custom";
}
}
/**
* 自定义资源实现
*/
public class CustomResource extends AbstractResource {
private final String path;
public CustomResource(String path) {
this.path = path;
}
@Override
public boolean exists() {
// 自定义存在性检查逻辑
return true;
}
@Override
public InputStream getInputStream() throws IOException {
// 自定义输入流获取逻辑
return new ByteArrayInputStream("Custom resource content".getBytes());
}
@Override
public String getDescription() {
return "custom resource [" + this.path + "]";
}
}
5. ResourcePatternResolver资源模式解析器
/*
* ResourcePatternResolver接口
* 支持通配符模式的资源解析
*/
/**
* ResourceLoader的扩展接口
* 支持Ant风格的路径模式解析
*/
public interface ResourcePatternResolver extends ResourceLoader {
/**
* Pseudo URL prefix for all matching resources from the class path: "classpath*:"
* This differs from ResourceLoader's classpath URL prefix in that it
* retrieves all matching resources for a given name (e.g. "/beans.xml"),
* for example in the root of all deployed JAR files.
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* 解析给定位置模式到Resource对象
*
* @param locationPattern 位置模式
* @return 匹配的Resource对象数组
* @throws IOException 如果解析过程中发生错误
*/
Resource[] getResources(String locationPattern) throws IOException;
}
/**
* PathMatchingResourcePatternResolver实现
* DefaultResourceLoader的扩展,支持路径匹配
*/
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
// 日志记录器
private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
// 内部ResourceLoader
private final ResourceLoader resourceLoader;
// 路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();
/**
* 默认构造方法
*/
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
/**
* 带ResourceLoader的构造方法
*/
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
/**
* 带ClassLoader的构造方法
*/
public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
this.resourceLoader = new DefaultResourceLoader(classLoader);
}
/**
* 获取ResourceLoader
*/
@Override
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
/**
* 获取ClassLoader
*/
@Override
@Nullable
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;
}
/**
* 根据位置获取资源
*/
@Override
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
/**
* 根据位置模式获取资源数组
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 处理classpath*:前缀
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// classpath*:模式处理
return findPathMatchingResources(locationPattern);
}
// 处理标准classpath:前缀
else if (getResourceLoader() instanceof ResourcePatternResolver) {
// 委托给底层ResourcePatternResolver
return ((ResourcePatternResolver) getResourceLoader()).getResources(locationPattern);
}
else {
// 标准ResourceLoader处理
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
/**
* 查找路径匹配的资源
*/
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<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
// 根据URL协议处理不同类型的资源
if (rootDirUrl.getProtocol().startsWith("jar")) {
// JAR文件处理
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
// 文件系统处理
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
/**
* 确定根目录
*/
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);
}
/**
* 解析根目录资源
*/
protected Resource resolveRootDirResource(Resource original) throws IOException {
return original;
}
/**
* 查找路径匹配的JAR资源
*/
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
URLConnection con = rootDirURL.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean closeJarFile;
if (con instanceof JarURLConnection) {
// 应该通常为这种情况
JarURLConnection jarCon = (JarURLConnection) con;
ResourceUtils.useCachesIfNecessary(jarCon);
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
else {
// 没有JarURLConnection -> 需要自己处理jar文件
String urlFile = rootDirURL.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 = "";
}
closeJarFile = true;
}
try {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
}
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// 根目录必须以斜杠结尾才能正确搜索
rootEntryPath = rootEntryPath + "/";
}
Set<Resource> result = new LinkedHashSet<>(8);
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 {
// 关闭jar文件,但仅在必要时
if (closeJarFile) {
jarFile.close();
}
}
}
/**
* 查找路径匹配的文件资源
*/
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
File rootDir;
try {
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot search for matching files underneath " + rootDirResource +
" because it does not correspond to a directory in the file system", ex);
}
return Collections.emptySet();
}
return doFindMatchingFileResources(rootDir, subPattern);
}
/**
* 查找匹配的文件资源
*/
protected Set<Resource> doFindMatchingFileResources(File rootDir, String pattern) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
}
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, pattern);
Set<Resource> result = new LinkedHashSet<>(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()) {
// 沉默地跳过不存在的目录
if (logger.isDebugEnabled()) {
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
}
return Collections.emptySet();
}
if (!rootDir.isDirectory()) {
// 沉默地跳过非目录
if (logger.isDebugEnabled()) {
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
}
return Collections.emptySet();
}
if (!rootDir.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping search for matching files underneath directory [" + rootDir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
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<>(8);
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}
/**
* 执行匹配文件的检索
*/
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
for (File content : listDirectory(dir)) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
/**
* 列出目录内容
*/
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isWarnEnabled()) {
logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files);
return files;
}
}
6. 实际使用示例
/*
* ResourceLoader实际使用示例
*/
/**
* 资源加载服务示例
*/
@Service
public class ResourceLoadingService {
private static final Logger logger = LoggerFactory.getLogger(ResourceLoadingService.class);
// 注入ResourceLoader
@Autowired
private ResourceLoader resourceLoader;
// 注入ResourcePatternResolver
@Autowired
private ResourcePatternResolver resourcePatternResolver;
/**
* 加载单个资源
*/
public String loadResource(String location) {
try {
Resource resource = resourceLoader.getResource(location);
// 检查资源是否存在
if (!resource.exists()) {
logger.warn("资源不存在: {}", location);
return null;
}
// 读取资源内容
try (InputStream inputStream = resource.getInputStream()) {
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
} catch (IOException e) {
logger.error("加载资源失败: {}", location, e);
return null;
}
}
/**
* 加载多个资源
*/
public List<String> loadResources(String pattern) {
List<String> contents = new ArrayList<>();
try {
Resource[] resources = resourcePatternResolver.getResources(pattern);
for (Resource resource : resources) {
try (InputStream inputStream = resource.getInputStream()) {
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
contents.add(content);
}
}
} catch (IOException e) {
logger.error("加载资源模式失败: {}", pattern, e);
}
return contents;
}
/**
* 加载配置文件
*/
public Properties loadProperties(String location) {
Properties properties = new Properties();
try {
Resource resource = resourceLoader.getResource(location);
try (InputStream inputStream = resource.getInputStream()) {
properties.load(inputStream);
}
} catch (IOException e) {
logger.error("加载属性文件失败: {}", location, e);
}
return properties;
}
/**
* 加载模板文件
*/
public String loadTemplate(String templateName) {
String location = "classpath:templates/" + templateName + ".tpl";
return loadResource(location);
}
/**
* 获取资源信息
*/
public ResourceInfo getResourceInfo(String location) {
try {
Resource resource = resourceLoader.getResource(location);
if (!resource.exists()) {
return null;
}
ResourceInfo info = new ResourceInfo();
info.setLocation(location);
info.setExists(true);
info.setDescription(resource.getDescription());
try {
info.setFilename(resource.getFilename());
info.setContentLength(resource.contentLength());
info.setLastModified(new Date(resource.lastModified()));
info.setReadable(resource.isReadable());
info.setWritable(resource instanceof WritableResource &&
((WritableResource) resource).isWritable());
} catch (IOException e) {
logger.warn("获取资源信息失败: {}", location, e);
}
return info;
} catch (Exception e) {
logger.error("获取资源信息失败: {}", location, e);
return null;
}
}
/**
* 资源信息类
*/
public static class ResourceInfo {
private String location;
private boolean exists;
private String description;
private String filename;
private long contentLength;
private Date lastModified;
private boolean readable;
private boolean writable;
// Getters and Setters
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public boolean isExists() { return exists; }
public void setExists(boolean exists) { this.exists = exists; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getFilename() { return filename; }
public void setFilename(String filename) { this.filename = filename; }
public long getContentLength() { return contentLength; }
public void setContentLength(long contentLength) { this.contentLength = contentLength; }
public Date getLastModified() { return lastModified; }
public void setLastModified(Date lastModified) { this.lastModified = lastModified; }
public boolean isReadable() { return readable; }
public void setReadable(boolean readable) { this.readable = readable; }
public boolean isWritable() { return writable; }
public void setWritable(boolean writable) { this.writable = writable; }
}
}
/**
* 配置文件加载示例
*/
@Component
public class ConfigurationLoader {
private static final Logger logger = LoggerFactory.getLogger(ConfigurationLoader.class);
@Autowired
private ResourceLoader resourceLoader;
private final Map<String, Properties> configCache = new ConcurrentHashMap<>();
/**
* 加载并缓存配置
*/
public Properties loadConfiguration(String configLocation) {
return configCache.computeIfAbsent(configLocation, this::loadProperties);
}
/**
* 加载属性文件
*/
private Properties loadProperties(String location) {
Properties properties = new Properties();
try {
Resource resource = resourceLoader.getResource(location);
if (resource.exists()) {
try (InputStream inputStream = resource.getInputStream()) {
properties.load(inputStream);
logger.info("成功加载配置文件: {}", location);
}
} else {
logger.warn("配置文件不存在: {}", location);
}
} catch (IOException e) {
logger.error("加载配置文件失败: {}", location, e);
}
return properties;
}
/**
* 重新加载配置
*/
public void reloadConfiguration(String configLocation) {
configCache.remove(configLocation);
loadConfiguration(configLocation);
}
/**
* 获取配置值
*/
public String getProperty(String configLocation, String key) {
Properties properties = loadConfiguration(configLocation);
return properties.getProperty(key);
}
/**
* 获取配置值(带默认值)
*/
public String getProperty(String configLocation, String key, String defaultValue) {
Properties properties = loadConfiguration(configLocation);
return properties.getProperty(key, defaultValue);
}
}
/**
* 模板引擎服务示例
*/
@Service
public class TemplateEngineService {
private static final Logger logger = LoggerFactory.getLogger(TemplateEngineService.class);
@Autowired
private ResourceLoader resourceLoader;
private final Map<String, String> templateCache = new ConcurrentHashMap<>();
/**
* 渲染模板
*/
public String renderTemplate(String templateName, Map<String, Object> model) {
try {
String templateContent = getTemplateContent(templateName);
return processTemplate(templateContent, model);
} catch (Exception e) {
logger.error("渲染模板失败: {}", templateName, e);
return "Template rendering error";
}
}
/**
* 获取模板内容
*/
private String getTemplateContent(String templateName) {
return templateCache.computeIfAbsent(templateName, this::loadTemplate);
}
/**
* 加载模板
*/
private String loadTemplate(String templateName) {
String location = "classpath:templates/" + templateName + ".html";
try {
Resource resource = resourceLoader.getResource(location);
if (resource.exists()) {
try (InputStream inputStream = resource.getInputStream()) {
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
}
} catch (IOException e) {
logger.error("加载模板失败: {}", location, e);
}
return "Template not found: " + templateName;
}
/**
* 处理模板
*/
private String processTemplate(String templateContent, Map<String, Object> model) {
String result = templateContent;
// 简单的占位符替换(实际应用中应使用专业模板引擎)
for (Map.Entry<String, Object> entry : model.entrySet()) {
String placeholder = "${" + entry.getKey() + "}";
String value = String.valueOf(entry.getValue());
result = result.replace(placeholder, value);
}
return result;
}
/**
* 清除模板缓存
*/
public void clearTemplateCache() {
templateCache.clear();
}
/**
* 清除特定模板缓存
*/
public void clearTemplateCache(String templateName) {
templateCache.remove(templateName);
}
}
/**
* 应用程序主类
*/
@SpringBootApplication
public class ResourceLoaderApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ResourceLoaderApplication.class, args);
// 测试资源加载
testResourceLoading(context);
// 测试配置加载
testConfigurationLoading(context);
// 测试模板引擎
testTemplateEngine(context);
}
/**
* 测试资源加载
*/
private static void testResourceLoading(ConfigurableApplicationContext context) {
ResourceLoadingService service = context.getBean(ResourceLoadingService.class);
System.out.println("=== 资源加载测试 ===");
// 加载类路径资源
String content = service.loadResource("classpath:application.properties");
System.out.println("配置文件内容: " + (content != null ? content.substring(0, Math.min(100, content.length())) : "null"));
// 加载多个资源
List<String> resources = service.loadResources("classpath*:*.properties");
System.out.println("找到的属性文件数量: " + resources.size());
// 获取资源信息
ResourceLoadingService.ResourceInfo info = service.getResourceInfo("classpath:application.properties");
if (info != null) {
System.out.println("资源信息: " + info.getDescription());
System.out.println("文件名: " + info.getFilename());
System.out.println("大小: " + info.getContentLength() + " 字节");
}
}
/**
* 测试配置加载
*/
private static void testConfigurationLoading(ConfigurableApplicationContext context) {
ConfigurationLoader loader = context.getBean(ConfigurationLoader.class);
System.out.println("=== 配置加载测试 ===");
// 加载配置
Properties props = loader.loadConfiguration("classpath:application.properties");
System.out.println("配置项数量: " + props.size());
// 获取配置值
String appName = loader.getProperty("classpath:application.properties", "spring.application.name", "default-app");
System.out.println("应用名称: " + appName);
}
/**
* 测试模板引擎
*/
private static void testTemplateEngine(ConfigurableApplicationContext context) {
TemplateEngineService engine = context.getBean(TemplateEngineService.class);
System.out.println("=== 模板引擎测试 ===");
// 渲染模板
Map<String, Object> model = new HashMap<>();
model.put("title", "测试标题");
model.put("content", "这是测试内容");
model.put("author", "测试作者");
String result = engine.renderTemplate("test", model);
System.out.println("渲染结果: " + result);
}
}
7. 最佳实践和注意事项
/*
* ResourceLoader最佳实践
*/
/**
* 正确的资源加载方式
*/
@Component
public class GoodResourceLoading {
private static final Logger logger = LoggerFactory.getLogger(GoodResourceLoading.class);
@Autowired
private ResourceLoader resourceLoader;
/**
* 正确的资源加载实现
*/
public String loadResourceCorrectly(String location) {
try {
// 获取资源
Resource resource = resourceLoader.getResource(location);
// 检查资源是否存在
if (!resource.exists()) {
logger.warn("资源不存在: {}", location);
return null;
}
// 检查资源是否可读
if (!resource.isReadable()) {
logger.warn("资源不可读: {}", location);
return null;
}
// 使用try-with-resources确保资源正确关闭
try (InputStream inputStream = resource.getInputStream()) {
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
} catch (IOException e) {
logger.error("加载资源失败: {}", location, e);
return null;
}
}
/**
* 批量资源加载
*/
public List<String> loadResourcesBatch(String pattern) {
List<String> results = new ArrayList<>();
try {
// 使用ResourcePatternResolver加载多个资源
if (resourceLoader instanceof ResourcePatternResolver) {
ResourcePatternResolver resolver = (ResourcePatternResolver) resourceLoader;
Resource[] resources = resolver.getResources(pattern);
for (Resource resource : resources) {
try (InputStream inputStream = resource.getInputStream()) {
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
results.add(content);
}
}
}
} catch (IOException e) {
logger.error("批量加载资源失败: {}", pattern, e);
}
return results;
}
/**
* 带缓存的资源加载
*/
private final Map<String, String> resourceCache = new ConcurrentHashMap<>();
private final Map<String, Long> cacheTimestamps = new ConcurrentHashMap<>();
private static final long CACHE_TIMEOUT = 60000; // 1分钟缓存
public String loadResourceWithCache(String location) {
return resourceCache.computeIfAbsent(location, key -> {
cacheTimestamps.put(key, System.currentTimeMillis());
return loadResourceUncached(key);
});
}
private String loadResourceUncached(String location) {
try {
Resource resource = resourceLoader.getResource(location);
if (resource.exists()) {
try (InputStream inputStream = resource.getInputStream()) {
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
}
} catch (IOException e) {
logger.error("加载资源失败: {}", location, e);
}
return null;
}
/**
* 清除过期缓存
*/
public void clearExpiredCache() {
long now = System.currentTimeMillis();
cacheTimestamps.entrySet().removeIf(entry -> {
if (now - entry.getValue() > CACHE_TIMEOUT) {
resourceCache.remove(entry.getKey());
return true;
}
return false;
});
}
}
/**
* 避免的错误实现
*/
@Component
public class BadResourceLoading {
@Autowired
private ResourceLoader resourceLoader;
/**
* 错误的资源加载实现
*/
public String loadResourceIncorrectly(String location) {
// 错误1: 不检查资源是否存在
Resource resource = resourceLoader.getResource(location);
// resource.exists() 可能返回false
try {
// 错误2: 不使用try-with-resources
InputStream inputStream = resource.getInputStream(); // 可能导致资源泄露
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
inputStream.close(); // 手动关闭,容易出错
return content;
} catch (IOException e) {
// 错误3: 不处理异常或处理不当
return null; // 沉默失败
}
}
/**
* 错误的批量加载
*/
public List<String> loadResourcesIncorrectly(String pattern) {
List<String> results = new ArrayList<>();
try {
// 错误: 没有检查ResourceLoader类型
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(pattern);
for (Resource resource : resources) {
// 错误: 不检查资源是否存在
InputStream inputStream = resource.getInputStream();
String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
inputStream.close();
results.add(content);
}
} catch (Exception e) {
// 错误: 吞掉异常
}
return results;
}
}
/**
* 线程安全的资源加载
*/
@Component
public class ThreadSafeResourceLoading {
@Autowired
private ResourceLoader resourceLoader;
// 使用线程安全的集合
private final ConcurrentHashMap<String, CachedResource> resourceCache = new ConcurrentHashMap<>();
/**
* 线程安全的资源加载
*/
public String loadResourceThreadSafely(String location) {
CachedResource cached = resourceCache.get(location);
if (cached != null && !cached.isExpired()) {
return cached.getContent();
}
// 双重检查锁定模式
synchronized (resourceCache) {
cached = resourceCache.get(location);
if (cached != null && !cached.isExpired()) {
return cached.getContent();
}
String content = loadResourceUncached(location);
if (content != null) {
CachedResource newCached = new CachedResource(content, System.currentTimeMillis() + 60000);
resourceCache.put(location, newCached);
return content;
}
}
return null;
}
private String loadResourceUncached(String location) {
try {
Resource resource = resourceLoader.getResource(location);
if (resource.exists() && resource.isReadable()) {
try (InputStream inputStream = resource.getInputStream()) {
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
}
} catch (IOException e) {
// 记录日志但不抛出异常
}
return null;
}
/**
* 缓存的资源包装类
*/
private static class CachedResource {
private final String content;
private final long expireTime;
public CachedResource(String content, long expireTime) {
this.content = content;
this.expireTime = expireTime;
}
public String getContent() {
return content;
}
public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
}
/**
* 自定义协议解析器
*/
@Component
public class CustomProtocolResolver implements ProtocolResolver {
private static final String DATABASE_PROTOCOL = "database:";
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith(DATABASE_PROTOCOL)) {
String resourceId = location.substring(DATABASE_PROTOCOL.length());
return new DatabaseResource(resourceId, jdbcTemplate);
}
return null;
}
@Override
public String getProtocol() {
return "database";
}
/**
* 数据库资源实现
*/
private static class DatabaseResource extends AbstractResource {
private final String resourceId;
private final JdbcTemplate jdbcTemplate;
public DatabaseResource(String resourceId, JdbcTemplate jdbcTemplate) {
this.resourceId = resourceId;
this.jdbcTemplate = jdbcTemplate;
}
@Override
public boolean exists() {
try {
Integer count = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM resources WHERE id = ?", Integer.class, resourceId);
return count != null && count > 0;
} catch (Exception e) {
return false;
}
}
@Override
public InputStream getInputStream() throws IOException {
try {
String content = jdbcTemplate.queryForObject(
"SELECT content FROM resources WHERE id = ?", String.class, resourceId);
if (content == null) {
throw new FileNotFoundException("Database resource not found: " + resourceId);
}
return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new IOException("Failed to load database resource: " + resourceId, e);
}
}
@Override
public String getDescription() {
return "database resource [" + resourceId + "]";
}
}
}
/**
* 配置类注册自定义协议解析器
*/
@Configuration
public class ResourceLoaderConfig {
@Autowired
private CustomProtocolResolver customProtocolResolver;
/**
* 配置ResourceLoader
*/
@Bean
public DefaultResourceLoader defaultResourceLoader() {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
resourceLoader.addProtocolResolver(customProtocolResolver);
return resourceLoader;
}
/**
* 配置ResourcePatternResolver
*/
@Bean
public ResourcePatternResolver resourcePatternResolver() {
return new PathMatchingResourcePatternResolver(defaultResourceLoader());
}
}
8. 核心设计要点总结
8.1 设计目的
- 提供统一的资源访问抽象
- 屏蔽不同资源类型的差异
- 支持多种资源访问协议
- 与Spring IoC容器无缝集成
8.2 核心接口
- ResourceLoader: 基础资源加载接口
- Resource: 统一资源访问接口
- ResourcePatternResolver: 支持模式匹配的资源解析器
- ProtocolResolver: 自定义协议解析器
8.3 支持的资源类型
- ClassPathResource: 类路径资源
- FileSystemResource: 文件系统资源
- UrlResource: URL资源
- ByteArrayResource: 字节数组资源
- InputStreamResource: 输入流资源
8.4 常用前缀
classpath:: 类路径资源file:: 文件系统资源http:: HTTP资源classpath*:: 多个类路径资源
8.5 使用建议
- 正确管理资源: 使用try-with-resources确保资源关闭
- 检查资源存在性: 在使用前检查资源是否存在
- 异常处理: 适当处理IOException
- 缓存策略: 合理使用缓存提高性能
- 线程安全: 确保多线程环境下的正确使用
8.6 最佳实践
- 优先使用依赖注入获取ResourceLoader
- 实现适当的缓存机制
- 使用ResourcePatternResolver处理批量资源
- 注册自定义协议解析器扩展功能
- 注意资源加载的性能影响
ResourceLoader作为Spring框架的基础组件,为开发者提供了强大而灵活的资源访问能力,是构建企业级Java应用的重要工具。

854

被折叠的 条评论
为什么被折叠?



