容器的基本实现_Spring 源码阅读

容器的基本实现_Spring 源码阅读

第一行代码_资源加载相关

从此行读源码

1
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("xxx.xml"))

ps: XmlBeanFactory 已经被弃用, 但思路一样,重点是loadBeanDifinitions。

替代:

1
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xxx.xml");

一 . 返回 ClassPathResource

Resource 概览

YzUmI.png

相关实现

1
2
3
4
5
6
7
8
* @see WritableResource
* @see ContextResource
* @see UrlResource
* @see FileUrlResource
* @see FileSystemResource
* @see ClassPathResource
* @see ByteArrayResource
* @see InputStreamResource

为什么需要抽象 Resource,不用 Url ?

  1. Url 注册 handle 读取资源,但是没有 ClassPath 等资源的 handle,虽然可以自己注册 handle,但是不利于使用者代码编写(需要熟悉 Url 实现机制)。
  2. 没有相关方法

源码

1.ClassPathResource

1
2
3
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
1
2
3
4
5
6
7
8
9
10
11
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path); // 详见2
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.absolutePath = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.clazz = null;
}

2.cleanPath

主要的目的是消除冗余的路径信息,获得绝对路径。

总结:

  1. 将Windows路径中的反斜杠\替换为正斜杠/
  2. 处理路径中的重复斜杠,将它们替换为单个斜杠。
  3. 处理路径中的".",将它们去掉。
  4. 处理路径中的"..",将它们和它前面的路径部分都去掉。

例如:

  • /foo/bar/../baz 会被规范化为 /foo/baz
  • /foo/./bar/baz 会被规范化为 /foo/bar/baz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public static String cleanPath(String path) {

if (!hasLength(path)) {//判空 详见3
return path;
}
// 将路径中的"\\" (即WINDOWS_FOLDER_SEPARATOR) 换为 "/" (即FOLDER_SEPARATOR), 例如"C:\\ClassPathResourceTests.java" 详见4
String normalizedPath = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
String pathToUse = normalizedPath;

// Shortcut if there is no work to do
if (pathToUse.indexOf('.') == -1) {
return pathToUse;
}

// 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);
//这是因为在后面的处理过程中,如果前缀不为空,会在处理过程中加上一个路径分隔符 FOLDER_SEPARATOR,从而使得最终结果的前缀和传入的前缀不一致。因此需要在处理之前将前缀置空,以避免这个问题。
if (prefix.contains(FOLDER_SEPARATOR)) {
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);
// we never require more elements than pathArray and in the common case the same number
Deque<String> pathElements = new ArrayDeque<>(pathArray.length);
// ".." 的数量
int tops = 0;

for (int i = pathArray.length - 1; i >= 0; i--) {
String element = pathArray[i];
// CURRENT_PATH == "."
if (CURRENT_PATH.equals(element)) {
// Points to current directory - drop it.
}
//".." == TOP_PATH
//..表示返回上一级路径
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.addFirst(element);
}
}
}

// All path elements stayed the same - shortcut
if (pathArray.length == pathElements.size()) {
return normalizedPath;
}
// Remaining top paths need to be retained.
for (int i = 0; i < tops; i++) {
pathElements.addFirst(TOP_PATH);
}
// If nothing else left, at least explicitly point to current path.

// 判断是否是根路径
if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) {
pathElements.addFirst(CURRENT_PATH);
}
//用 "/" 连接pathElements元素
final String joined = collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
// avoid string concatenation with empty prefix
return prefix.isEmpty() ? joined : prefix + joined;
}

3.hasLength

1
2
3
public static boolean hasLength(@Nullable String str) {
return (str != null && !str.isEmpty());
}

4.replace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 用 newPatten 替换 inString 中所有 oldPattern
*/
public static String replace(String inString, String oldPattern, @Nullable String newPattern) {
if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
return inString;
}
int index = inString.indexOf(oldPattern);
if (index == -1) {
// no occurrence -> can return input as-is
return inString;
}
// 扩大了就提前预留空间
int capacity = inString.length();
if (newPattern.length() > oldPattern.length()) {
capacity += 16;
}
StringBuilder sb = new StringBuilder(capacity);

int pos = 0; // our position in the old string
int patLen = oldPattern.length();
while (index >= 0) {
sb.append(inString, pos, index);
sb.append(newPattern);
pos = index + patLen;
index = inString.indexOf(oldPattern, pos);
}

// append any characters to the right of a match
sb.append(inString, pos, inString.length());
return sb.toString();
}

二. 返回 XmlBeanFactory

ps: 此类已经被弃用

此过程重点注意 loadBeandefinitions 方法即可

参考

Spring 源码深度解析


容器的基本实现_Spring 源码阅读
http://lanfunoe.site/2023/03/10/Spring 的结构组成_Spring 源码阅读/
作者
John Doe
发布于
2023年3月10日
许可协议