spring中ResourceLoader源码

本文是记录一下spring的ResourceLoader接口的一些实现及ResourceLoader在ApplicationContext中的作用。

实例代码

代码结构如下:

spring中ResourceLoader源码_第1张图片

在resources/config 目录有两个配置文件
db.properties
db1=host:3306
net.properties
url=http://www.test.com

在工程引用的jar包sso.jar中也存在config目录,里边有一个sso.properties

UAS_SERVICE=http://uas.....cn/uas/...
DOMAIN_NAME=default
HTTP_SESSION_TIMEOUT=1800
ssogfd.read.url=jdbc\:mysql\://10.*.*.124\:3306/test_db?characterEncoding\=utf-8
ssogfd.read.username=0330
ssogfd.read.password=0330
jdbc.dataSource.driverClassName=com.mysql.jdbc.Driver
jdbc.dataSource.initialSize=2
jdbc.dataSource.maxActive=50
jdbc.dataSource.maxIdle=20
jdbc.dataSource.maxWait=1000

Service1.java 里是测试代码,用来测试 classpath:config/.properties 会不会将三个配置文件全部获取到。
Service1.java

public class Service1 {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring/*.xml");
        Resource[] resources = context.getResources("classpath*:config/*.properties");
        for (Resource resource : resources) {
            Properties prop = PropertiesLoaderUtils.loadProperties(resource);
            Enumeration  propEum = prop.propertyNames();
            Object eleKey = null;
            while(propEum.hasMoreElements()){
                System.out.println((eleKey=propEum.nextElement())+" : "+prop.get(eleKey));
            }
        }

    }
}

运行结果:显示jar包里的配置及工程中config目录下的配置全部能读取到。
spring中ResourceLoader源码_第2张图片
下面我们就到sprnig 的代码里看一下具体实现。

spring代码分析

spring中ResourceLoader源码_第3张图片
spring从底层资源的实际类型中抽象出来的资源描述符的接口Resource接口(资源描述符),Resouce的具体实现可以代表系统文件、类路径资源、网络文件等等。spring提供了ResourceLoader接口作为加载Resouce的抽象。我们分析就从这个接口开始。

ResourceLoader接口

/**
 * 加载资源的策略接口,一个Applicationcontext被要求提供相关的功能和扩展。
 * DefaultResourceLoader 是一个独立与ApplicationContext之外的单独可用实现
 * 这将是特定于实现的,通常由 ApplicationContext 实现提供。
 */
public interface ResourceLoader{
    /**
        返回一个指定资源位置的资源句柄。这个句柄应该是一个可以可重用的资源描述符。
        必须支持全限定URLs,例如:file:c:/test.dat
        必须支持classpath伪URLs,例如:classpath:test.dat
        应该支持相对文件路径,例如:WEB-INF/test.dat   
     */
   Resource getResource(String location);
   ClassLoader getClassLoader();
}

ResourceLoader有一个非常重要的扩展接口ResourcePatternResover和一个重要的实现类DefaultResourceLoader 。这两个class在spring中都有非常重要的使用。

ResourcePatternResover

/**
 *解析一个位置模式(例如:ant-style路径模式)到Resource对象的策略接口
 * PathMatchingResourcePatternResolver 是一个在ApplicationContext外可以重用的单独实现
 *这个接口仅仅指定了转换方法而不是具体指定模式格式。
 *这个接口支持了一个新的资源前缀"classpath*"(从classpath中找到所有的匹配资源)。
 *请注意,在这种情况下,资源位置应该是没有占位符的路径(我理解应该不能是有file:  http:等占位符)。
 */
public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}

通过注释我们大概知道ResourcePatternResover是解析所有符合模式的资源的一个类,提供了相关的方法。其中CLASSPATH_ALL_URL_PREFIX 定义了我们常用的资源搜索前缀。

DefaultResourceLoader

/**
 *ResourceLoader接口的默认实现。被ResourceEditor使用,而且作为一个基础来为AbstractApplicationContext服务。
  *可以单独使用
  *如果定位的值是个URL返回URLResource ,如果不是定位不是URL或者是"classpath"伪url则返回ClassPathResource
 */
public class DefaultResourceLoader implements ResourceLoader {

    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");

        for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }
        //如果开头是/ 返回ClassPathContextResource
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }
}

ApplicationContext

ApplicationContext 继承了接口ResourcePatternResover,间接也继承了ResourceLoader 。
AbstractApplicationContext 继承了ApplicationContext 也继承了DefaultResourceLoader。AbstractApplicationContext getResource继承自DefaultResourceLoader ,getResources实现源码如下。


public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    private ResourcePatternResolver resourcePatternResolver;
    
    public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
    }
    
    protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        return this.resourcePatternResolver.getResources(locationPattern);
    }

}

从上面代码看到,Applicaton的getResouces 是有PathMatchingResourcePatternResolver来处理的。PathMatchingResourcePatternResolver 这个重要的类终于登场了。

PathMatchingResourcePatternResolver

/**
 *一种 ResourcePatternResolver 实现,能够将指定的资源位置路径解析为一个或多个匹配的资源。 
 *源路径可以是与目标资源一对一映射的简单路径,或者可以包含特殊的“classpath*:”前缀和/或内部 Ant 样式的正则表达式(使用 Spring 的 AntPathMatcher 实用程序匹配) . 后者都是有效的通配符。
 *没有通配符:在简单的情况下,如果指定的位置路径不以“classpath*:”前缀开头,并且不包含 PathMatcher 模式,则此解析器将通过底层 ResourceLoader 上的 getResource() 调用简单地返回单个资源
 *Ant风格模式:当路径位置包含 Ant 样式模式时。例如:
 *     /WEB-INF/*-context.xml
 *     com/mycompany/**/applicationContext.xml
 *     file:C:/some/path/*-context.xml
 *     classpath:com/mycompany/**/applicationContext.xml
 * 解析器遵循更复杂但已定义的过程来尝试解析通配符。 它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个 URL。 
 * 如果此 URL 不是“jar:” URL 或特定于容器的变体(例如 WebLogic 中的“zip:”、WebSphere 中的“wsjar”等),则从中获取 java.io.File 并使用 通过遍历文件系统来解析通配符。
 * 对于jar URL,解析器要么从中获取java.net.JarURLConnection,要么手动解析jar URL,然后遍历jar文件的内容,以解析 通配符。
 *   
 * 对可移植性的影响:
 *    如果指定的路径已经是文件URL(显式或隐式,因为基本 ResourceLoader 是一个文件系统),那么通配符可以保证以完全可移植的方式工作。
 *  .....(后续补充)
 * classpath*: 前缀:
 *   这是通过“classpath*:”前缀检索具有相同名称的多个类路径资源的特别支持。 例classpath*:META-INF/beans.xml 将会查找到classpath,包括classes目录或jar文件里的所有bean.xml文件。这对于自动检测每个 jar 文件中相同位置的同名配置文件特别有用。在内部,这是通过 ClassLoader.getResources() 调用发生的,并且是完全可移植的。
 *   “classpath*:”前缀也可以与位置路径的其余部分中的 PathMatcher 模式结合使用。例如classpath*:META-INF/*-beans.xml。在这种情况下,解决策略也比较简单:在最后一个非通配符路径段上使用 ClassLoader.getResources() 调用来获取类加载器层次结构中的所有匹配资源,然后遍历每个资源,使用上述相同的 PathMatcher 解析策略用于通配符子路径。
 */
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    
@Override
    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // a class path resource (multiple resources for same name possible)
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // a class path resource pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // all class path resources with the given name
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            // Generally only look for a pattern after a prefix here,
            // and on Tomcat only after the "*/" separator for its "war:" protocol.
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
                    locationPattern.indexOf(':') + 1);
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // a single resource with the given name
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        //最后一个非通配符段的路径,例如:classpath*:/config/*-bean.xml,rootDirPath是classpath*:/config
        String rootDirPath = determineRootDir(locationPattern);
        //去除rootDitPath的路径,可能包含匹配模式,也可能不包含
        String subPattern = locationPattern.substring(rootDirPath.length());
        //从classPah(classes 和jar)获取rootDirPath,
        Resource[] rootDirResources = getResources(rootDirPath);
        Set result = new LinkedHashSet<>(16);
        //遍历获取到的rootDitPath的所有资源
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            URL rootDirUrl = rootDirResource.getURL();
            if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
                URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
                if (resolvedUrl != null) {
                    rootDirUrl = resolvedUrl;
                }
                rootDirResource = new UrlResource(rootDirUrl);
            }
            if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
            }
            else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
            }
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[0]);
}

通过各个类的代码我们知道,在applicationContext中使用的还是PathMatchingResourcePatternResolver还获取相关资源,这个类在很多地方都在使用。

warning

PathMatchingResourcePatternResolver的注释有几个warning,单独拿出来记录一下。

warning1

当与 Ant 样式模式结合使用时,“classpath:”只能在模式开始前至少在一个根目录下可靠地工作,除非实际的目标文件驻留在文件系统中(classes中).这就意味这像classpath:*.xml 不会从jar文件中检索xml文件,而只是从扩展目录中获取。这源于 JDK 的 ClassLoader.getResources() 方法的限制,该方法仅返回传入的空字符串的文件系统位置。

warning2

如果要搜索的根包(rootdir)在多个类路径位置中可用,则不能保证具有“classpath:”资源的 Ant 样式模式找到匹配的资源。例如:com/mycompany/package1/service-context.xml 可能在project中只有一个位置,当时当使用classpath:com/mycompany/*/service-context.xml 来尝试解析它,则解析器将处理 getResource("com/company") 返回的(第一个)URL.所以如果此基础包节点存在于多个类加载器位置,则实际的最终资源可能不在下面。 "classpath:"

你可能感兴趣的:(java)