spring引用配置文件的时候classpath:和classpath*:的区别

我们平时使用spring框架的时候经常需要引入一些配置文件,如.propertis文件。其实有号和没有星号的区别不是很大,但有细微区别,这里我结合spring的源码简要分析有号和没有*号的的区别,有误的地方请指出。
classparth:只会从第一个classpath中加载,而classpath*:会从所有的classpath中加载如果要加载的资源,不在当前ClassLoader的路径里,那么如果用classpath:打头前缀是找不到的,这种情况下就需要使用classpath*:前缀;另一种情况下,在多个classpath中存在同名资源,都需要加载,那么用classpath:只会加载第一个,这种情况下也需要用classpath*:前缀。可想而知,用classpath*:需要遍历所有的classpath,所以加载速度较classpath慢,因此,在规划的时候,应该尽可能规划好资源文件所在的路径,尽量避免使用classpath*。
现在我们分析一下classpath 与 classpath*以及通配符spring是怎么处理的。
首先需要知道spring解析文件路径主要是spring-core-xxxxx.jar\org\springframework\core\io包及其子包完成的。先看InputStreamSource
在Spring中,定义了接口InputStreamSource,这个类中只包含一个方法:
public interface InputStreamSource {

/**
 * Return an {@link InputStream}.
 * <p>It is expected that each call creates a <i>fresh</i> stream.
 * <p>This requirement is particularly important when you consider an API such
 * as JavaMail, which needs to be able to read the stream multiple times when
 * creating mail attachments. For such a use case, it is <i>required</i>
 * that each {@code getInputStream()} call returns a fresh stream.
 * @return the input stream for the underlying resource (must not be {@code null})
 * @throws IOException if the stream could not be opened
 * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
 */
InputStream getInputStream() throws IOException;

}
InputStreamSource下面有一个Resource子接口,重要的方法有几个:
/**
* Return whether this resource actually exists in physical form.
*

This method performs a definitive existence check, whereas the
* existence of a {@code Resource} handle only guarantees a
* valid descriptor handle.
*/
boolean exists();

/**
 * Return whether the contents of this resource can be read,
 * e.g. via {@link #getInputStream()} or {@link #getFile()}.
 * <p>Will be {@code true} for typical resource descriptors;
 * note that actual content reading may still fail when attempted.
 * However, a value of {@code false} is a definitive indication
 * that the resource content cannot be read.
 * @see #getInputStream()
 */
boolean isReadable();

/**
 * Return whether this resource represents a handle with an open
 * stream. If true, the InputStream cannot be read multiple times,
 * and must be read and closed to avoid resource leaks.
 * <p>Will be {@code false} for typical resource descriptors.
 */
boolean isOpen();

而Spring加载Resource文件是通过ResourceLoader来进行的,ResourceLoader接口只提供了classpath前缀的支持。
/* Pseudo URL prefix for loading from the class path: “classpath:” /
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
而classpath*的前缀支持是在它的子接口ResourcePatternResolver中。
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.
 * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
 */
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

……………….
}
ResourcePatternResolver有一个实现类:PathMatchingResourcePatternResolver,现在我们看看PathMatchingResourcePatternResolver的getResources()方法,

@Override
public Resource[] getResources(String locationPattern) throws IOException {
    Assert.notNull(locationPattern, "Location pattern must not be null");
    //CLASSPATH_ALL_URL_PREFIX对应classpath*:
    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()))) {
            // 包含通配符
            return findPathMatchingResources(locationPattern);
        }
        else {
            // all class path resources with the given name
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
        }
    }
    else {
        // Only look for a pattern after a prefix here
        // (to not get fooled by a pattern symbol in a strange prefix).
        int prefixEnd = locationPattern.indexOf(":") + 1;
        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
            // a file pattern
            return findPathMatchingResources(locationPattern);
        }
        else {
            // 不以classpath*开头,且路径不包含通配符的就以这个方式处理;
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
        }
    }
}

由此可以看出以classpath*开头,包含通配符的就调用findAllClassPathResources(….)处理,
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith(“/”)) {
path = path.substring(1);
}
Set result = doFindAllClassPathResources(path);
return result.toArray(new Resource[result.size()]);
}

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
    Set<Resource> result = new LinkedHashSet<Resource>(16);
    ClassLoader cl = getClassLoader();
    Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
    while (resourceUrls.hasMoreElements()) {
        URL url = resourceUrls.nextElement();
        result.add(convertClassLoaderURL(url));
    }
    if ("".equals(path)) {
        // The above result is likely to be incomplete, i.e. only containing file system references.
        // We need to have pointers to each of the jar files on the classpath as well...
        addAllClassLoaderJarRoots(cl, result);
    }
    return result;
}

/**
 * Convert the given URL as returned from the ClassLoader into a {@link Resource}.
 * <p>The default implementation simply creates a {@link UrlResource} instance.
 * @param url a URL as returned from the ClassLoader
 * @return the corresponding Resource object
 * @see java.lang.ClassLoader#getResources
 * @see org.springframework.core.io.Resource
 */
protected Resource convertClassLoaderURL(URL url) {
    return new UrlResource(url);
}

/**
 * Search all {@link URLClassLoader} URLs for jar file references and add them to the
 * given set of resources in the form of pointers to the root of the jar file content.
 * @param classLoader the ClassLoader to search (including its ancestors)
 * @param result the set of resources to add jar roots to
 */
protected void addAllClassLoaderJarRoots(ClassLoader classLoader, Set<Resource> result) {
    if (classLoader instanceof URLClassLoader) {
        try {
            for (URL url : ((URLClassLoader) classLoader).getURLs()) {
                if (ResourceUtils.isJarFileURL(url)) {
                    try {
                        UrlResource jarResource = new UrlResource(
                                ResourceUtils.JAR_URL_PREFIX + url.toString() + ResourceUtils.JAR_URL_SEPARATOR);
                        if (jarResource.exists()) {
                            result.add(jarResource);
                        }
                    }
                    catch (MalformedURLException ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cannot search for matching files underneath [" + url +
                                    "] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
                        }
                    }
                }
            }
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader +
                        "] does not support 'getURLs()': " + ex);
            }
        }
    }
    if (classLoader != null) {
        try {
            addAllClassLoaderJarRoots(classLoader.getParent(), result);
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader +
                        "] does not support 'getParent()': " + ex);
            }
        }
    }
}
这样一直加载到jar文件里面去了。

如果不以classpath*开头,且路径不包含通配符的就以这个方式处理;
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};

如果不以classpath开头则使用DefaultResourceLoader 处理:
public class DefaultResourceLoader implements ResourceLoader {

private ClassLoader classLoader;


/**
 * Create a new DefaultResourceLoader.
 * <p>ClassLoader access will happen using the thread context class loader
 * at the time of this ResourceLoader's initialization.
 * @see java.lang.Thread#getContextClassLoader()
 */
public DefaultResourceLoader() {
    this.classLoader = ClassUtils.getDefaultClassLoader();
}

/**
 * Create a new DefaultResourceLoader.
 * @param classLoader the ClassLoader to load class path resources with, or {@code null}
 * for using the thread context class loader at the time of actual resource access
 */
public DefaultResourceLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
}


/**
 * Specify the ClassLoader to load class path resources with, or {@code null}
 * for using the thread context class loader at the time of actual resource access.
 * <p>The default is that ClassLoader access will happen using the thread context
 * class loader at the time of this ResourceLoader's initialization.
 */
public void setClassLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
}

/**
 * Return the ClassLoader to load class path resources with.
 * <p>Will get passed to ClassPathResource's constructor for all
 * ClassPathResource objects created by this resource loader.
 * @see ClassPathResource
 */
@Override
public ClassLoader getClassLoader() {
    return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}


@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
        try {
            // Try to parse the location as a URL...
            URL url = new URL(location);
            return new UrlResource(url);
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }

最后是路径中包含通配符的,如(classpath*:resource/**/xxx.xml)
主要的思想就是
1.先获取目录,加载目录里面的所有资源
2.在所有资源里面进行查找匹配,找出我们需要的资源

/**
 * Find all resources that match the given location pattern via the
 * Ant-style PathMatcher. Supports resources in jar files and zip files
 * and in the file system.
 * @param locationPattern the location pattern to match
 * @return the result as Resource array
 * @throws IOException in case of I/O errors
 * @see #doFindPathMatchingJarResources
 * @see #doFindPathMatchingFileResources
 * @see org.springframework.util.PathMatcher
 */
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    String rootDirPath = determineRootDir(locationPattern);
    String subPattern = locationPattern.substring(rootDirPath.length());
    Resource[] rootDirResources = getResources(rootDirPath);
    Set<Resource> result = new LinkedHashSet<Resource>(16);
    for (Resource rootDirResource : rootDirResources) {
        rootDirResource = resolveRootDirResource(rootDirResource);
        if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
        }
        else if (isJarResource(rootDirResource)) {
            result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
        }
        else {
            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
        }
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
    }
    return result.toArray(new Resource[result.size()]);
}

我们可以总结一下:
1.无论是classpath还是classpath*都可以加载整个classpath下(包括jar包里面)的资源文件。
2.classpath只会返回第一个匹配的资源,查找路径是优先在项目中存在资源文件,再查找jar包。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值