2. maven项目中的classpath
2.1 maven项目中对应的classpath
maven
项目分为src/main/java
目录、src/main/resources
目录,src/test/java
目录,/peis-src/test/resources
目录:
- 其中
src/main/java
目录、src/main/resources
目录对应到项目的target\classes
目录,如果在src/main
目录调用classpath
,则class
的根目录为target\classes
; -
src/test/java
目录,src/test/resources
目录对应到test-classes
目录,如果在src/test/java
目录调用classpath
,则class
的根目录为target\test-classes
;
2.2 classpath*:
与classpath:
比对结果
classpath:
在整个目录下找配置确定的一个,且是第一个(资源使用通配符除外),使用资源使用通配符只在根目录查找
classpath*:
只匹配根路径(包括jar包根路径,但不包括jar包测试路径test-resource
),没有找子文件夹。
2.3 结果对照表
- 下表
src/main/resources
以resource
代替 - 下表
src/test/resources
以test-resource
代替 - 测试代码位置位于
src/test/java
目录
location | 查找内容 |
---|---|
classpath:lyz.txt |
test-resource/lyz.txt :(测试代码位置位于src/test/java 目录,则首要查找为test-resource 目录,若无则去resource 目录查找;再无则去jar目录查找)该路径查找的是整个目录(包括jar目录)中匹配到的第一文件
|
classpath:l*.txt |
test-resource/lyz.txt :只匹配test-resource 根目录,没有找子文件夹与resource 目录及jar包
|
classpath*:lyz.txt |
test-resource/lyz.txt 、resource/lyz.txt 、jar路径/resource/lyz.txt :只匹配根路径(不包括jar包测试路径test-resource ),没有找子文件夹
|
classpath*:l*.txt |
test-resource/lyz.txt 、resource/lyz.txt 、jar路径/resource/lyz.txt :只匹配根路径(包括jar包根路径,但不包括jar包测试路径test-resource ),没有找子文件夹
|
classpath*:/*/lyz.txt |
/resource/txt/lyz.txt 、jar路径/resource/txt/lyz.txt :匹配根路径下子文件夹中的文件,(包括jar包根路径,但不包括jar包测试路径test-resource )
|
2.4 Junit测试代码
@Test
public void testPath() throws Exception {
String location="classpath*:/*/lyz.txt";
ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
Resource[] source = resourceLoader.getResources(location);
System.out.println("source.size: "+source.length);
for (int i = 0; i < source.length; i++) {
Resource resource = source[i];
System.out.println(resource);
}
}
3. spring 查找代码解析
3.1 原理(核心逻辑)
这是代码中的主类 PathMatchingResourcePatternResolver
类,位于包 org.springframework.core.io.support
下,该类中的 getResources
函数是逻辑的核心,如下:
@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()))) {
// case 1:如果以classpath*开头且包含?或者* ,例如查找: classpath*: applicationContext-*.xml
return findPathMatchingResources(locationPattern);
}
else {
// case 2: 不包含?或者*,直接全名查找,例如查找: classpath*: applicationContext-test.xml
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// 以 classpath:开头
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// case 3: 如果不是以classpath*开头且包含?或者* ,例如查找: classpath: applicationContext-*.xml
return findPathMatchingResources(locationPattern);
}
else {
// case 4: 如果不是以classpath*开头且不包含?或者* ,例如查找: classpath: applicationContext-test.xml
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
3.2 如果以classpath*
开头且包含?或者*
例如查找: classpath*: applicationContext-*.xml
,使用findPathMatchingResources
函数,看下该函数:
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//函数determineRootDir函数是拿到能够确定的目录,如 classpath*:/aaa/bbb/applicationContext-*.xml 则返回classpath*:/aaa/bbb/
//classpath*:/aaa/*/applicationContext-*.xml,则返回 classpath*:/aaa/
String rootDirPath = determineRootDir(locationPattern);
// 获取字符串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);
URL rootDirURL = rootDirResource.getURL();
if (equinoxResolveMethod != null) {
if (rootDirURL.getProtocol().startsWith("bundle")) {
rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
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.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
总体来说:该函数把locationPattern
拆分成两部分:rootDirPath
和subPattern
,rootDirPath
是根目录路径,subPattern
是子目录路径匹配规则字符串。遍历根目录下的所有子目录、并得到所有的子目录在doFindPathMatchingFileResources(rootDirResource, subPattern)
方法中,再根据子目录逐个逐个去匹配subPattern
。
3.3如果以classpath*
开头且不包含?或者*,直接全名查找
例如查找: classpath*: applicationContext-test.xml
,使用findAllClassPathResources
函数,看下该函数:
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
其实核心在doFindAllClassPathResources
,该函数扫描该路径下的所有资料,其函数体如下:
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)) {
// 如果路径为空的话,就找到所有jar包,加到result中(该函数不再深入)
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
在该函数中重要:Enumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
即如果当前类加载器cl
不为空的话,就调用cl.getResources(path)
,cl
为空的话就使用系统类加载器去加载,事实上cl.getResources(path)
内容使用的即是双亲委派模型(当前类加载器加载资源,如果存在父加载器,则用父加载器进行加载)。
3.4 如果不是以classpath*
开头且包含?或者*
例如查找: classpath: applicationContext-*.xml
,使用函数findPathMatchingResources
,类似3.2情况。
3.5 如果不是以classpath*
开头且不包含?或者*
例如查找: classpath: applicationContext-test.xml
,直接getResourceLoader().getResource(locationPattern)
,即直接使用当然的资源加载器去加载,这里默认使用的是DefaultResourceLoader()
。
网友评论