开始之前我先说明几点
1、Spring版本3.1.3
2、我写的会尽量的细
3、我对Spring了解的也没那么清楚,希望能和大家一起学习学习,如果有理解错误的地方,请指出,在这里先谢过了。
我们从XmlBeanFactory开始,虽然在这个版本里已经不被建议使用了,被加上了@Deprecated注解,但是不妨碍我们从这里开始学习。
XmlBeanFactory在使用的时候一般是这样的
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationcontext.xml"));
那么这里的ClassPathResource是个什么东西呢?这一篇我们要说的就是它!
先看下Resource的类图。
下面直接从代码上来看
new ClassPathResource("applicationcontext.xml")
进入此构造方法
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
此构造方法,又调用了另一个构造方法
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
虽然这个构造方法很简单,我们还是解析下这个构造方法,如果path为null的话,会报一个异常
以下是Assert的notNull方法
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
如果不是null的话,会先调用StringUtils的cleanPath方法
实际上这个方法完全可以当做一道算法题来面试
先大概说下这个方法的作用与算法,然后我们把代码贴上来,在代码里做一些注释就好了。
cleanPath就是将一个路径字符串进行简化,得到一个简单的路径。
比如1/./2/../../../../3/4/././../../../5最终简化为../../../5
考虑到路径可能是这样的file:/1/2/3,那么file:这个前缀是不应该动的。所以算法大致就是这样的。
先拿到前缀,然后对后缀路径进行简化,对后缀路径用slash(斜线,反斜线 inversing slash)进行分割,得到一个路径数组。
对路径数组从后往前进行循环,并初始下(..)的数量为0
如果循环的值为(.),那么忽略
如果循环的值为(..),那么(..)的数量+1
如果循环的值不是(..),再分两种情况,如果(..)的数量大于0,那么(..)数量-1,否则将循环的值加入到一个LinkedList的开头。
循环结束之后,如果(..)的数量还大于0,那么将多出的(..)再加到LinkedList的开头(这个老版本是没有的,所以../太多的时候回出现bug,比如../../../a.txt,会返回一个a.txt,显然是不对的)。最后再将前缀加上来就完事了。
public static String cleanPath(String path) {
String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR,FOLDER_SEPARATOR);
// Strip prefix from path to analyze, to not treat it as part of the
// first path element. This is necessary to correctly parse paths like
// "file:core/../core/io/Resource.class", where the ".." should just
// strip the first "core" directory while keeping the "file:" prefix.
// 处理一些文件前缀。如"file:", "jndi:"等等
int prefixIndex = pathToUse.indexOf(":");
String prefix = "";
if (prefixIndex != -1) {
prefix = pathToUse.substring(0, prefixIndex + 1);
pathToUse = pathToUse.substring(prefixIndex + 1);
}
// 使用slash分割pathToUse
String[] pathArray = delimitedListToStringArray(pathToUse,FOLDER_SEPARATOR);
// 用于储存clean后的path
List pathElements = new LinkedList();
// 记录当前还有多少个../
int tops = 0;
for (int i = pathArray.length - 1; i >= 0; i--) {
// 如果是"./",表示当前路径,直接跳过
if (CURRENT_PATH.equals(pathArray[i])) {
// do nothing
} else if (TOP_PATH.equals(pathArray[i])) {
// 将../的个数+1
tops++;
} else {
// 如果../个数大于0,那么抵消一次
if (tops > 0) {
tops--;
}
// 如果“../”已经抵消完毕,将此目录放到LinkedList中去,每次都应该插在链表的头部,这样正好就形成了从高到低的目录结构,这是这个算法的重点
else {
pathElements.add(0, pathArray[i]);
}
}
}
// 将多余的../加上来,在Spring的老版本中是没有这行的,所以当相对路径里../过多的时候就会出现Bug了。
for (int i = 0; i < tops; i++) {
pathElements.add(0, TOP_PATH);
}
// 将前缀加上来并返回
return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
}
继续回到public ClassPathResource(String path, ClassLoader classLoader)这个构造方法,下一行会去掉首个斜线(the leading slash),而这里的classLoader为null,所以classLoader为ClassUtils.getDefaultClassLoader()。
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back to system class loader...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
}
return cl;
}
这里默认使用的是Thread.currentThread().getContextClassLoader(),如果加载不成功,才会使用ClassUtil.class.getClassLoader()。大家可以多去看下类加载器,这个还是挺重要的。
到此,我们在new一个XmlBeanFactory的时候需要的参数Resource是怎么来的,已经清楚了,这个实际上还是非常简单的,主要讲了StringUtils的cleanPath方法。
这里提个小问题,大家可以思考下,Resource使用的是ClasspathResource,而Resource有许多的实现类,为什么不用其他的呢?比如FileSystemResource,还有为什么我们传入ClasspathResource里面的值是相对于src的一个路径呢?