Spring DefaultResourceLoader源码详解(详细注释版)

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应用的重要工具。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值