ClassPathResource详解
ClassPathReource resource=new ClassPathResource("spring_beans.xml");
public
class ClassPathResource
extends
在ClassPathResource中,含参数String path的构造函数:
public ClassPathResource(String
path
this (
path , (ClassLoader)
null);
}
2:上述构造函数指向了另外一个构造函数:
public ClassPathResource (String
path , ClassLoader
classLoader
Assert.
notNull(
path,
"Path must not be null");
pathToUse = StringUtils.
cleanPath(
path);
if (
pathToUse .startsWith(
"/")) {
pathToUse =
pathToUse
}
this .
path =
pathToUse;
this .
classLoader = (
classLoader !=
null ?
classLoader : ClassUtils.
getDefaultClassLoader());
}
能够看到path由StringUtils的cleanPath方法返回了pathToUse。由此,我们找到StringUtils的cleanPath方法
public
abstract
class
public
static String cleanPath (String
path
if (
path ==
null) {
return
null
}
pathToUse =
replace(
path ,
WINDOWS_FOLDER_SEPARATOR ,
FOLDER_SEPARATOR);
int
prefixIndex =
pathToUse .indexOf(
":"
prefix =
""
if (
prefixIndex
prefix =
pathToUse .substring(0,
prefixIndex
pathToUse =
pathToUse .substring(
prefixIndex
}
if (
pathToUse .startsWith(
FOLDER_SEPARATOR)) {
prefix =
prefix +
FOLDER_SEPARATOR;
pathToUse =
pathToUse
}
pathArray =
delimitedListToStringArray(
pathToUse,
FOLDER_SEPARATOR
pathElements =
new
int
tops
for (
int
i =
pathArray.
length - 1;
i >= 0;
i
element =
pathArray [
i
if (
CURRENT_PATH .equals(
element)) {
// Points to current directory - drop it.
}
else
if (
TOP_PATH.equals(
element)) {
// Registering top path found.
tops
}
else
if (
tops
// Merging path element with element corresponding to top path.
tops
}
else
// Normal path element found.
pathElements .add(0,
element
}
}
}
// Remaining top paths need to be retained.
for (
int
i = 0;
i <
tops;
i++) {
pathElements .add(0,
TOP_PATH);
}
return
prefix +
collectionToDelimitedString(
pathElements,
FOLDER_SEPARATOR
}
4:StringUtils类中 replace方法
public
static String replace (String
inString , String
oldPattern , String
newPattern
if (!
hasLength(
inString ) || !
hasLength(
oldPattern) ||
newPattern ==
null
return
inString
}
sb =
new
int
pos = 0;
// our position in the old string
int
index =
inString .indexOf(
oldPattern
// the index of an occurrence we've found, or -1
int
patLen =
oldPattern.length();
while (
index
sb.append(
inString .substring(
pos ,
index
sb.append(
newPattern
pos =
index +
patLen;
index =
inString .indexOf(
oldPattern,
pos
}
sb.append(
inString .substring(
pos
// remember to append any characters to the right of a match
return
sb
}
5:StringUtils类类中的hasLength方法。由此可以看出,同样的方法名,不同的方法签名,然后在其中一个方法中引用另外一个方法。好多类都是这么用的,就像开始的时候的构造函数那样,虽然不知道好处 是什么,但先记下来。
public
static
boolean
hasLength (String
str
) {
return
hasLength((CharSequence)
str
);
}
public
static
boolean
hasLength (CharSequence
str
) {
return
(
str
!=
null
&&
str
.length() > 0);
}
跟踪到这里,可以知道hasLength方法的目的就是str不为空且str的长度大于0。突然发现CharSequence这个类没接触过,来看一下它的源码
public
interface
CharSequence{}
额,源码没看懂 就不粘贴过来了。
7:回到StringUtils的replace方法
首先判断传入的三个参数,如果为空后者长度小于0,直接返回inString;那么我看一下这三个参数都是什么:
inString :path 这个就是我们传入的文件名
private
static
final
String
WINDOWS_FOLDER_SEPARATOR
=
"\\"
;
private
static
final
String
FOLDER_SEPARATOR
=
"/"
;这两个是文件分隔符
然后给局部变量index赋值,通过查阅API:
public int
indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引。
意思就是在path中查找"\\",例如我写文件的绝对路径是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM,我就需要循环的读取“\\”,接下来while循环中出现了substring方法,继续查阅API:
String
substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。该子字符串从指定索引处的字符开始,直到此字符串末尾。
public String substring(int beginIndex,
int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的
beginIndex 处开始,直到索引
endIndex - 1 处的字符。因此,该子字符串的长度为
endIndex-beginIndex
故此 ,第一次循环会把path路径中从0索引开始,直到第一个"\\"之间的内容添加到StringBuffer中,然后再在StringBuffer中添加“/”,接下来pos和index都需要改变,要往后挪。因为循环需要往后走,我们要找到第二个“\\”,觉得这个有点算法的意思。返回sb.toString()。
总结一下replace方法,本意是根据传入的路径path,如果是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM这种格式的,给转换成D:/文件/API/JDK_API_1_6_zh_CN.CHM这种格式。
8:StringUtils的cleanPath方法:
通过replace的返回值,我们得到了可以用的路径pathToUse,然后我们要把这个路径下“:”给找出来,正如代码
int
prefixIndex
=
pathToUse
.indexOf(
":"
);
那样,需要知道,indexOf方法只要没找到相应的字符,就会返回-1,所以在下面的判断中才会以perfixIndex是否为-1来进行判断。如果路 径中有“:”,接着以D:/文件/API/JDK_API_1_6_zh_CN.CHM举例,prefix="D:" pathToUse="/文件/API/JDK_API_1_6_zh_CN.CHM ",这个很有意思,因为程序不知道我们输入的是绝对路径 带D:的这种 ,还是/开头的这种,或者说相对路径,程序直接全给你判断了。接下来会判断pathToUse是否以“/"开头,是的话prefix会加上“/”,现在的prefix有两种情况,可能是"D:/"这种,也可能是"/"这种,而pathToUse肯定是“文件/API/JDK_API_1_6_zh_CN.CHM ”这种了。
String[]
pathArray
= delimitedListToStringArray(
pathToUse
,
FOLDER_SEPARATOR
);
看到这句代码,我估计是把pathToUse给拆成字符串数组里,就像是这样,文件 API ***的这种。接下来看看具体的代码是不是这样:
9:StringUtils的delimitedListToStringArray方法
public
static
String[] delimitedListToStringArray(String
str
, String
delimiter
) {
return
delimitedListToStringArray(
str
,
delimiter
,
null
);
}
public
static
String[] delimitedListToStringArray(String
str
, String
delimiter
, String
charsToDelete
) {
if
(
str
==
null
) {
return
new
String[0];
}
if
(
delimiter
==
null
) {
return
new
String[] {
str
};
}
List<String>
result
=
new
ArrayList<String>();
if
(
""
.equals(
delimiter
)) {
for
(
int
i
= 0;
i
<
str
.length();
i
++) {
result
.add(deleteAny(
str
.substring(
i
,
i
+ 1),
charsToDelete
));
}
}
else
{
int
pos
= 0;
int
delPos
;
while
((
delPos
=
str
.indexOf(
delimiter
,
pos
)) != -1) {
result
.add(deleteAny(
str
.substring(
pos
,
delPos
),
charsToDelete
));
pos
=
delPos
+
delimiter
.length();
}
if
(
str
.length() > 0 &&
pos
<=
str
.length()) {
// Add rest of String, but not in case of empty input.
result
.add(deleteAny(
str
.substring(
pos
),
charsToDelete
));
}
}
return
toStringArray(
result
);
}
先看看传入的参数:
str:pathToUse,就是文件/API/JDK_API_1_6_zh_CN.CHM
delimiter:"/"
charsToDelete:null
public
static
String deleteAny(String
inString
, String
charsToDelete
) {
if
(!hasLength(
inString
) || !hasLength(
charsToDelete
)) {
return
inString
;
}
StringBuilder
sb
=
new
StringBuilder();
for
(
int
i
= 0;
i
<
inString
.length();
i
++) {
char
c
=
inString
.charAt(
i
);
if
(
charsToDelete
.indexOf(
c
) == -1) {
sb
.append(
c
);
}
}
return
sb
.toString();
}
如果说我们传入的"/"等于""的话,显然是不可能,我们所假如的话,会把pathTOUse倒着循环,每个字符都摘出来,然后当成字符串用,传入deleteAny中,然后又是循环,对每个字符而言,如果charsToDelete中没有这个字符,就在StringBuilder中添加这个字符。返回值是String。当然了,这个还没用到。我们用到的是那个很复杂的else
delimitedListToStringArray方法之后,接着往后循环,最终的结果就是实现了把pathToUse给切割成若干个String的形式。
10:回到StringUtils的cleanPath方法
我们遇到了一个倒着的循环,如果说我们这个是特别正常的路径,就相当于复制了,如果是以.或者..结尾的这些内容,我们就把它给忽略了。
我得承认上面这些过程真的好复杂,其实就是做了一件事,对输入的路径进行了处理,只不过考虑的情况多了一点。所以我决定还是用debug来走一遍看看。
这时我用的是绝对路径
终于熬到了这个方法的结束。
11:回到ClassPathResource的构造函数
this
.
classLoader
= (
classLoader
!=
null
?
classLoader
: ClassUtils.getDefaultClassLoader());
如果传入的classLoaser有值,就返回这个值,如果没有,就获取一个。
public
static
ClassLoader getDefaultClassLoader() {
ClassLoader
cl
=
null
;
try
{
cl
= Thread.currentThread().getContextClassLoader();
}
catch
(Throwable
ex
) {
// Cannot access thread context ClassLoader - falling back...
}
if
(
cl
==
null
) {
// No thread context class loader -> use class loader of this class.
cl
= ClassUtils.
class
.getClassLoader();
if
(
cl
==
null
) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try
{
cl
= ClassLoader.getSystemClassLoader();
}
catch
(Throwable
ex
) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return
cl
;
}
写到这,我们的ClassPathResouce resouce实例就有了path 和 classLoader这两个关键属性。
如果我们想获取输入流
@Override
public
InputStream getInputStream()
throws
IOException {
InputStream
is
;
if
(
this
.
clazz
!=
null
) {
is
=
this
.
clazz
.getResourceAsStream(
this
.
path
);
}
else
if
(
this
.
classLoader
!=
null
) {
is
=
this
.
classLoader
.getResourceAsStream(
this
.
path
);
}
else
{
is
= ClassLoader.getSystemResourceAsStream(
this
.
path
);
}
if
(
is
==
null
) {
throw
new
FileNotFoundException(getDescription() +
" cannot be opened because it does not exist"
);
}
return
is
;
}
会判断clazz 有没有值,classLoader有没有值,然后再获取输入流。前两天整理了java.lang.Class这个类的意思,现在就能用一点了,先看看定义的一些属性
private
final
String
path
;
private
ClassLoader
classLoader
;
private
Class<?>
clazz
;
ClassPathResource有好几个构造函数,有的构造函数会传入classLoader,有的会传入clazz,这个clazz就是相应的类在JVM上的实例,显然上面的例子中并没有这个东西,而classLoader是有的,所以通过classLoader获取输入流。我觉得对ClassPathResource理解的更透彻了,虽然大部分时间都是在对path进行处理。近期还要看看ClassLoader,还不是很清楚它的工作机制。