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

来源:互联网 发布:nginx和apache是什么 编辑:程序博客网 时间:2024/06/11 17:26

我们平时使用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()方法,

@Overridepublic 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 */@Overridepublic ClassLoader getClassLoader() {    return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());}@Overridepublic 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 0
原创粉丝点击