接上一篇,我们通过ClassUtils.forName()方法完成了对依赖注入接口的加载,接着DefaultListableBeanFactory实例化了一个静态map,这个map用于存储bean工厂实例和序列化id的映射关系。这个流程结束后,XmlBeanFactory的类初始化完成,然后就进入它的构造方法了。
1.再看一下Demo中构造方法的参数,是一个ClassPathResource实例,ClassPathResource通过构造方法构造一个配置文件的实例对象,很明显,进入构造方法之前,需要先进入ClassPathResource的构造方法:
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
这个构造方法调用了另一个构造方法,参数是path和类加载器。进去看:
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());
}
首先调用StringUtils.cleanPath方法,进去看:
public static String cleanPath(String path) {
if (path == null) {
return null;
}
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.
int prefixIndex = pathToUse.indexOf(":");
String prefix = "";
if (prefixIndex != -1) {
prefix = pathToUse.substring(0, prefixIndex + 1);
if (prefix.contains("/")) {
prefix = "";
}
else {
pathToUse = pathToUse.substring(prefixIndex + 1);
}
}
if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
prefix = prefix + FOLDER_SEPARATOR;
pathToUse = pathToUse.substring(1);
}
String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
List<String> pathElements = new LinkedList<String>();
int tops = 0;
for (int i = pathArray.length - 1; i >= 0; i--) {
String 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 > 0) {
// 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);
}
这方法的作用是将传入路径参数处理成可供IOC容器识别解析的路径。
1)首先是对Windows的文件分隔符“\”替换成“/”
2)接着对路径的前缀进行处理,如果路径包含“:”,将前缀取出保存,然后对“:”之后的路径进行解析
3)根据路径中存在的"/"情况,将整个路径截断成各个层级,并存入一个数组中,数组从前至后分别存放从上到下的目录
4)接着,沿着目录层级,从下至上的递归遍历:三种情况:a.如果存在标识当前目录的"./",则直接跳过;b.如果存在标识上级目录的"../",则对其进行记录,如果记录数目大于零的情况下,遍历到了包含实际目录信息的元素,则进行一次抵消,如"../asf/dasf/../../dfsad.xml",当遍历到dasf这一层时,有两个"../",这时不会保存dasf,而是对"../"进行一次抵消,因为我们知道"../"标识的就是父目录,dasf是实际的父目录;c.最先保存的信息一定是位于最后一层的配置文件的信息,也就是dfsad.xml
5)经过上述处理之后,如果还有"../"未被抵消,则将剩下的加入最终路径。将遍历后的路径与之前保存的前缀组合,得出最后路径,引用上例,我们会得到../dfsad.xml
2.对路径处理完了之后,调用ClassUtils.getDefaultClassLoader方法获取默认类加载器。至此ClassResource实例化完成。这里针对Resource进行一下介绍。
Resource接口被Spring用来封装底层资源,上类图:
我们可以看到,Resource的父接口是InputStreamSource,这个接口只定义了一个方法getInputStream()用于返回inputStream对象,这说明一切可以返回inputStream的资源都可以被Resource封装。回到Resource,我们可以看到,Resource为底层资源封装了三个属性:exists(是否存在)、isReadable(是否可通过inputStream读取)、isOpen(该资源是否持有一个打开的InputStream)。除此之外,Resource还提供了获取资源URL、URI、资源信息描述、相关资源等方法。从类图中我们也可以看到,spring针对不同的资源类型,如URL、file、ByteArray等提供了不同的Resource实现。介绍完了,继续走。
3.调用XmlBeanFactory的构造方法XmlBeanFactory(Resource resoutce),该构造方法指向这个方法:
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
一直向上调用各级父类的构造方法,然后就调用loadBeanDefinitions(resource)对资源文件进行解析了。