同名资源存在时,classpath: 只从第一个符合条件的classpath中加载资源,而classpath*: 会从所有的classpath中加载符合条件的资源

classpath*:需要遍历所有的classpath,效率肯定比不上classpath,因此在项目设计的初期就尽量规划好资源文件所在的路径,避免使用classpath*来加载

classpath本质是jvm的根路径,jvm获取资源都是从该根路径下找的,注意这个根路径是个逻辑路径,并不是磁盘路径。比如两个jar包的路径是/packagea/a.jar和/packageb/b.jar,但是用classpath*:就可以找到这两个jar包中的资源。

一般classpath指向的是classes,也就是编译路径的根路径,而一般classes中放着这些文件:

1.java文件编译好的class文件。

2.properties配置文件。

3.xml配置文件。

4.一些模版文件,如*.ftl。

5.其他需要用classpath获取到的文件。

搞懂了classpath指向的classes里放的东西,我们再来看看classpath: 和classpath*:的区别。

1.classpath:只会到你的classes路径中查找找文件。

2.classpath*:不仅会到classes路径,还包括jar文件中(classes路径)进行查找。

<!-- myBatis配置.
            classpath和classpath*的区别,参考文档:http://blog.csdn.net/zl3450341/article/details/9306983.
            classpath只会返回第一个匹配的资源,建议确定路径的单个文档使用classpath;匹配多个文档时使用classpath*.
       -->
       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
             p:dataSource-ref="dataSource"
             p:configLocation="classpath:mybatis.xml"
             p:mapperLocations="classpath*:com/*Mapper.xml" />

Spring还提供了一种更强大的Ant模式通配符匹配,从能一个路径匹配一批资源。 Ant路径通配符支持“?”、“*”、“**”,注意通配符匹配不包括目录分隔符“/”:

“?”:匹配一个字符,如“config?.xml”将匹配“config1.xml”;

“*”:匹配零个或多个字符串,如“cn/*/config.xml”将匹配“cn/javass/config.xml”,但不匹配匹配“cn/config.xml”;而“cn/config-*.xml”将匹配“cn/config-dao.xml”;

“**”:匹配路径中的零个或多个目录,如“cn/**/config.xml”将匹配“cn /config.xml”,也匹配“cn/javass/spring/config.xml”;而“cn/javass/config-**.xml”将匹配“cn/javass/config-dao.xml”,即把“**”当做两个“*”处理。

Spring加载Resource文件是通过ResourceLoader来进行的

重点————classpath 与 classpath*以及通配符是怎么处理的

public interface ResourceLoader {

/** Pseudo URL prefix for loading from the class path: "classpath:" */

String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

Resource getResource(String location);

ClassLoader getClassLoader();

}

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*:";

Resource[] getResources(String locationPattern) throws IOException;

}

通过2个接口的源码对比,我们发现ResourceLoader提供 classpath下单资源文件的载入,而ResourcePatternResolver提供了多资源文件的载入。

ResourcePatternResolver有一个实现类:PathMatchingResourcePatternResolver,那我们直奔主题,查看PathMatchingResourcePatternResolver的getResources()

public Resource[] getResources(String locationPattern) throws IOException {

Assert.notNull(locationPattern, "Location pattern must not be null");

//是否以classpath*开头

if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {

//是否包含?或者*

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 {

// 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 {

// a single resource with the given name

return new Resource[] {getResourceLoader().getResource(locationPattern)};

}

}

}

由此我们可以看出在加载配置文件时,以是否是以classpath*开头分为2大类处理场景,每大类在又根据路径中是否包括通配符分为2小类进行处理,

处理的流程图如下:

从上图看,整个加载资源的场景有三条处理流程

以classpath*开头,但路径不包含通配符的

让我们来看看findAllClassPathResources是怎么处理的

protected Resource[] findAllClassPathResources(String location) throws IOException {

String path = location;

if (path.startsWith("/")) {

path = path.substring(1);

}

Enumeration<URL> resourceUrls = getClassLoader().getResources(path);

Set<Resource> result = new LinkedHashSet<Resource>(16);

while (resourceUrls.hasMoreElements()) {

URL url = resourceUrls.nextElement();

result.add(convertClassLoaderURL(url));

}

return result.toArray(new Resource[result.size()]);

}

我们可以看到,最关键的一句代码是:Enumeration<URL> resourceUrls = getClassLoader().getResources(path);

public ClassLoader getClassLoader() {

return getResourceLoader().getClassLoader();

}

public ResourceLoader getResourceLoader() {

return this.resourceLoader;

}

//默认情况下

public PathMatchingResourcePatternResolver() {

this.resourceLoader = new DefaultResourceLoader();

}

其实上面这3个方法不是最关键的,之所以贴出来,是让大家清楚整个调用链,其实这种情况最关键的代码在于ClassLoader的getResources()方法。那么我们同样跟进去,看看源码

public Enumeration<URL> getResources(String name) throws IOException {

Enumeration[] tmp = new Enumeration[2];

if (parent != null) {

tmp[0] = parent.getResources(name);

} else {

tmp[0] = getBootstrapResources(name);

}

tmp[1] = findResources(name);

  1. return new CompoundEnumeration(tmp);
  2. }

是不是一目了然了?当前类加载器,如果存在父加载器,则向上迭代获取资源, 因此能加到jar包里面的资源文件。

  • 不以classpath*开头,且路径不包含通配符的
处理逻辑如下

  1. return new Resource[] {getResourceLoader().getResource(locationPattern)};

上面我们已经贴过getResourceLoader()的逻辑了, 即默认是DefaultResourceLoader(),那我们进去看看getResouce()的实现

public Resource getResource(String location) {

Assert.notNull(location, "Location must not be null");

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开头,则创建为一个ClassPathResource,否则则试图以URL的方式加载资源,创建一个UrlResource.

  • 路径包含通配符的
             这种情况是最复杂的,涉及到层层递归,那我把加了注释的代码发出来大家看一下,其实主要的思想就是
1.先获取目录,加载目录里面的所有资源
2.在所有资源里面进行查找匹配,找出我们需要的资源

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {

//拿到能确定的目录,即拿到不包括通配符的能确定的路径  比如classpath*:/aaa/bbb/spring-*.xml 则返回classpath*:/aaa/bbb/                                     //如果是classpath*:/aaa/*/spring-*.xml,则返回 classpath*:/aaa/

String rootDirPath = determineRootDir(locationPattern);

//得到spring-*.xml

String subPattern = locationPattern.substring(rootDirPath.length());

//递归加载所有的根目录资源,要注意的是递归的时候又得考虑classpath,与classpath*的情况,而且还得考虑根路径中是否又包含通配符,参考上面那张流程图

Resource[] rootDirResources = getResources(rootDirPath);

Set<Resource> result = new LinkedHashSet<Resource>(16);

//将根目录所有资源中所有匹配我们需要的资源(如spring-*)加载result中

for (Resource rootDirResource : rootDirResources) {

rootDirResource = resolveRootDirResource(rootDirResource);

if (isJarResource(rootDirResource)) {

result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));

}

else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {

result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));

}

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()]);

}

 
值得注解一下的是determineRootDir()方法的作用,是确定根目录,这个根目录必须是一个能确定的路径,不会包含通配符。如果classpath*:aa/bb*/spring-*.xml,得到的将是classpath*:aa/  可以看下他的源码
 

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);

}

 

分析到这,结合测试我们可以总结一下:
1.无论是classpath还是classpath*都可以加载整个classpath下(包括jar包里面)的资源文件。
2.classpath只会返回第一个匹配的资源,查找路径是优先在项目中存在资源文件,再查找jar包。
3.文件名字包含通配符资源(如果spring-*.xml,spring*.xml),   如果根目录为"", classpath加载不到任何资源, 而classpath*则可以加载到classpath中可以匹配的目录中的资源,但是不能加载到jar包中的资源
    
      第1,2点比较好表理解,大家可以自行测试,第三点表述有点绕,举个例,现在有资源文件结构如下:
 
classpath:notice*.txt                                                               加载不到资源
classpath*:notice*.txt                                                            加载到resource根目录下notice.txt
classpath:META-INF/notice*.txt                                      
 
 加载到META-INF下的一个资源(classpath是加载到匹配的第一个资源,就算删除classpath下的notice.txt,他仍然可以
                                                                       
                         加载jar包中的notice.txt)
classpath:META-*/notice*.txt                                              加载不到任何资源
classpath*:META-INF/notice*.txt                                        加载到classpath以及所有jar包中META-INF目录下以notice开头的txt文件
classpath*:META-*/notice*.txt                                             只能加载到classpath下 META-INF目录的notice.txt
 
文章参考:http://www.cnblogs.com/softidea/p/5683174.html
 
 

classpath: VS classpath*:的更多相关文章

  1. web.xml 配置中classpath: 与classpath*:的区别

    首先 classpath是指 WEB-INF文件夹下的classes目录 解释classes含义: 1.存放各种资源配置文件 eg.init.properties log4j.properties s ...

  2. classpath: 和classpath*:的区别

    classpath本质是jvm的根路径,jvm获取资源都是从该根路径下找的,注意这个根路径是个逻辑路径,并不是磁盘路径.比如两个jar包的路径是/a/a.jar和/b/b.jar,但是用classpa ...

  3. 在web.xml中classpath和classpath*的区别

    classpath 和 classpath* 区别: classpath:只会到你指定的class路径中查找找文件; classpath*:不仅包含class路径,还包括jar文件中(class路径) ...

  4. WEB项目web.xml文件中classpath: 跟classpath*:使用的区别

    引用一篇很不错的文章:http://blog.csdn.net/wxwzy738/article/details/16983935 首先 classpath是指 WEB-INF文件夹下的classes ...

  5. 【转载】Spring加载resource时classpath*:与classpath:的区别

    免责声明:     本文转自网络文章,转载此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除.     原文作者:kyfxbl     原文地址: spring配置中classpath和cla ...

  6. web.xml中classpath:和classpath*: 有什么区别?

    web.xml中classpath:和classpath*:     IccBoY applicationContext.xml 配置文件的存放位置 web.xml中classpath:和classp ...

  7. spring加载资源文件中classpath*与classpath的区别

    在spring和MyBatis继承的时候,配置mapperLocations.一开始配置是这样的. 需要加载路径为com/thomas/base/mapper和com/thomas/bu/mapper ...

  8. Spring配置中的"classpath:"与"classpath*:"的区别研究(转)

    Spring配置中的"classpath:"与"classpath*:"的区别研究(转)   概念解释及使用场景: classpath是指WEB-INF文件夹下 ...

  9. 转:web.xml 配置中classpath: 与classpath*:的区别

    原文链接:web.xml 配置中classpath: 与classpath*:的区别 引用自:http://blog.csdn.net/wxwzy738/article/details/1698393 ...

随机推荐

  1. HTML 迷宫

    今天补个遗,将很久以前研究 HTML5 的时候写的生成迷宫.迷宫寻路程序整理出来. 下载链接在文章最后. 简介 为什么要做这个 HTML5 迷宫程序?因为我喜欢.我愿意.也是向老程序员学习(见第5节) ...

  2. Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复

    Android中的状态保存和恢复 Android中的状态保存和恢复, 包括Activity和Fragment以及其中View的状态处理. Activity的状态除了其中的View和Fragment的状 ...

  3. 如何将github上的 lib fork之后通过podfile 改变更新源到自己fork的地址

    解决办法: http://stackoverflow.com/questions/20936885/cocoapods-and-github-forks 就是fork完后,提交更改到自己的github ...

  4. 小白挑战:AsyncTask源码分析

    //AsyncTask从本质上讲,是对ThreadPool和handler的封装. 在学习线程池相关的知识时,看到书中提到AsyncTask的实现中使用到了ThreadPool,于是把源码翻了出来, ...

  5. Python之基础

    # 需要导入字符编码,否则遇到中文会报错 # coding=utf-8 # 1 定义变量 a = 10 b = 2 c = a+b print(c) # 2 判断语句 score = 90 if sc ...

  6. DP优化与换零钱问题

    1 当贪心不再起效的时候 对于换零钱问题,最简单也是性能最好的方法就是贪心算法.可是贪心算法一定要满足面值相邻两个零钱至少为二倍关系的前提条件.例如1,2,5,10,20……这样的零钱组应用贪心最简单 ...

  7. SQL Server编程入门

    SQL编程要比Java编程.C#编程简单许多,下面我们直接讲干货21:04:31 使用变量 局部变量 在T-SQL中,局部变量的名称必须以标记@作为前缀.T-SQL的局部变量其实和Java中的局部变量 ...

  8. 在Windows Server 2012 R2中搭建SQL Server 2012故障转移集群

    需要说明的是我们搭建的SQL Server故障转移集群(SQL Server Failover Cluster)是可用性集群,而不是负载均衡集群,其目的是为了保证服务的连续性和可用性,而不是为了提高服 ...

  9. 搭建 OpenStack 实验环境 - 每天5分钟玩转 OpenStack(16)

    在学习 OpenStack 各服务之前,让我们先搭建起一个实验环境. 毋庸置疑,一个看得到摸得着而且允许我们随便折腾的 OpenStack 能够提高我们的学习效率. 因为是我们自己学习用的实验环境,C ...

  10. Linux一些经典书籍

    一.linux之路 1.入门篇 <LINUX权威指南>书不错,写的很全面也比较广,涉及的不深,做为入门书籍不错,可以比较全面的了解linux .另外比较热门的也可以看看<鸟哥的私房菜 ...