纯天然绿色学渣
  • <i class="menu-item-icon fa fa-fw fa-home"></i> <br/>首页
  • <i class="menu-item-icon fa fa-fw fa-tags"></i> <br/>标签<span class="badge">28</span>
  • <i class="menu-item-icon fa fa-fw fa-th"></i> <br/>分类<span class="badge">12</span>
  • <i class="menu-item-icon fa fa-fw fa-archive"></i> <br/>归档<span class="badge">42</span>
  • <i class="menu-item-icon fa fa-fw fa-tree"></i> <br/>工具

  • 搜索

AbstractQueuedSynchronizer 原理分析 - 独占/共享模式

发表于 2019-10-24 | 更新于 2019-10-25 | 分类于 Java | 评论数: | 阅读次数:

概念

AbstractQueuedSynchronizer (抽象队列同步器,简称 AQS),AQS 是很多同步器的基础框架,比如 ReentrantLock、CountDownLatch 和 Semaphore 等都是基于 AQS 实现的。除此之外,我们还可以基于 AQS,定制出我们所需要的同步器。

原理

在 AQS 内部,通过维护一个FIFO 队列来管理多线程的排队工作。在公平竞争的情况下,无法获取同步状态的线程将会被封装成一个节点,置于队列尾部。入队的线程将会通过自旋的方式获取同步状态,若在有限次的尝试后,仍未获取成功,线程则会被阻塞住。大致示意图如下:

当头结点释放同步状态后,且后继节点对应的线程被阻塞,此时头结点线程将会去唤醒后继节点线程。后继节点线程恢复运行并获取同步状态后,会将旧的头结点从队列中移除,并将自己设为头结点。大致示意图如下:

常用方法

主要介绍三组重要的方法,通过使用这三组方法即可实现一个同步组件。
第一组方法是用于访问/设置同步状态的,如下:

方法说明
int getState()设置同步状态
void setState()设置同步状态
boolean compareAndSetState(int expect, int update)通过 CAS 设置同步状态

第二组方需要由同步组件覆写。如下:

方法说明
boolean tryAcquire(int arg)独占式获取同步状态
boolean tryRelease(int arg)独占式释放同步状态
int tryAcquireShared(int arg)共享式获取同步状态
boolean tryReleaseShared(int arg)共享式私房同步状态
boolean isHeldExclusively()检测当前线程是否获取独占锁

第三组方法是一组模板方法,同步组件可直接调用。如下:

方法说明
void acquire(int arg)独占式获取同步状态,该方法将会调用 tryAcquire 尝试获取同步状态。获取成功则返回,获取失败,线程进入同步队列等待。
void acquireInterruptibly(int arg)响应中断版的 acquire
boolean tryAcquireNanos(int arg,long nanos)超时+响应中断版的 acquire
void acquireShared(int arg)共享式获取同步状态,同一时刻可能会有多个线程获得同步状态。比如读写锁的读锁就是就是调用这个方法获取同步状态的。
void acquireSharedInterruptibly(int arg)响应中断版的 acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos)超时+响应中断版的 acquireShared
boolean release(int arg)独占式释放同步状态
boolean releaseShared(int arg)共享式释放同步状态

上面列举了一堆方法,看似繁杂。但稍微理一下,就会发现上面诸多方法无非就两大类:一类是独占式获取和释放独占状态,另一类是共享式获取和释放同步状态。

源码

¶线程队列节点结构

在并发的情况下,AQS 会将未获取同步状态的线程将会封装成节点,并将其放入同步队列尾部。同步队列中的节点除了要保存线程,还要保存等待状态。不管是独占式还是共享式,在获取状态失败时都会用到节点类。所以这里我们要先看一下节点类的实现,为后面的源码分析进行简单铺垫。源码如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
static final class Node {

/** 共享类型节点,标记节点在共享模式下等待 */
static final Node SHARED = new Node();

/** 独占类型节点,标记节点在独占模式下等待 */
static final Node EXCLUSIVE = null;

/*
* 这里的状态,我的个人理解是,对下一个对象的操作行为
* 如果是CANCELLED 表示:当前线程已经释放锁
* 如果是0(默认状态) 表示:下一个线程正在运行中,不需要唤醒
* 如果是SIANAL、CONDITION、propagate 表示:线程正在等待中,需要前节点释放同步状态后,来唤醒
*/
/** 当前线程取消竞争 */
static final int CANCELLED = 1;

/**
* 后置节点等待被唤醒
*/
static final int SIGNAL = -1;

/** 条件等待。表明节点等待在 Condition 上 */
static final int CONDITION = -2;

/**
* 和共享有关
*/
static final int PROPAGATE = -3;

/**
* 等待状态,取值如下:
* SIGNAL,
* CANCELLED,
* CONDITION,
* PROPAGATE,
* 0
*
* 初始情况下,waitStatus = 0
*/
volatile int waitStatus;

/**
* 前驱节点
*/
volatile Node prev;

/**
* 后继节点
*/
volatile Node next;

/**
* 对应的线程
*/
volatile Thread thread;

/**
* 下一个等待节点,和condition 有关,用在 ConditionObject 中
*/
Node nextWaiter;

/**
* 判断节点是否是共享节点
*/
final boolean isShared() {
return nextWaiter == SHARED;
}

// CLH队列的头结点,其不包含线程信息,head永远为null
private transient volatile Node head;

// CLH队列的尾节点,每次新加一个节点都会尾插到最后
private transient volatile Node tail;

// 当前锁被占据的次数,因为可以被一个线程重复占据,所以其值可以大于0
// 没有线程占据,其值就是0
private volatile int state;

// 当前运行的线程,也是占据锁的线程,注意和CLH中的线程无关
private transient Thread exclusiveOwnerThread;

/**
* 获取前驱节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

Node() { // Used to establish initial head or SHARED marker
}

/** addWaiter 方法会调用该构造方法 */
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}

/** Condition 中会用到此构造方法 */
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

¶独占模式

¶获取同步状态

独占式获取同步状态时通过 acquire 进行的,下面来分析一下该方法的源码。如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/**
* 该方法将会调用子类复写的 tryAcquire 方法获取同步状态,
* - 获取成功:直接返回
* - 获取失败:将线程封装在节点中,并将节点置于同步队列尾部,
* 通过自旋尝试获取同步状态。如果在有限次内仍无法获取同步状态,
* 该线程将会被 LockSupport.park 方法阻塞住,直到被前驱节点唤醒
* arg 是一个扩展字段,或者说是调用发的一个信号量
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

/** 向同步队列尾部添加一个节点 */
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试以快速方式将节点添加到队列尾部
// tail是当前队列的尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 使用cas(乐观锁), 保证队列的更新
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}

// 快速插入节点失败,调用 enq 方法,不停的尝试插入节点
// 初始化 或者 等待cas结果的一致
enq(node);
return node;
}

/**
* 通过 CAS + 自旋的方式插入节点到队尾
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 设置头结点,初始情况下,头结点是一个空节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
/*
* 将节点插入队列尾部。这里是先将新节点的前驱设为尾节点,之后在尝试将新节点设为尾节
* 点,最后再将原尾节点的后继节点指向新的尾节点。除了这种方式,我们还先设置尾节点,
* 之后再设置前驱和后继,即:
*
* if (compareAndSetTail(t, node)) {
* node.prev = t;
* t.next = node;
* }
*
* 但如果是这样做,会导致一个问题,即短时内,队列结构会遭到破坏。考虑这种情况,
* 某个线程在调用 compareAndSetTail(t, node)成功后,该线程被 CPU 切换了。此时
* 设置前驱和后继的代码还没带的及执行,但尾节点指针却设置成功,导致队列结构短时内会
* 出现如下情况:
*
* +------+ prev +-----+ +-----+
* head | | <---- | | | | tail
* | | ----> | | | |
* +------+ next +-----+ +-----+
*
* tail 节点完全脱离了队列,这样导致一些队列遍历代码出错。如果先设置
* 前驱,在设置尾节点。及时线程被切换,队列结构短时可能如下:
*
* +------+ prev +-----+ prev +-----+
* head | | <---- | | <---- | | tail
* | | ----> | | | |
* +------+ next +-----+ +-----+
*
* 这样并不会影响从后向前遍历,不会导致遍历逻辑出错。
*
* 参考:
* https://www.cnblogs.com/micrari/p/6937995.html
*/
// 这个代码细节写的very/very/very good, 很精妙
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

/**
* 同步队列中的线程在此方法中以循环尝试获取同步状态,在有限次的尝试后,
* 若仍未获取锁,线程将会被阻塞,直至被前驱节点的线程唤醒。
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 循环获取同步状态
for (;;) {
final Node p = node.predecessor();
/*
* 前驱节点如果是头结点,表明前驱节点已经获取了同步状态。前驱节点释放同步状态后,
* 在不出异常的情况下, tryAcquire(arg) 应返回 true。此时节点就成功获取了同
* 步状态,并将自己设为头节点,原头节点出队。
*/
if (p == head && tryAcquire(arg)) {
// 成功获取同步状态,设置自己为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}

/*
* 如果获取同步状态失败,则根据条件判断是否应该阻塞自己。
* 如果不阻塞,CPU 就会处于忙等状态,这样会浪费 CPU 资源
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
/*
* 如果在获取同步状态中出现异常,failed = true,cancelAcquire 方法会被执行。
* tryAcquire 需同步组件开发者覆写,难免不了会出现异常。
*/
if (failed)
cancelAcquire(node);
}
}

/** 设置头节点 */
private void setHead(Node node) {
// 仅有一个线程可以成功获取同步状态,所以这里不需要进行同步控制
head = node;
node.thread = null;
node.prev = null;
}

/**
* 该方法主要用途是,当线程在获取同步状态失败时,根据前驱节点的等待状态,决定后续的动作。比如前驱
* 节点等待状态为 SIGNAL,表明当前节点线程应该被阻塞住了。不能老是尝试,避免 CPU 忙等。
* —————————————————————————————————————————————————————————————————
* | 前驱节点等待状态 | 相应动作 |
* —————————————————————————————————————————————————————————————————
* | SIGNAL | 阻塞 |
* | CANCELLED | 向前遍历, 移除前面所有为该状态的节点 |
* | waitStatus < 0 | 将前驱节点状态设为 SIGNAL, 并再次尝试获取同步状态 |
* —————————————————————————————————————————————————————————————————
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
/*
* 前驱节点等待状态为 SIGNAL,表示当前线程应该被阻塞。
* 线程阻塞后,会在前驱节点释放同步状态后被前驱节点线程唤醒
*/
if (ws == Node.SIGNAL)
return true;

/*
* 前驱节点等待状态为 CANCELLED,则以前驱节点为起点向前遍历,
* 移除其他等待状态为 CANCELLED 的节点。
*/
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 等待状态为 0 或 PROPAGATE,设置前驱节点等待状态为 SIGNAL,
* 并再次尝试获取同步状态。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

private final boolean parkAndCheckInterrupt() {
// 调用 LockSupport.park 阻塞自己
LockSupport.park(this);
return Thread.interrupted();
}

/**
* 取消获取同步状态
*/
private void cancelAcquire(Node node) {
if (node == null)
return;

node.thread = null;

// 前驱节点等待状态为 CANCELLED,则向前遍历并移除其他为该状态的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;

// 记录 pred 的后继节点,后面会用到
Node predNext = pred.next;

// 将当前节点等待状态设为 CANCELLED
node.waitStatus = Node.CANCELLED;

/*
* 如果当前节点是尾节点,则通过 CAS 设置前驱节点 prev 为尾节点。设置成功后,再利用 CAS 将
* prev 的 next 引用置空,断开与后继节点的联系,完成清理工作。
*/
if (node == tail && compareAndSetTail(node, pred)) {
/*
* 执行到这里,表明 pred 节点被成功设为了尾节点,这里通过 CAS 将 pred 节点的后继节点
* 设为 null。注意这里的 CAS 即使失败了,也没关系。失败了,表明 pred 的后继节点更新
* 了。pred 此时已经是尾节点了,若后继节点被更新,则是有新节点入队了。这种情况下,CAS
* 会失败,但失败不会影响同步队列的结构。
*/
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 根据条件判断是唤醒后继节点,还是将前驱节点和后继节点连接到一起
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {

Node next = node.next;
if (next != null && next.waitStatus <= 0)
/*
* 这里使用 CAS 设置 pred 的 next,表明多个线程同时在取消,这里存在竞争。
* 不过此处没针对 compareAndSetNext 方法失败后做一些处理,表明即使失败了也
* 没关系。实际上,多个线程同时设置 pred 的 next 引用时,只要有一个能设置成
* 功即可。
*/
compareAndSetNext(pred, predNext, next);
} else {
/*
* 唤醒后继节点对应的线程。这里简单讲一下为什么要唤醒后继线程,考虑下面一种情况:
* head node1 node2 tail
* ws=0 ws=1 ws=-1 ws=0
* +------+ prev +-----+ prev +-----+ prev +-----+
* | | <---- | | <---- | | <---- | |
* | | ----> | | ----> | | ----> | |
* +------+ next +-----+ next +-----+ next +-----+
*
* 头结点初始状态为 0,node1、node2 和 tail 节点依次入队。node1 自旋过程中调用
* tryAcquire 出现异常,进入 cancelAcquire。head 节点此时等待状态仍然是 0,它
* 会认为后继节点还在运行中,所它在释放同步状态后,不会去唤醒后继等待状态为非取消的
* 节点 node2。如果 node1 再不唤醒 node2 的线程,该线程面临无法被唤醒的情况。此
* 时,整个同步队列就回全部阻塞住。
*/
unparkSuccessor(node);
}

node.next = node; // help GC
}
}

private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
/*
* 通过 CAS 将等待状态设为 0,让后继节点线程多一次
* 尝试获取同步状态的机会
*/
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);

Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
/*
* 这里如果 s == null 处理,是不是表明 node 是尾节点?答案是不一定。原因之前在分析
* enq 方法时说过。这里再啰嗦一遍,新节点入队时,队列瞬时结构可能如下:
* node1 node2
* +------+ prev +-----+ prev +-----+
* head | | <---- | | <---- | | tail
* | | ----> | | | |
* +------+ next +-----+ +-----+
*
* node2 节点为新入队节点,此时 tail 已经指向了它,但 node1 后继引用还未设置。
* 这里 node1 就是 node 参数,s = node1.next = null,但此时 node1 并不是尾
* 节点。所以这里不能从前向后遍历同步队列,应该从后向前。
*/
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒 node 的后继节点线程
LockSupport.unpark(s.thread);
}

到这里,独占式获取同步状态的分析就讲完了。如果仅分析获取同步状态的大致流程,那么这个流程并不难。但若深入到细节之中,还是需要思考思考。这里对独占式获取同步状态的大致流程做个总结,如下:

  • 调用 tryAcquire 方法尝试获取同步状态
  • 获取成功,直接返回
  • 获取失败,将线程封装到节点中,并将节点入队
  • 入队节点在 acquireQueued 方法中自旋获取同步状态
  • 若节点的前驱节点是头节点,则再次调用 tryAcquire 尝试获取同步状态
  • 获取成功,当前节点将自己设为头节点并返回
  • 获取失败,可能再次尝试,也可能会被阻塞。这里简单认为会被阻塞。

上面的步骤对应下面的流程图:

¶释放同步状态

相对于获取同步状态,释放同步状态的过程则要简单的多,这里简单罗列一下步骤:

  • 调用 tryRelease(arg) 尝试释放同步状态
  • 根据条件判断是否应该唤醒后继线程

就两个步骤,下面看一下源码分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
/*
* 这里简单列举条件分支的可能性,如下:
* 1. head = null
* head 还未初始化。初始情况下,head = null,当第一个节点入队后,head 会被初始
* 为一个虚拟(dummy)节点。这里,如果还没节点入队就调用 release 释放同步状态,
* 就会出现 h = null 的情况。
*
* 2. head != null && waitStatus = 0
* 表明后继节点对应的线程仍在运行中,不需要唤醒
*
* 3. head != null && waitStatus < 0
* 后继节点对应的线程可能被阻塞了,需要唤醒
*/
if (h != null && h.waitStatus != 0)
// 唤醒后继节点,上面分析过了,这里不再赘述
unparkSuccessor(h);
return true;
}
return false;
}

¶共享模式

与独占模式不同,共享模式下,同一时刻会有多个线程获取共享同步状态。共享模式是实现读写锁中的读锁、CountDownLatch 和 Semaphore 等同步组件的基础,搞懂了,再去理解一些共享同步组件就不难了。

¶获取同步状态

共享类型的节点获取共享同步状态后,如果后继节点也是共享类型节点,当前节点则会唤醒后继节点。这样,多个节点线程即可同时获取共享同步状态。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public final void acquireShared(int arg) {
// 尝试获取共享同步状态,tryAcquireShared 返回的是整型
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 这里和前面一样,也是通过有限次自旋的方式获取同步状态
for (;;) {
final Node p = node.predecessor();
/*
* 前驱是头结点,其类型可能是 EXCLUSIVE,也可能是 SHARED.
* 如果是 EXCLUSIVE,线程无法获取共享同步状态。
* 如果是 SHARED,线程则可获取共享同步状态。
* 能不能获取共享同步状态要看 tryAcquireShared 具体的实现。比如多个线程竞争读写
* 锁的中的读锁时,均能成功获取读锁。但多个线程同时竞争信号量时,可能就会有一部分线
* 程因无法竞争到信号量资源而阻塞。
*/
if (p == head) {
// 尝试获取共享同步状态
// 获取失败:负数
// 成功:0,但是后续节点无法获取同步状态
// 成功:>0 , 同时后续节点可以获取同步状态,但是后续线程必须检查状态的可用性
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置头结点,如果后继节点是共享类型,唤醒后继节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

/**
* 这个方法做了两件事情:
* 1. 设置自身为头结点
* 2. 根据条件判断是否要唤醒后继节点
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
// 设置头结点
setHead(node);

/*
* 这个条件分支由 propagate > 0 和 h.waitStatus < 0 两部分组成。
* h.waitStatus < 0 时,waitStatus = SIGNAL 或 PROPAGATE。这里仅依赖
* 条件 propagate > 0 判断是否唤醒后继节点是不充分的,至于原因请参考下面
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
/*
* 节点 s 如果是共享类型节点,则应该唤醒该节点
* 至于 s == null 的情况前面分析过,这里不在赘述。
*/
if (s == null || s.isShared())
doReleaseShared();
}
}

/**
* 该方法用于在 acquires/releases 存在竞争的情况下,确保唤醒动作向后传播。
*/
private void doReleaseShared() {
/*
* 下面的循环在 head 节点存在后继节点的情况下,做了两件事情:
* 1. 如果 head 节点等待状态为 SIGNAL,则将 head 节点状态设为 0,并唤醒后继节点
* 2. 如果 head 节点等待状态为 0,则将 head 节点状态设为 PROPAGATE,保证唤醒能够正
* 常传播下去。关于 PROPAGATE 状态的细节分析,后面会讲到。
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
/*
* ws = 0 的情况下,这里要尝试将状态从 0 设为 PROPAGATE,保证唤醒向后
* 传播。setHeadAndPropagate 在读到 h.waitStatus < 0 时,可以继续唤醒
* 后面的节点。
*/
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

到这里,共享模式下获取同步状态的逻辑就分析完了,不过我这里只做了简单分析。相对于独占式获取同步状态,共享式的情况更为复杂。独占模式下,只有一个节点线程可以成功获取同步状态,也只有获取已同步状态节点线程才可以释放同步状态。但在共享模式下,多个共享节点线程可以同时获得同步状态,在一些线程获取同步状态的同时,可能还会有另外一些线程正在释放同步状态。所以,共享模式更为复杂。这里我的脑力跟不上了,没法面面俱到的分析。
最后说一下共享模式下获取同步状态的大致流程,如下:

  • 获取共享同步状态
  • 若获取失败,则生成节点,并入队
  • 如果前驱为头结点,再次尝试获取共享同步状态
  • 获取成功则将自己设为头结点,如果后继节点是共享类型的,则唤醒
  • 若失败,将节点状态设为 SIGNAL,再次尝试。若再次失败,线程进入等待状态

¶释放同步状态

释放共享状态主要逻辑在 doReleaseShared 中,doReleaseShared 上节已经分析过,这里就不赘述了。共享节点线程在获取同步状态和释放同步状态时都会调用 doReleaseShared,所以 doReleaseShared 是多线程竞争集中的地方。

1
2
3
4
5
6
7
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

PROPAGATE 状态存在的意义

AQS 的节点有几种不同的状态,PROPAGATE 字面意义,即向后传播唤醒动作。
那么就有两个问题:

  • PROPAGATE 状态用在哪里,以及怎样向后传播唤醒动作的?
  • 引入 PROPAGATE 状态是为了解决什么问题?

¶利用 PROPAGATE 传播唤醒动作

PROPAGATE 状态是用来传播唤醒动作的,那么它是在哪里进行传播的呢?答案是在setHeadAndPropagate方法中,这里再来看看 setHeadAndPropagate 方法的实现:

1
2
3
4
5
6
7
8
9
10
11
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);

if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}

注意看 setHeadAndPropagate 方法中那个长长的判断语句,其中有一个条件是h.waitStatus < 0,当 h.waitStatus = SIGNAL(-1) 或 PROPAGATE(-3) 是,这个条件就会成立。那么 PROPAGATE 状态是在何时被设置的呢?答案是在doReleaseShared方法中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {...}

// 如果 ws = 0,则将 h 状态设为 PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
...
}
}

再回到 setHeadAndPropagate 的实现,该方法既然引入了h.waitStatus < 0这个条件,就意味着仅靠条件propagate > 0判断是否唤醒后继节点线程的机制是不充分的。为啥?

¶引入 PROPAGATE 所解决的问题

PROPAGATE 的引入是为了解决一个 BUG – JDK-6801020,复现这个 BUG 的代码如下:

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
import java.util.concurrent.Semaphore;

public class TestSemaphore {

private static Semaphore sem = new Semaphore(0);

private static class Thread1 extends Thread {
@Override
public void run() {
sem.acquireUninterruptibly();
}
}

private static class Thread2 extends Thread {
@Override
public void run() {
sem.release();
}
}

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000000; i++) {
Thread t1 = new Thread1();
Thread t2 = new Thread1();
Thread t3 = new Thread2();
Thread t4 = new Thread2();
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
System.out.println(i);
}
}
}

根据 BUG 的描述消息可知 JDK 6u11,6u17 两个版本受到影响。那么,接下来再来看看引起这个 BUG 的代码 – JDK 6u17 中 setHeadAndPropagate 和 releaseShared 两个方法源码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void setHeadAndPropagate(Node node, int propagate) {
setHead(node);
if (propagate > 0 && node.waitStatus != 0) {
/*
* Don't bother fully figuring out successor. If it
* looks null, call unparkSuccessor anyway to be safe.
*/
Node s = node.next;
if (s == null || s.isShared())
unparkSuccessor(node);
}
}

// 和 release 方法的源码基本一样
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

下面来简单说明 TestSemaphore 这个类的逻辑。这个类持有一个数值为 0 的信号量对象,并创建了4个线程,线程 t1 和 t2 用于获取信号量,t3 和 t4 则是调用 release() 方法释放信号量。在一般情况下,TestSemaphore 这个类的代码都可以正常执行。但当有极端情况出现时,可能会导致同步队列挂掉。这里演绎一下这个极端情况,考虑某次循环时,队列结构如下:

  • 时刻1:线程 t3 调用 unparkSuccessor 方法,head 节点状态由 SIGNAL(-1) 变为0,并唤醒线程 t1。此时信号量数值为1。
  • 时刻2:线程 t1 恢复运行,t1 调用 Semaphore.NonfairSync 的 tryAcquireShared,返回0。然后线程 t1 被切换,暂停运行。
  • 时刻3:线程 t4 调用 releaseShared 方法,因 head 的状态为0,所以 t4 不会调用 unparkSuccessor 方法。
  • 时刻4:线程 t1 恢复运行,t1 成功获取信号量,调用 setHeadAndPropagate。但因为 propagate = 0,线程 t1 无法调用 unparkSuccessor 唤醒线程 t2,t2 面临无线程唤醒的情况。因为 t2 无法退出等待状态,所以 t2.join 会阻塞主线程,导致程序挂住。

下面再来看一下修复 BUG 后的代码,根据 BUG 详情页显示,该 BUG 在 JDK 1.7 中被修复。这里找一个 JDK 7 较早版本(JDK 7u10)的代码看一下,如下:

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
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);

if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

在按照上面的代码演绎一下逻辑,如下:

  • 时刻1:线程 t3 调用 unparkSuccessor 方法,head 节点状态由 SIGNAL(-1) 变为0,并唤醒线程t1。此时信号量数值为1。
  • 时刻2:线程 t1 恢复运行,t1 调用 Semaphore.NonfairSync 的 tryAcquireShared,返回0。然后线程 t1 被切换,暂停运行。
  • 时刻3:线程 t4 调用 releaseShared 方法,检测到h.waitStatus = 0,t4 将头节点等待状态由0设为PROPAGATE(-3)。
  • 时刻4:线程 t1 恢复运行,t1 成功获取信号量,调用 setHeadAndPropagate。因 propagate = 0,propagate > 0 条件不满足。而 h.waitStatus = PROPAGATE(-3),所以条件h.waitStatus < 0成立。进而,线程 t1 可以唤醒线程 t2,完成唤醒动作的传播。

¶PROPAGATE 状态用在哪里,以及怎样向后传播唤醒动作的?

PROPAGATE 状态用在 setHeadAndPropagate。当头节点状态被设为 PROPAGATE 后,后继节点成为新的头结点后。若 propagate > 0 条件不成立,则根据条件h.waitStatus < 0成立与否,来决定是否唤醒后继节点,即向后传播唤醒动作。

¶引入 PROPAGATE 状态是为了解决什么问题?

引入 PROPAGATE 状态是为了解决并发释放信号量所导致部分请求信号量的线程无法被唤醒的问题。

AbstractQueuedSynchronizer 原理分析 - 独占模式

发表于 2019-10-24 | 更新于 2019-10-25 | 分类于 Java | 评论数: | 阅读次数:

AQS 结构

先来看看 AQS 有哪些属性,搞清楚这些基本就知道 AQS 是什么套路了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;

// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
private transient volatile Node tail;

// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
private volatile int state;

// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer

AbstractQueuedSynchronizer 的等待队列示意如下所示,注意了,之后分析过程中所说的 queue,也就是阻塞队列
不包含 head !!!
不包含 head !!!
不包含 head !!!

等待队列中每个线程被包装成一个 Node 实例,数据结构是链表

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
static final class Node {
// 标识节点当前在共享模式下
static final Node SHARED = new Node();
// 标识节点当前在独占模式下
static final Node EXCLUSIVE = null;

// ======== 下面的几个int常量是给waitStatus用的 ===========
/** waitStatus value to indicate thread has cancelled */
// 代码此线程取消了争抢这个锁
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 本文不分析condition,所以略过吧,下一篇文章会介绍这个
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
// 同样的不分析,略过吧
static final int PROPAGATE = -3;
// =====================================================


// 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
// 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
// ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
volatile int waitStatus;
// 前驱节点的引用
volatile Node prev;
// 后继节点的引用
volatile Node next;
// 这个就是线程本尊
volatile Thread thread;

}

Node 的数据结构其实也挺简单的,就是 thread + waitStatus + pre + next 四个属性而已,大家先要有这个概念在心里。
上面的是基础知识,后面会多次用到,心里要时刻记着它们,心里想着这个结构图就可以了。下面,我们开始说 ReentrantLock 的公平锁。再次强调,我说的阻塞队列不包含 head 节点。

首先,我们先看下 ReentrantLock 的使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 我用个web开发中的service概念吧
public class OrderService {
// 使用static,这样每个线程拿到的是同一把锁,当然,spring mvc中service默认就是单例,别纠结这个
private static ReentrantLock reentrantLock = new ReentrantLock(true);

public void createOrder() {
// 比如我们同一时间,只允许一个线程创建订单
reentrantLock.lock();
// 通常,lock 之后紧跟着 try 语句
try {
// 这块代码同一时间只能有一个线程进来(获取到锁的线程),
// 其他的线程在lock()方法上阻塞,等待获取到锁,再进来
// 执行代码...
// 执行代码...
// 执行代码...
} finally {
// 释放锁
reentrantLock.unlock();
}
}
}

ReentrantLock 在内部用了内部类 Sync 来管理锁,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。

1
2
abstract static class Sync extends AbstractQueuedSynchronizer {
}

Sync 有两个实现,分别为 NonfairSync(非公平锁)和 FairSync(公平锁),我们看 FairSync 部分。

1
2
3
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

线程强锁

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 争锁
final void lock() {
acquire(1);
}
// 来自父类AQS,我直接贴过来这边,下面分析的时候同样会这样做,不会给读者带来阅读压力
// 我们看到,这个方法,如果tryAcquire(arg) 返回true, 也就结束了。
// 否则,acquireQueued方法会将线程压到队列中
public final void acquire(int arg) { // 此时 arg == 1
// 首先调用tryAcquire(1)一下,名字上就知道,这个只是试一试
// 因为有可能直接就成功了呢,也就不需要进队列排队了,
// 对于公平锁的语义就是:本来就没人持有锁,根本没必要进队列等待(又是挂起,又是等待被唤醒的)
if (!tryAcquire(arg) &&
// tryAcquire(arg)没有成功,这个时候需要把当前线程挂起,放到阻塞队列中。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 尝试直接获取锁,返回值是boolean,代表是否获取到锁
// 返回true:1.没有线程在等待锁;2.重入锁,线程本来就持有锁,也就可以理所当然可以直接获取
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state == 0 此时此刻没有线程持有锁
if (c == 0) {
// 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 看看有没有别人在队列中等了半天了
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
// 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_=
// 因为刚刚还没人的,我判断过了
compareAndSetState(0, acquires)) {

// 到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁
setExclusiveOwnerThread(current);
return true;
}
}
// 会进入这个else if分支,说明是重入了,需要操作:state=state+1
// 这里不存在并发问题
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁
// 回到上面一个外层调用方法继续看:
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
return false;
}

// 假设tryAcquire(arg) 返回false,那么代码将执行:
// acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
// 这个方法,首先需要执行:addWaiter(Node.EXCLUSIVE)

/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
// 此方法的作用是把线程包装成node,同时进入到队列中
// 参数mode此时是Node.EXCLUSIVE,代表独占模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 以下几行代码想把当前node加到链表的最后面去,也就是进到阻塞队列的最后
Node pred = tail;

// tail!=null => 队列不为空(tail==head的时候,其实队列是空的,不过不管这个吧)
if (pred != null) {
// 将当前的队尾节点,设置为自己的前驱
node.prev = pred;
// 用CAS把自己设置为队尾, 如果成功后,tail == node 了,这个节点成为阻塞队列新的尾巴
if (compareAndSetTail(pred, node)) {
// 进到这里说明设置成功,当前node==tail, 将自己与之前的队尾相连,
// 上面已经有 node.prev = pred,加上下面这句,也就实现了和之前的尾节点双向连接了
pred.next = node;
// 线程入队了,可以返回了
return node;
}
}
// 仔细看看上面的代码,如果会到这里,
// 说明 pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队)
// 读者一定要跟上思路,如果没有跟上,建议先不要往下读了,往回仔细看,否则会浪费时间的
enq(node);
return node;
}

/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
// 采用自旋的方式入队
// 之前说过,到这个方法只有两种可能:等待队列为空,或者有线程竞争入队,
// 自旋在这边的语义是:CAS设置tail过程中,竞争一次竞争不到,我就多次竞争,总会排到的
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 之前说过,队列为空也会进来这里
if (t == null) { // Must initialize
// 初始化head节点
// 细心的读者会知道原来 head 和 tail 初始化的时候都是 null 的
// 还是一步CAS,你懂的,现在可能是很多线程同时进来呢
if (compareAndSetHead(new Node()))
// 给后面用:这个时候head节点的waitStatus==0, 看new Node()构造方法就知道了

// 这个时候有了head,但是tail还是null,设置一下,
// 把tail指向head,放心,马上就有线程要来了,到时候tail就要被抢了
// 注意:这里只是设置了tail=head,这里可没return哦,没有return,没有return
// 所以,设置完了以后,继续for循环,下次就到下面的else分支了
tail = head;
} else {
// 下面几行,和上一个方法 addWaiter 是一样的,
// 只是这个套在无限循环里,反正就是将当前线程排到队尾,有线程竞争的话排不上重复排
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}


// 现在,又回到这段代码了
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();

// 下面这个方法,参数node,经过addWaiter(Node.EXCLUSIVE),此时已经进入阻塞队列
// 注意一下:如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的话,
// 意味着上面这段代码将进入selfInterrupt(),所以正常情况下,下面应该返回false
// 这个方法非常重要,应该说真正的线程挂起,然后被唤醒后去获取锁,都在这个方法里了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// p == head 说明当前节点虽然进到了阻塞队列,但是是阻塞队列的第一个,因为它的前驱是head
// 注意,阻塞队列不包含head节点,head一般指的是占有锁的线程,head后面的才称为阻塞队列
// 所以当前节点可以去试抢一下锁
// 这里我们说一下,为什么可以去试试:
// 首先,它是队头,这个是第一个条件,其次,当前的head有可能是刚刚初始化的node,
// enq(node) 方法里面有提到,head是延时初始化的,而且new Node()的时候没有设置任何线程
// 也就是说,当前的head不属于任何一个线程,所以作为队头,可以去试一试,
// tryAcquire已经分析过了, 忘记了请往前看一下,就是简单用CAS试操作一下state
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这里,说明上面的if分支没有成功,要么当前node本来就不是队头,
// 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 什么时候 failed 会为 true???
// tryAcquire() 方法抛异常的情况
if (failed)
cancelAcquire(node);
}
}

/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
// 刚刚说过,会到这里就是没有抢到锁呗,这个方法说的是:"当前线程没有抢到锁,是否需要挂起当前线程?"
// 第一个参数是前驱节点,第二个参数才是代表当前线程的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,当前线程需要挂起,直接可以返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;

// 前驱节点 waitStatus大于0 ,之前说过,大于0 说明前驱节点取消了排队。
// 这里需要知道这点:进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。
// 所以下面这块代码说的是将当前节点的prev指向waitStatus<=0的节点,
// 简单说,就是为了找个好爹,因为你还得依赖它来唤醒呢,如果前驱节点取消了排队,
// 找前驱节点的前驱节点做爹,往前遍历总能找到一个好爹的
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 仔细想想,如果进入到这个分支意味着什么
// 前驱节点的waitStatus不等于-1和1,那也就是只可能是0,-2,-3
// 在我们前面的源码中,都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0
// 正常情况下,前驱节点是之前的 tail,那么它的 waitStatus 应该是 0
// 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 这个方法返回 false,那么会再走一次 for 循序,
// 然后再次进来此方法,此时会从第一个分支返回 true
return false;
}

// private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
// 这个方法结束根据返回值我们简单分析下:
// 如果返回true, 说明前驱节点的waitStatus==-1,是正常情况,那么当前线程需要被挂起,等待以后被唤醒
// 我们也说过,以后是被前驱节点唤醒,就等着前驱节点拿到锁,然后释放锁的时候叫你好了
// 如果返回false, 说明当前不需要被挂起,为什么呢?往后看

// 跳回到前面是这个方法
// if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt())
// interrupted = true;

// 1. 如果shouldParkAfterFailedAcquire(p, node)返回true,
// 那么需要执行parkAndCheckInterrupt():

// 这个方法很简单,因为前面返回true,所以需要挂起线程,这个方法就是负责挂起线程的
// 这里用了LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒=======
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

// 2. 接下来说说如果shouldParkAfterFailedAcquire(p, node)返回false的情况

// 仔细看shouldParkAfterFailedAcquire(p, node),我们可以发现,其实第一次进来的时候,一般都不会返回true的,原因很简单,前驱节点的waitStatus=-1是依赖于后继节点设置的。也就是说,我都还没给前驱设置-1呢,怎么可能是true呢,但是要看到,这个方法是套在循环里的,所以第二次进来的时候状态就是-1了。

// 解释下为什么shouldParkAfterFailedAcquire(p, node)返回false的时候不直接挂起线程:
// => 是为了应对在经过这个方法后,node已经是head的直接后继节点了。剩下的读者自己想想吧。
}

说到这里,也就明白了,多看几遍 final boolean acquireQueued(final Node node, int arg) 这个方法吧。自己推演下各个分支怎么走,哪种情况下会发生什么,走到哪里。

解锁操作

最后,就是还需要介绍下唤醒的动作了。我们知道,正常情况下,如果线程没获取到锁,线程会被 LockSupport.park(this); 挂起停止,等待被唤醒。

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
// 唤醒的代码还是比较简单的,你如果上面加锁的都看懂了,下面都不需要看就知道怎么回事了
public void unlock() {
sync.release(1);
}

public final boolean release(int arg) {
// 往后看吧
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

// 回到ReentrantLock看tryRelease方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否完全释放锁
boolean free = false;
// 其实就是重入的问题,如果c==0,也就是说没有嵌套锁了,可以释放了,否则还不能释放掉
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
// 唤醒后继节点
// 从上面调用处知道,参数node是head头结点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
// 如果head节点当前waitStatus<0, 将其修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
// 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从后往前找,仔细看代码,不必担心中间有节点取消(waitStatus==1)的情况
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}

唤醒线程以后,被唤醒的线程将从以下代码中继续往前走:

1
2
3
4
5
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 刚刚线程被挂起在这里了
return Thread.interrupted();
}
// 又回到这个方法了:acquireQueued(final Node node, int arg),这个时候,node的前驱是head了

总结

总结一下吧。

1
2
3
4
5
在并发环境下,加锁和解锁需要以下三个部件的协调:

锁状态。我们要知道锁是不是被别的线程占有了,这个就是 state 的作用,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
阻塞队列。因为争抢锁的线程可能很多,但是只能有一个线程拿到锁,其他的线程都必须等待,这个时候就需要一个 queue 来管理这些线程,AQS 用的是一个 FIFO 的队列,就是一个链表,每个 node 都持有后继节点的引用。AQS 采用了 CLH 锁的变体来实现,感兴趣的读者可以参考这篇文章关于CLH的介绍,写得简单明了。

示例图解析

下面属于回顾环节,用简单的示例来说一遍,如果上面的有些东西没看懂,这里还有一次帮助你理解的机会。

首先,第一个线程调用 reentrantLock.lock(),翻到最前面可以发现,tryAcquire(1) 直接就返回 true 了,结束。只是设置了 state=1,连 head 都没有初始化,更谈不上什么阻塞队列了。要是线程 1 调用 unlock() 了,才有线程 2 来,那世界就太太太平了,完全没有交集嘛,那我还要 AQS 干嘛。

如果线程 1 没有调用 unlock() 之前,线程 2 调用了 lock(), 想想会发生什么?

线程 2 会初始化 head【new Node()】,同时线程 2 也会插入到阻塞队列并挂起 (注意看这里是一个 for 循环,而且设置 head 和 tail 的部分是不 return 的,只有入队成功才会跳出循环)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

首先,是线程 2 初始化 head 节点,此时 head==tail, waitStatus==0

然后线程 2 入队:

同时我们也要看此时节点的 waitStatus,我们知道 head 节点是线程 2 初始化的,此时的 waitStatus 没有设置, java 默认会设置为 0,但是到 shouldParkAfterFailedAcquire 这个方法的时候,线程 2 会把前驱节点,也就是 head 的waitStatus设置为 -1。

那线程 2 节点此时的 waitStatus 是多少呢,由于没有设置,所以是 0;

如果线程 3 此时再进来,直接插到线程 2 的后面就可以了,此时线程 3 的 waitStatus 是 0,到 shouldParkAfterFailedAcquire 方法的时候把前驱节点线程 2 的 waitStatus 设置为 -1。

这里可以简单说下 waitStatus 中 SIGNAL(-1) 状态的意思,Doug Lea 注释的是:代表后继节点需要被唤醒。也就是说这个 waitStatus 其实代表的不是自己的状态,而是后继节点的状态,我们知道,每个 node 在入队的时候,都会把前驱节点的状态改为 SIGNAL,然后阻塞,等待被前驱唤醒。这里涉及的是两个问题:有线程取消了排队、唤醒操作。其实本质是一样的,读者也可以顺着 “waitStatus代表后继节点的状态” 这种思路去看一遍源码。

疑问

¶保留head为刚开始的new Node()不好吗?为什么要重新设置一下head呢?

1
2
3
4
5
6
7
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}

假设不调用 setHead(node),我们假设此时 A 持有这个锁,head 是 new Node() 那个空节点。

A 持有的锁用完了,释放锁,唤醒后继节点 B。后继节点 B 从 parkAndCheckInterrupt() 这个方法返回,注意这里的 for 循环。然后调用 final Node p = node.predecessor(); 这个方法,那么这个时候,p == head 就不成立了,也就进不到 tryAcquire(arg) 这里去获取锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 进到这里,说明获取到锁了
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}

另一处。既然当前线程获取到了锁,它就不应该再是阻塞队列的一员。如果没有 setHead 这一步,下面这个方法就不准确了:

1
2
3
4
5
6
7
8
9
public final Collection<Thread> getQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null) // 注意这里
list.add(t);
}
return list;
}

¶这个finally里的cancelAcquire 似乎永远都不会被执行吧?为什么都是要从tail往前找第一个状态是非CANCEL的节点呢,如果从head往后找第一个状态是非CANCEL的话效率会不会高一点?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}

第一个问题:这里的 failed 确实永远都不会为 true,cancelAcquire() 永远不会得到执行。那为什么要这么写呢,如果你看了后面的两篇,可能会有些体会,这部分其实是用于响应中断或超时的,你可以参考 doAcquireNanos(int arg, long nanosTimeout) 或 doAcquireInterruptibly(int arg)。在这个方法中确实是没用的,这更像是模板代码吧。
这段代码的异常可能发生在 tryAcquire(arg) 这里,因为这是依赖于子类来实现的。

第二个问题:应该说的是 unparkSuccessor(Node node) 这个方法。

1
2
3
4
5
6
7
8
9
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);

首先,第一行代码先检测 head 的后继节点,只有当此时的后继节点不存在或者这个后继节点取消了才开始从后往前找,所以大部分情况下,其实不会发生从后往前遍历整个队列的情况。(后继节点取消很正常,但是某节点在入队的时候,如果发现前驱是取消状态,前驱节点是会被请出队列的)

这个问题的答案在 addWaiter(Node mode)方法中,看下面的代码:

1
2
3
4
5
6
7
8
9
10
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 1. 先设置的 tail
if (compareAndSetTail(pred, node)) {
// 2. 设置前驱节点的后继节点为当前节点
pred.next = node;
return node;
}
}

所以,这里存在并发问题:从前往后寻找不一定能找到刚刚加入队列的后继节点。

¶感觉Java的monitor和AQS的目的和实现思路很相似,为什么还要再实现一遍AQS呢?

monitor 功能太单一了,就是获取独占锁,

AQS 相比 monitor,功能要丰富很多,比如我们可以设置超时时间,可以用线程中断进行退出,可以选择公平/非公平模式等, 采用 AQS 可以实现很多灵活的场景

¶acquireQueued方法里面,第一次调用shouldParkAfterFailedAcquire(p, node)的时候,把前驱节点waitStatus从0改为-1,然后返回false,回到acquireQueued方法,再尝试拿一次锁,然后第二次调用shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt()挂起线程。那么,如果在某线程B还没有挂起之前,前驱节点的线程A发现自己waitStatus为-1直接unpark,然后刚刚的线程B才挂起。那不就没人能唤醒它了吗?它是怎么保证被唤醒的?

你应该已经把流程摸清楚了,我就说一点就可以了,你的疑问其实在 unpark() 方法上。
1、如果一个线程 park 了,那么调用 unpark(thread) 这个线程会被唤醒;
2、如果一个线程先被调用了 unpark,那么下一个 park(thread) 操作不会挂起线程。

¶为什么要先读tail,再读head,我猜是为了增加tail为null的可能性,可是增加这种可能性的目的呢?

1
2
3
4
5
6
7
8
9
10
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

看下面这段代码,如果是第一个 node 进队列的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 1. 设置 head
if (compareAndSetHead(new Node()))
// 2. 设置 tail
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

也就是说,先有 head 然后才有 tail。

回到 hasQueuedPredecessors:

1
2
3
4
5
6
7
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

有可能的情况就是 t 为 null,h 不为 null 对吧,这个时候返回值取决于 h.next。
如果调换一下 Node t = tail; 和 Node h = head; 那么可能出现 h 为 null,t 不为 null,这个方法会返回 false。
但是其实不对的,很可能这个间隙是有节点 enq 成功的。

¶为什么共享锁的node节点是new了一个节点,独占是null呢?

1
2
其实我也不知道是为什么???
大神 Doug Lea 的想法应该是(我怎么知道他到底怎么想的):nextWaiter 这个属性在这个时候是没用的,因为它用来实现 Condition,那么不用白不用,虽然不好理解,可是充分利用资源呀,不然还得自己额外定义一个用来标识模式的属性。

¶请问这个里如果同步状态tryAcquire(arg)获取失败,就构造一个同步节点通过addWaiter(Node.EXCLUSIVE)将节点添加到尾部,如果条件成立执行 selfInterrupt()会中断当前线程吗 selfInterrupt()的用途不是太明白,有的书籍上说acquire(int arg)方法对中断不敏感,不会将获取同步状态失败的线程从同步队列中移除

1
2
3
4
5
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

acquireQueued 返回值代表的是:是否被中断过。但是,不管是否被中断过,acquireQueued 出来以后,线程的中断状态一定是 false,所以如果发生过中断,要重新设置中断状态。
虽然 acquire(int arg) 确实是不关心中断的,但是它会保持这个状态,如果客户端想要知道是否发生过中断的话,还是可以知道的。因为中断情况下,中断状态虽然中间丢过,但是 selfInterrupt() 会设置回去。
会实际受到中断影响的是另一个方法 acquireInterruptibly(int arg),这个方法会通过抛出异常的方式告诉客户端。

acquireQueued 返回值代表的是:是否被中断过。但是,不管是否被中断过,acquireQueued 出来以后,线程的中断状态一定是 false。
请问为什么说:acquireQueued 出来以后,线程的中断状态一定是 false ?

看一下 Thread.interrupted() 这个方法,这个方法用于检测中断状态,同时会清除中断状态。

¶不是很明白head 节点是当前独占锁的持有者的意思(注释也说到head一般指的是占有锁的线程),请问从何作出这个判断? 看代码感觉整个阻塞队列(包括head节点)都没有当前占有锁线程的信息。

这是隐含的信息,ReentrantLock 具有排他性,lock() 方法要么阻塞,要么顺利拿到锁返回。
当 lock() 返回的时候,我们说当前线程持有了独占锁,而此时的 head 就是当前线程。
(这里说的情况不考虑连 head 都没有初始化的场景)

这样的说法很容易让人混淆,应该是得分两种情况考虑:
1、当前已有别的线程持有锁的时候,head是指向(head.next)下次解锁时即将能持有锁的线程。
2、当持有锁的线程unlock时, head 指向的就是当前持有锁的线程 ,但这个时间非常短,因为head马上又会指向一下个即将能持有锁的线程。

AcquireQueue()里,如果tryacquire()成功,会用setHead(node)将当前获得锁的Node设为Head。

¶为什么如果不满足条件就唤醒下一位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void cancelAcquire(Node node) {
...
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
/* 这里为什么如果不满足条件就唤醒下一位
* 第一个条件我是理解的 如果pred是head 那么需要唤醒下一位
* 因为如果前面是pred ==head 那么这个cancel的线程定时醒来后
* 如果还没有执行赋值,那么此时持有锁的线程正好开始释放锁,
* 那么会唤醒第一个阻塞的线程,假设这个线程正好是上面的线程
* 那么如果此时这个cancel的线程不传递这个唤醒,就会造成
* 其他线程醒不来, 但是为什么下面两个条件的失败也会唤醒后面的线程?
*/
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
...
}

转化一下,如果要进入到 else,需要满足:

1
2
3
(ws = pred.waitStatus) != Node.SIGNAL
&&
ws > 0 || (ws <=0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))

HongJie 2018-04-24 08:00:26
转化一下,如果要进入到 else,需要满足:

(ws = pred.waitStatus) != Node.SIGNAL
&&
ws > 0 || (ws <=0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
再将其转化为下面两种情况:

(ws = pred.waitStatus != Node.SIGNAL) && ws > 0
前驱节点处于 CANCELLED 状态,显然是需要唤醒后继节点的,这条很简单

(ws = pred.waitStatus != Node.SIGNAL) && (ws <= 0) && (!compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
这种情况下,在将前驱设置为 SIGNAL 的时候失败了,我想到的一种情况是,在 CAS 之前前驱设置为了 CANCELLED

结合 acquireQueued 方法来看,假设里面的 tryAcquire 抛出异常的场景。

¶pred.thread == null

head 是 new Node() 的“空节点”,要是不做唤醒后继节点的话,那。。。你懂的

¶VM的monitor最终是使用了(如果升级为重量锁)mutex,操作系统级的支持;能否这样理解,基于AQS的ReentrantLock不需要操作系统的锁支持了,所以比较轻?而且也不会升级为重量级锁,本身只是个等待队列而已。

我里理解的ReetrantLock 公平锁 和 synchronized 的原理差不多,也有重量级锁,也有升级的步骤。
ReetrantLock 首先会采用自旋获取锁(偏向锁),之后的线程进来发现获取不到锁,就加入队列中,如果前驱节点是 head,就会尝试获取一次错,如果没有获取成功,就线程挂起(锁膨胀)。涉及到上下文切换,系统调用。

¶1队列非空,2队列第一个节点(非head)非当前线程 满足两个条件返回true, 那么h != t && (s = h.next) == null 这样一种场景怎么理解

1
2
3
4
5
6
7
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
( (s = h.next) == null || s.thread != Thread.currentThread() );
}

你的问题需要到 enq 找答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// 看这里
t.next = node;
return t;
}
}
}
}

节点入队时,先成为 tail 然后才设置前驱的 next 属性。
那么对应于你的问题,h != t && h.next == null 对应的就是某个节点现在已经是 tail 了,但是 head.next 还是 null。

虽然文中没有分析到这个分支,不过还是有些细心的读者对这个很感兴趣的,你也可以往前看下另一个读者的问题,也是针对这个方法的。
这里肯定是不会为 null 的,你可以把你认为它可能会为 null 的分析过程描述得详细一些,这样我们比较容易在一个频道上。

¶这个不是个死循环嘛?这么循环怎么能成立的呀?麻烦大神给解释一下

1
do { node.prev = pred = pred.prev;  } while (pred.waitStatus > 0);<br>pred = pred.prev

这里没有死循环呀,一直在沿着队列往前走,找到一个 waitStatus<=0 的节点

¶阻塞队列的头结点是什么时候初始化的呢?

  1. 当前队列为空
  2. 2.当有线程阻塞的时候

¶为什么不在锁的构造器里就先建一个阻塞队列的头结点呢?

如果没有线程竞争锁的话就是浪费一个节点的空间,Doug Lea大师的注释如下。可见大师的代码一点一滴都体现水平。

  • CLH queues need a dummy header node to get started. But
  • we don’t create them on construction, because it would be wasted
  • effort if there is never contention. Instead, the node
  • is constructed and head and tail pointers are set upon first
  • contention. ``` 问:阻塞队列的头结点什么时候会被GC呢? 答:当队列里第一个Node节点得到锁后,该节点会被设置成新的头结点。那么原来“老”的头结点由于没有任何引用指向它,就会被GC回收。

¶队列中的线程节点node被唤醒后,node直接变为head,指向原head的指针pred没有被置null,原head节点没法回收吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final boolean acquireQueued(final Node node, int arg) {
...
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 拿到锁进来这里
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
...
}

在 setHead(node) 方法里面:

1
2
3
4
5
6
private void setHead(Node node) {
head = node;
node.thread = null;
// 这个
node.prev = null;
}

¶head节点的属性是不是state,这个state表示当前锁已经被占用几次了。阻塞队列里的node的属性是waitStatus,代表着阻塞队列里节点的状态。这是两个枚举值完全不相关的属性吗?

head节点和阻塞队列其它的node都是waitStatus,state是AQS的属性,不相关的

¶刚初始化 fifo 的时候,h=new Node(); t = null; 这样子 h != t && (s = h.next) == null 不是为 true 吗, h.next == null; 因为是刚初始化的节点,这样子不是没有 Predecessors 嘛。那这个明明没有Predecessors 却返回 true ;不是错了吗 是我理解错误吗?

只要 head!=tail 就说明有新的节点进来到队列的尾部了,如果 h.next == null, 说明正在初始化节点中,如果不是初始化中的话,只要 Head 的下一个节点不是 刚进来的 thread 的 的Node,如果是的话就说明没有正在等待的节点,
对了先补充下这个方法的意思,查询是否有任何线程等待获取比当前线程更长的时间。
true如果有排队线前面的当前线程,并 false如果当前线程在队列或队列的头部是空的

¶对于 cancelAcquire() 的 unparkSuccessor(node); 会不会唤醒两次呢。 如果 pred == head;然后又刚好 pred 线程也要出同步块了 也调用了 unparkSuccessor(node); 那这个时候 node.next 是不是 会两次 unpark() 呢,因为 unpark 会抵消 park() ,所以 在第一次 unpark 将线程唤醒了,第二次 unpark 将许可证置为可用的.那么下次 condition 的 waiter 会不会 被抵消掉呢?

你的问题挺有趣的,我没有仔细去推演每一步,不过我觉得其实这也不是什么大问题。仔细想想,即使真的是两次 unpark 了(假设真的如此),大不了就是后面会出现一次 park 直接返回。
对于 park 方法,我们本来就是要假设它有可能会无故返回的,被中断或者系统的假唤醒,所以这些代码往往都在循环体中。

¶获取锁、释放锁,阻塞队列的Node数量不会减少吗?好像没看到在哪里减少阻塞队列里面Node的数量?

因为我们通常并不关心阻塞队列中到底有多少 Node
获取到锁的节点会变成head,在那里会把原来的head移出队列
在acquireQueued里面获取到同步状态,下面两条语句让Head出列。
setHead(node);
p.next = null;

¶在acquireQueued里会做自旋操作,如果前边节点是head就尝试获取锁,如果前面节点不是head,做后面的挂起或者不挂起. 这样的话,只有一个节点(就是head后面的那个节点)才会尝试获取锁,这样何来竞争锁的说法呢?

1
因为非公平锁的实现里面,每个新来的线程,是不管队列是否为空,都会先去做一次抢锁的。

¶在unparkSuccessor()方法中,如果s == null || s.waitStatus > 0,会从阻塞队列的尾部开始一直向前找,找到最前面一个waitStatus == -1的节点,将其唤醒。我的问题是,唤醒之后,该线程从acquireQueued(Node node, int arg)方法的for循环中醒过来,继续循环,只有当自己的前驱节点为head节点即node.predecessor() == head时,才会去tryAcquire()抢锁,可是在从后往前找的过程中,并没有看到把当前找寻的目标节点和head节点链接上的步骤啊,这样不就不能判断node.predecessor() == head为true吗?也就不能抢锁了…

问题在 acquireQueued 这个方法中。
我不清楚你具体的疑惑在哪里,但是你可以看看嵌套在死循环中的 shouldParkAfterFailedAcquire。
我觉得 AQS 里面有个比较重要的点是,它总是先保证节点能安全进入到队列中,至于你说的这种边界问题,都在“被动”发现 waitStatus 不对的时候去“纠正”它。

spring - IOC

发表于 2019-10-22 | 更新于 2019-11-14 | 分类于 Java | 评论数: | 阅读次数:

IOC体系结构

¶BeanFactory


ClassPathXmlApplicationContext 使用代码执行xml文件的类,是一个入口方法
通过这个文件向上找,会发现最终会继承ListableBeanFactory、HierarchicalBeanFactory等等一些实现了BeanFactory的抽象类

所以通过这个入口,我们发现BeanFactory是spring一切的基础,最顶级的抽象类

  • ListableBeanFactory:可列表化的bean工厂
  • HierarchicalBeanFactory:有层级关系的bean工厂(有父子继承关系的)
  • AutowireCapableBeanFactory:可自动注入的Bean工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface BeanFactory {
//对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象,
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";

//根据 bean 的名字,获取在 IOC 容器中得到 bean 实例
Object getBean(String name) throws BeansException;

//根据 bean 的名字和 Class 类型来得到 bean 实例,增加了类型安全验证机制。
Object getBean(String name, Class requiredType) throws BeansException;

//提供对 bean 的检索,看看是否在 IOC 容器有这个名字的 bean
boolean containsBean(String name);

//根据 bean 名字得到 bean 实例,并同时判断这个 bean 是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//得到 bean 实例的 Class 类型
Class getType(String name) throws NoSuchBeanDefinitionException;

//得到 bean 的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);
}

在 BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 bean 是如何定义怎样加载的。
正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。

要知道工厂是如何产生对象的,我们需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器的
实现。比如 XmlBeanFactory,ClasspathXmlApplicationContext 等。其中 XmlBeanFactory 就是针对最
基本的 IOC 容器的实现,这个 IOC 容器可以读取 XML 文件定义的 BeanDefinition(XML 文件中对 bean
的描述),如果说 XmlBeanFactory 是容器中的低配屌丝,ApplicationContext 应该算容器中的高帅富

从 ApplicationContext 接口的实现,我们看出其特点:

1
2
3
4
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

}

1.支持信息源,可以实现国际化。(实现 MessageSource 接口)
2.访问资源。(实现 ResourcePatternResolver 接口)
3.支持应用事件。(实现 ApplicationEventPublisher 接口)

¶BeanDefinition

这个主要是存储了bean定义的一些配置信息(其实就是讲xml中配置的信息,保存到BeanDefinition)

SpringIOC 容器管理了我们定义的各种 Bean 对象及其相互的关系,Bean 对象在 Spring 实现中是以
BeanDefinition 来描述的,其继承体系如下:

Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵
活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通
过下图中的类完成:

IOC容器的初始化

IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、载入和注册这三个基本的过程

  • 定位:就是找到定义的xml配置文件在哪里
  • 加载:通过xml解析工具解析xml文件
  • 注册:将配置信息交给beanFactory,对xml中的bean进行创建生产
    以ApplicationContext 为例,ApplicationContext 系列容器也许是我们最熟悉的,因为 web 项目中
    使用的 XmlWebApplicationContext 就属于这个继承体系,还有 ClasspathXmlApplicationContext 等,
    其继承体系如下图所示:

    ApplicationContext 允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于 bean 的查找
    可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的 Spring
    应用提供了一个共享的 bean 定义环境。

使用FileSystemXmlApplicationContext和使用ClassPathXmlApplicationContext的区别在于:

  • FileSystemXmlApplicationContext在指定的文件系统路径下查找xml文件和本项目下的classPath
    ApplicationContext context = new FileSystemXmlApplicationContext(“C:/bean.xml, classPath: bean.xml”);
  • ClassPathXmlApplicationContext是在所有的类路径(包含JAR文件) 下查找xml文件(classPath*)
    ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);

¶XmlBeanFactory

低配BeanFactory的整个流程
先看下XmlBeanFactory的源码:

1
2
3
4
5
6
7
8
9
10
11
public class XmlBeanFactory extends DefaultListableBeanFactory{
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource)throws BeansException{
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException{
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
}

还原一下调用过程

1
2
3
4
5
6
7
8
9
10
11
12
//根据 Xml 配置文件创建 Resource 资源对象,该对象中包含了 BeanDefinition 的信息
ClassPathResource resource =new ClassPathResource("application-context.xml");

//创建 DefaultListableBeanFactory
DefaultListableBeanFactory factory =new DefaultListableBeanFactory();

//创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition。之所以需要 BeanFactory 作为参数,是因为会将读取的信息配置回调给 factory
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);

//XmlBeanDefinitionReader 执行载入 BeanDefinition 的方法,最后会完成 Bean 的载入和注册。完成后 Bean 就成功
的放置到 IOC 容器当中,以后我们就可以从中取得 Bean 来使用
reader.loadBeanDefinitions(resource);

然后我们看下loadBeanDefinitions方法

其实就是对xml文件的解析,并把解析出来的bean 定义配置回传给factory,让其进行new对象

¶FileSystemXmlApplicationContext

最主要的构造方法,其余构造方法都是调用这个

1
2
3
4
5
6
7
8
9
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files and automatically refreshing the context.
* @param configLocations array of file paths
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}

实际调用

1
2
3
4
5
6
7
8
9
10
11
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {
// 为了动态的确定用什么加载器去加载配置文件
super(parent);
// 告诉读取器reader 配置文件在哪里:定位配置文件,为了加载配置文件
setConfigLocations(configLocations);
// 刷新
if (refresh) {
refresh();
}
}

因为spring的context是可以自定义的,所以如果不设置,则使用默认的资源加载器,如果配置则使用用户自定义的ApplicationContext

设置资源加载器和资源定位
通过分析 FileSystemXmlApplicationContext 的源代码可以知道,在创建
FileSystemXmlApplicationContext 容器时,构造方法做以下两项重要工作:
1.调用父类容器的构造方法(super(parent)方法)为容器设置好 Bean 资源加载器。
2.再调用父类 AbstractRefreshableConfigApplicationContext 的
setConfigLocations(configLocations)方法设置 Bean 定义资源文件的定位路径。
通过追踪 FileSystemXmlApplicationContext 的继承体系,发现其父类的父类
AbstractApplicationContext 中初始化 IOC 容器所做的主要源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
//静态初始化块,在整个容器创建过程中只执行一次
static {
//为了避免应用程序在 Weblogic8.1 关闭时出现类加载异常加载问题,加载 IoC 容器关闭事件(ContextClosedEvent)类
ContextClosedEvent.class.getName();
}
public AbstractApplicationContext() {
// 解析资源文件,动态的匹配
this.resourcePatternResolver = getResourcePatternResolver();
}
//FileSystemXmlApplicationContext 调用父类构造方法调用的就是该方法
public AbstractApplicationContext(ApplicationContext parent) {
this();
setParent(parent);
}
//获取一个 Spring Source 的加载器用于读入 Spring Bean 定义资源文件
protected ResourcePatternResolver getResourcePatternResolver() {
//AbstractApplicationContext 继承 DefaultResourceLoader,因此也是一个资源加载器
//Spring 资源加载器,其 getResource(String location)方法用于载入资源
return new PathMatchingResourcePatternResolver(this);
}
……
}

AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver的构造方法创建
Spring 资源加载器:

1
2
3
4
5
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
//设置 Spring 的资源加载器
this.resourceLoader = resourceLoader;
}

在设置容器的资源加载器之后,接下来 FileSystemXmlApplicationContet 执行 setConfigLocations 方
法通过调用其父类 AbstractRefreshableConfigApplicationContext 的方法进行对 Bean 定义资源文件
的定位,该方法的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//处理单个资源文件路径为一个字符串的情况
public void setConfigLocation(String location) {
//String CONFIG_LOCATION_DELIMITERS = ",; /t/n";
//即多个资源文件路径之间用” ,; /t/n”分隔,解析成数组形式
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}

//解析 Bean 定义资源文件的路径,处理多个资源文件字符串数组
public void setConfigLocations(String[] locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
// resolvePath 将字符串解析为路径的方法
this.configLocations[i] = resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}

通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个 Spring Bean 定义资源
文件,也可以使用字符串数组,即下面两种方式都是可以的:
1.ClasspathResource res = new ClasspathResource(“a.xml,b.xml,……”);
多个资源文件路径之间可以是用” ,; /t/n”等分隔。
2.ClasspathResource res = new ClasspathResource(newString[]{“a.xml”,”b.xml”,……});
Spring IOC 容器在初始化时将配置的 Bean 定义资源文件定位为 Spring 封装的 Resource。
3.AbstractApplicationContext 的 refresh 函数载入 Bean 定义过程:
Spring IOC 容器对 Bean 定义资源的载入是从 refresh()函数开始的,refresh()是一个模板方法,
refresh()方法的作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,
以保证在 refresh 之后使用的是新建立起来的 IOC 容器。refresh 的作用类似于对 IOC 容器的重启,在
新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入

¶refresh()

1
2
3
4
5
6
7
8
9
10
11
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {
// 为了动态的确定用什么加载器去加载配置文件
super(parent);
// 告诉读取器reader 配置文件在哪里:定位配置文件,为了加载配置文件
setConfigLocations(configLocations);
// 刷新
if (refresh) {
refresh();
}
}

FileSystemXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()函数启
动整个 IoC 容器对 Bean 定义的载入过程:

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
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
//告诉子类启动 refreshBeanFactory()方法,Bean 定义资源文件的载入从子类的 refreshBeanFactory()方法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//以下三个方法其实都是加载一些所谓的监听器(ApplicationAware/SessionAware等等)

//为容器的某些子类指定特殊的 BeanPost 事件处理器
//比如监听spring是否启动,ApplicationAware中的setApplicationContext方法就可以进行一些容器初始化事件操作
postProcessBeanFactory(beanFactory);
//调用所有注册的 BeanFactoryPostProcessor 的 Bean
invokeBeanFactoryPostProcessors(beanFactory);
//为 BeanFactory 注册 BeanPost 事件处理器.
//BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);

//初始化信息源,和国际化相关.
initMessageSource();

//初始化容器事件传播器.
initApplicationEventMulticaster();
//调用子类的某些特殊 Bean 初始化方法
onRefresh();
//为事件传播器注册事件监听器.
registerListeners();
//初始化所有剩余的单例 Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
} catch (BeansException ex) {
//销毁以创建的单态 Bean
destroyBeans();
//取消 refresh 操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}

refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件,Spring IOC 容器载入 Bean 定义资源文
件从其子类容器的 refreshBeanFactory()方法启动,所以整个 refresh()中
“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();”这句以后代码的
都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

refresh()方法的作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关
闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。refresh 的作用类似于对 IOC 容器的重启,
在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入

AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()
方法,启动容器载入 Bean 定义资源文件的过程,代码如下:

1
2
3
4
5
6
7
8
9
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用了委派设计模式,父类定义了抽象的 refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

AbstractApplicationContext 子类的 refreshBeanFactory()方法:
AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory()方法,容器真正调用的是
其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法,方法的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected final void refreshBeanFactory() throws BeansException {
//如果已经有容器,销毁容器中的 bean,关闭容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建 IOC 容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//对 IOC 容器进行定制化,如设置启动参数,开启注解的自动装配等
customizeBeanFactory(beanFactory);
//调用载入 Bean 定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的 loadBeanDefinitions方法,具体的实现调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

在这个方法中,先判断 BeanFactory 是否存在,如果存在则先销毁 beans 并关闭 beanFactory,接着创
建 DefaultListableBeanFactory,并调用 loadBeanDefinitions(beanFactory)装载 bean 定义。

AbstractRefreshableApplicationContext 子类的 loadBeanDefinitions 方法:
AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法,容器真正调
用的是其子类 AbstractXmlApplicationContext 对该方法的实现,AbstractXmlApplicationContext 的
主要源码如下:
loadBeanDefinitions 方法同样是抽象方法,是由其子类实现的,也即在
AbstractXmlApplicationContext 中。

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
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

……

//实现父类抽象的载入 Bean 定义方法
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException,
IOException {
//创建 XmlBeanDefinitionReader,即创建 Bean 读取器,并通过回调设置到容器中去,容 器使用该读取器读取 Bean 定义资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//为 Bean 读取器设置 Spring 资源加载器,AbstractXmlApplicationContext 的祖先父类 AbstractApplicationContext 继承 DefaultResourceLoader,因此,容器本身也是一个资源加载器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
//为 Bean 读取器设置 SAX xml 解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//当 Bean 读取器读取 Bean 定义的 Xml 资源文件时,启用 Xml 的校验机制
initBeanDefinitionReader(beanDefinitionReader);
//Bean 读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}

//Xml Bean 读取器加载 Bean 定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException,IOException {
//获取 Bean 定义资源的定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 定义资源
reader.loadBeanDefinitions(configResources);
}
//如果子类中获取的 Bean 定义资源定位为空,则获取 FileSystemXmlApplicationContext 构造方法中setConfigLocations 方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 定义资源
reader.loadBeanDefinitions(configLocations);
}
}

//这里又使用了一个委托模式,调用子类的获取 Bean 定义资源定位的方法
//该方法在 ClassPathXmlApplicationContext 中进行实现,对于我们
//举例分析源码的 FileSystemXmlApplicationContext 没有使用该方法
protected Resource[] getConfigResources() {
return null;
}
……
}

Xml Bean 读取器(XmlBeanDefinitionReader)调用其父类 AbstractBeanDefinitionReader
的 reader.loadBeanDefinitions 方法读取 Bean 定义资源。

由于我们使用 FileSystemXmlApplicationContext 作为例子分析,因此 getConfigResources 的返回值
为 null,因此程序执行 reader.loadBeanDefinitions(configLocations)分支。

AbstractBeanDefinitionReader 读取 Bean 定义资源:
AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源码如下
可以到 org.springframework.beans.factory.support 看一下 BeanDefinitionReader 的结构

在其抽象父类 AbstractBeanDefinitionReader 中定义了载入过程

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
//重载方法,调用下面的 loadBeanDefinitions(String, Set<Resource>);方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取在 IoC 容器初始化过程中设置的资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader
available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//将指定位置的 Bean 定义资源文件解析为 Spring IOC 容器封装的资源
//加载多个指定位置的 Bean 定义资源文件
// ResourcePatternResolver就是多个文件解析器,就是下面说到的多个文件可以使用,;等符号进行分割
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
//委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" +
location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
}
} else {
//将指定位置的 Bean 定义资源文件解析为 Spring IOC 容器封装的资源
//加载单个指定位置的 Bean 定义资源文件
// AbstractXmlApplicationContext line 89 设置了默认的资源加载器
Resource resource = resourceLoader.getResource(location);
//委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location +
"]");
}
return loadCount;
}
}
//重载方法,调用 loadBeanDefinitions(String);
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}

loadBeanDefinitions(Resource…resources)方法和上面分析的 3 个方法类似,同样也是调用
XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。

从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源码分析可以看出该方法做了以下
两件事:

  • 调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源。
  • 真正执行加载功能是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。

看到第 8、16 行,结合上面的 ResourceLoader 与 ApplicationContext 的继承系图,可以知道此时调用
的是 DefaultResourceLoader 中的 getSource()方法定位 Resource,因为
FileSystemXmlApplicationContext 本身就是 DefaultResourceLoader 的实现类,所以此时又回到了
FileSystemXmlApplicationContext 中来。

资源加载器获取要读入的资源:
XmlBeanDefinitionReader 通过调用其父类 DefaultResourceLoader 的 getResource 方法获取要加载的
资源,其源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//获取 Resource 的具体实现方法
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//如果是类路径的方式,那需要使用 ClassPathResource 来得到 bean 文件的资源对象
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()),getClassLoader());
}
try {
// 如果是 URL 方式,使用 UrlResource 作为 bean 文件的资源对象
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
}
//如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用
//容器本身的 getResourceByPath 方法获取 Resource
return getResourceByPath(location);
}

FileSystemXmlApplicationContext 容器提供了 getResourceByPath 方法的实现,就是为了处理既不是
classpath 标识,又不是 URL 标识的 Resource 定位这种情况。

1
2
3
4
5
6
7
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
//这里使用文件系统资源对象来定义 bean 文件
return new FileSystemResource(path);
}

这样代码就回到了 FileSystemXmlApplicationContext 中来,他提供了 FileSystemResource 来完
成从文件系统得到配置文件的资源定义。

这样,就可以从文件系统路径上对 IOC 配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方
加载,在 Spring 中我们看到它提供 的各种资源抽象,比如
ClassPathResource, URLResource,FileSystemResource 等来供我们使用。上面我们看到的是定位
Resource 的一个过程,而这只是加载过程的一部分.

¶XmlBeanDefinitionReader 加载 Bean 定义资源

Bean 定义的 Resource 得到了

继续回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文
件的资源定义以后的载入过程。

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
//XmlBeanDefinitionReader 加载资源的入口方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//将读入的 XML 资源进行特殊编码处理
return loadBeanDefinitions(new EncodedResource(resource));
}

//这里是载入 XML 形式 Bean 定义资源文件方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
.......
// 主要去除配置文件的循环依赖
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//将资源文件转为 InputStream 的 IO 流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//从 InputStream 中得到 XML 的解析源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//这里是具体的读取过程
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
//关闭从 Resource 中得到的 IO 流
inputStream.close();
}
}
.........
}

//从特定 XML 文件中实际载入 Bean 定义资源的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
//将 XML 文件转换为 DOM 对象,解析过程由 documentLoader 实现
Document doc = this.documentLoader.loadDocument(
inputSource, this.entityResolver, this.errorHandler, validationMode,
this.namespaceAware);
//这里是启动对 Bean 定义解析的详细过程,该解析过程会用到 Spring 的 Bean 配置规则
return registerBeanDefinitions(doc, resource);
}
.......
}

通过源码分析,载入 Bean 定义资源文件的最后一步是将 Bean 定义资源转换为 Document 对象,该过程
由 documentLoader 实现

DocumentLoader 将 Bean 定义资源转换为 Document 对象:
DocumentLoader 将 Bean 定义资源转换成 Document 对象的源码如下:

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
//使用标准的 JAXP 将载入的 Bean 定义资源转换成 document 对象
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//创建文件解析器工厂
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode,
namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//创建文档解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析 Spring 的 Bean 定义资源
return builder.parse(inputSource);
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException {
//创建文档解析工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
//设置解析 XML 的校验
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
} catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.with Apache Crimson?
" +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}

该解析过程调用 JavaEE 标准的 JAXP 标准进行处理。
至此 Spring IOC 容器根据定位的 Bean 定义资源文件,将其加载读入并转换成为 Document 对象过程完成。
接下来我们要继续分析 Spring IOC 容器将载入的 Bean 定义资源文件转换为 Document 对象之后,是如
何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中的。

¶XmlBeanDefinitionReader 解析载入的 Bean 定义资源文件

XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML 文件中实际载入Bean 定
义资源的方法,该方法在载入 Bean 定义资源之后将其转换为 Document 对象,接下来调用
registerBeanDefinitions 启动 Spring IOC 容器对 Bean 定义的解析过程,registerBeanDefinitions
方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//按照 Spring 的 Bean 语义要求将 Bean 定义资源解析并转换为容器内部数据结构
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//得到 BeanDefinitionDocumentReader 来对 xml 格式的 BeanDefinition 解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获得容器中注册的 Bean 数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader 只是个接口,
//具体的解析实现过程有实现类 DefaultBeanDefinitionDocumentReader 完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//统计解析的 Bean 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
//创建 BeanDefinitionDocumentReader 对象,解析 Document 对象
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}

Bean 定义资源的载入解析分为以下两个过程:

  • 首先,通过调用 XML 解析器将 Bean 定义资源文件转换得到 Document 对象,但是这些 Document 对象并没有按照 Spring 的 Bean 规则进行解析。这一步是载入的过程
  • 在完成通用的 XML 解析之后,按照 Spring 的 Bean 规则对 Document 对象进行解析。

按照 Spring 的 Bean 规则对 Document 对象解析的过程是在接口 BeanDefinitionDocumentReader 的实现
类 DefaultBeanDefinitionDocumentReader 中实现的。

¶DefaultBeanDefinitionDocumentReader 对 Bean 定义的 Document 对象解析

BeanDefinitionDocumentReader 接口通过 registerBeanDefinitions 方法调用其实现类
DefaultBeanDefinitionDocumentReader 对 Document 对象进行解析,解析的代码如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//根据 Spring DTD 对 Bean 的定义规则解析 Bean 定义 Document 对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//获得 XML 描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得 Document 的根元素
Element root = doc.getDocumentElement();
//具体的解析过程由 BeanDefinitionParserDelegate 实现,
//BeanDefinitionParserDelegate 中定义了 Spring Bean 定义 XML 文件的各种元素
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
//在解析 Bean 定义之前,进行自定义的解析,增强解析过程的可扩展性
preProcessXml(root);
//从 Document 的根元素开始进行 Bean 定义的 Document 对象
parseBeanDefinitions(root, delegate);
//在解析 Bean 定义之后,进行自定义的解析,增加解析过程的可扩展性
postProcessXml(root);
}

//创建 BeanDefinitionParserDelegate,用于完成真正的解析过程
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
//BeanDefinitionParserDelegate 初始化 Document 根元素
delegate.initDefaults(root);
return delegate;
}

//使用 Spring 的 Bean 规则从 Document 的根元素开始进行 Bean 定义的 Document 对象
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//Bean 定义的 Document 对象使用了 Spring 默认的 XML 命名空间
if (delegate.isDefaultNamespace(root)) {
//获取 Bean 定义的 Document 对象根元素的所有子节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//获得 Document 节点是 XML 元素节点
if (node instanceof Element) {
Element ele = (Element) node;
//Bean 定义的 Document 的元素节点使用的是 Spring 默认的 XML 命名空间
if (delegate.isDefaultNamespace(ele)) {
//使用 Spring 的 Bean 规则解析元素节点
parseDefaultElement(ele, delegate);
} else {
//没有使用 Spring 默认的 XML 命名空间,则使用用户自定义的解析规则解析元素节点
delegate.parseCustomElement(ele);
}
}
}
} else {
//Document 的根节点没有使用 Spring 默认的命名空间,则使用用户自定义的解析规则解析 Document 根节点
delegate.parseCustomElement(root);
}
}

//使用 Spring 的 Bean 规则解析 Document 元素节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果元素节点是<Import>导入元素,进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素节点是<Alias>别名元素,进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,
//按照 Spring 的 Bean 规则解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
}

//解析<Import>导入元素,从给定的导入路径加载 Bean 定义资源到 Spring IoC 容器中
protected void importBeanDefinitionResource(Element ele) {
//获取给定的导入元素的 location 属性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
//如果导入元素的 location 属性值为空,则没有导入任何资源,直接返回
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
//使用系统变量值解析 location 属性值
location = SystemPropertyUtils.resolvePlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
//标识给定的导入元素的 location 是否是绝对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) ||
ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
//给定的导入元素的 location 不是绝对路径
}
//给定的导入元素的 location 是绝对路径
if (absoluteLocation) {
try {
//使用资源读入器加载给定路径的 Bean 定义资源
int importCount = getReaderContext().getReader().loadBeanDefinitions(location,
actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" +
location + "]");
}
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
} else {
//给定的导入元素的 location 是相对路径
try {
int importCount;
//将给定导入元素的 location 封装为相对路径资源
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//封装的相对路径资源存在
if (relativeResource.exists()) {
//使用资源读入器加载 Bean 定义资源
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
//封装的相对路径资源不存在
else {
//获取 Spring IOC 容器资源读入器的基本路径
String baseLocation = getReaderContext().getResource().getURL().toString();
//根据 Spring IoC 容器资源读入器的基本路径加载给定导入路径的资源
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location ["
+ location + "]");
}
} catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" +
location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
//在解析完<Import>元素之后,发送容器导入其他资源处理完成事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

//解析<Alias>别名元素,为 Bean 向 Spring IoC 容器注册别名
protected void processAliasRegistration(Element ele) {
//获取<Alias>别名元素中 name 的属性值
String name = ele.getAttribute(NAME_ATTRIBUTE);
//获取<Alias>别名元素中 alias 的属性值
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
//<alias>别名元素的 name 属性值为空
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
//<alias>别名元素的 alias 属性值为空
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//向容器的资源读入器注册别名
getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
//在解析完<Alias>元素之后,发送容器别名处理完成事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}

//解析 Bean 定义资源 Document 对象的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// BeanDefinitionHolder 是对 BeanDefinition 的封装,即 Bean 定义的封装类
//对 Document 对象中<Bean>元素的解析由 BeanDefinitionParserDelegate 实现 BeanDefinitionHolder
bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//向 Spring IoC 容器注册解析得到的 Bean 定义,这是 Bean 定义向 IOC 容器注册的入口
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,
getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
//在完成向 Spring IoC 容器注册解析得到的 Bean 定义之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

通过上述 Spring IOC 容器对载入的 Bean 定义 Document 解析可以看出,我们使用 Spring 时,在 Spring
配置文件中可以使用<Import>元素来导入 IOC 容器所需要的其他资源,Spring IoC 容器在解析时会首
先将指定导入的资源加载进容器中。使用<Ailas>别名时,Spring IOC 容器首先将别名元素所定义的别
名注册到容器中。

对于既不是<Import>元素,又不是<Alias>元素的元素,即 Spring 配置文件中普通的<Bean>元素的解析
由 BeanDefinitionParserDelegate 类的 parseBeanDefinitionElement 方法来实现。

¶BeanDefinitionParserDelegate 解析 Bean 定义资源文件中的<Bean>元素

Bean 定义资源文件中的<Import>和<Alias>元素解析在 DefaultBeanDefinitionDocumentReader 中已经
完成,对 Bean 定义资源文件中使用最多的<Bean>元素交由 BeanDefinitionParserDelegate 来解析,其解析实现的源码如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//解析<Bean>元素的入口
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
//解析 Bean 定义资源文件中的<Bean>元素,这个方法中主要处理<Bean>元素的 id,name和别名属性
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition
containingBean) {
//获取<Bean>元素中的 id 属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取<Bean>元素中的 name 属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//获取<Bean>元素中的 alias 属性值
List<String> aliases = new ArrayList<String>();
//将<Bean>元素中的所有 name 属性值存放到别名中
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
//如果<Bean>元素中没有配置 id 属性时,将别名中的第一个值赋值给 beanName
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//检查<Bean>元素所配置的 id 或者 name 的唯一性,containingBean 标识<Bean>
//元素中是否包含子<Bean>元素
if (containingBean == null) {
//检查<Bean>元素所配置的 id、name 或者别名是否重复
checkNameUniqueness(beanName, aliases, ele);
}
//详细对<Bean>元素中配置的 Bean 定义进行解析的地方
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName,
containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果<Bean>元素中没有配置 id、别名或者 name,且没有包含子元素
//<Bean>元素,为解析的 Bean 生成一个唯一 beanName 并注册
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
//如果<Bean>元素中没有配置 id、别名或者 name,且包含了子元素
//<Bean>元素,为解析的 Bean 使用别名向 IOC 容器注册
beanName = this.readerContext.generateBeanName(beanDefinition);
//为解析的 Bean 使用别名注册时,为了向后兼容
//Spring1.2/2.0,给别名添加类名后缀
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() >
beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
//当解析出错时,返回 null
return null;
}
//详细对<Bean>元素中配置的 Bean 定义其他属性进行解析,由于上面的方法中已经对
//Bean 的 id、name 和别名等属性进行了处理,该方法中主要处理除这三个以外的其他属性数据
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
//记录解析的<Bean>
this.parseState.push(new BeanEntry(beanName));
//这里只读取<Bean>元素中配置的 class 名字,然后载入到 BeanDefinition 中去
//只是记录配置的 class 名字,不做实例化,对象的实例化在依赖注入时完成
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
//如果<Bean>元素中配置了 parent 属性,则获取 parent 属性的值
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//根据<Bean>元素配置的 class 名称和 parent 属性值创建 BeanDefinition
//为载入 Bean 定义信息做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//对当前的<Bean>元素中配置的一些属性进行解析和设置,如配置的单态(singleton)属性等
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//为<Bean>元素解析的 Bean 设置 description 信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//对<Bean>元素的 meta(元信息)属性解析
parseMetaElements(ele, bd);
//对<Bean>元素的 lookup-method 属性解析
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//对<Bean>元素的 replaced-method 属性解析
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析<Bean>元素的构造方法设置
parseConstructorArgElements(ele, bd);
//解析<Bean>元素的<property>设置
parsePropertyElements(ele, bd);
//解析<Bean>元素的 qualifier 属性
parseQualifierElements(ele, bd);
//为当前解析的 Bean 设置所需的资源和依赖对象
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
//解析<Bean>元素出错时,返回 null
return null;
}

只要使用过 Spring,对 Spring 配置文件比较熟悉的人,通过对上述源码的分析,就会明白我们在 Spring
配置文件中<Bean>元素的中配置的属性就是通过该方法解析和设置到 Bean 中去的。

注意:在解析<Bean>元素过程中没有创建和实例化 Bean 对象,只是创建了 Bean 对象的定义类BeanDefinition,将<Bean>元素中的配置信息设置到 BeanDefinition 中作为记录,当依赖注入时才使用这些记录信息创建和实例化具体的 Bean 对象。

上面方法中一些对一些配置如元信息(meta)、qualifier 等的解析,我们在 Spring 中配置时使用的也不多,我们在使用 Spring 的<Bean>元素时,配置最多的是<property>属性,因此我们下面继续分析源码,了解 Bean 的属性在解析时是如何设置的。

¶BeanDefinitionParserDelegate 解析<property>元素

BeanDefinitionParserDelegate 在解析<Bean>调用 parsePropertyElements 方法解析<Bean>元素中的
<property>属性子元素,解析源码如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//解析<Bean>元素中的<property>子元素
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
//获取<Bean>元素中所有的子元素
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//如果子元素是<property>子元素,则调用解析<property>子元素方法解析
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
//解析<property>元素
public void parsePropertyElement(Element ele, BeanDefinition bd) {
//获取<property>元素的名字
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
//如果一个 Bean 中已经有同名的 property 存在,则不进行解析,直接返回。
//即如果在同一个 Bean 中配置同名的 property,则只有第一个起作用
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
//解析获取 property 的值
Object val = parsePropertyValue(ele, bd, propertyName);
//根据 property 的名字和值创建 property 实例
PropertyValue pv = new PropertyValue(propertyName, val);
//解析<property>元素中的属性
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
//解析获取 property 值
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
//获取<property>的所有子元素,只能是其中一种类型:ref,value,list 等
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//子元素不是 description 和 meta 属性
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
//当前<property>元素包含有子元素
else {
subElement = (Element) node;
}
}
}
//判断 property 的属性值是 ref 还是 value,不允许既是 ref 又是 value
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR
sub-element", ele);
}
//如果属性是 ref,创建一个 ref 的数据对象 RuntimeBeanReference
//这个对象封装了 ref 信息
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
//一个指向运行时所依赖对象的引用
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
//设置这个 ref 的数据对象是被当前的 property 对象所引用
ref.setSource(extractSource(ele));
return ref;
}
//如果属性是 value,创建一个 value 的数据对象 TypedStringValue
//这个对象封装了 value 信息
else if (hasValueAttribute) {
//一个持有 String 类型值的对象
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
//设置这个 value 数据对象是被当前的 property 对象所引用
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
//如果当前<property>元素还有子元素
else if (subElement != null) {
//解析<property>的子元素
return parsePropertySubElement(subElement, bd);
}
else {
//propery 属性中既不是 ref,也不是 value 属性,解析出错返回 null
error(elementName + " must specify a ref or value", ele);
return null;
}
}

通过对上述源码的分析,我们可以了解在 Spring 配置文件中,<Bean>元素中<property>元素的相关配
置是如何处理的:

  • ref 被封装为指向依赖对象一个引用
  • value 配置都会封装成一个字符串类型的对象
  • ref 和 value 都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来
    在方法的最后对于<property>元素的子元素通过 parsePropertySubElement 方法解析,我们继续分析
    该方法的源码,了解其解析过程。

¶解析<property>元素的子元素

在 BeanDefinitionParserDelegate 类中的 parsePropertySubElement 方法对<property>中的子元素解析,源码如下:

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
86
//解析<property>元素中 ref,value 或者集合等子元素
public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {
//如果<property>没有使用 Spring 默认的命名空间,则使用用户自定义的规则解析
//内嵌元素
if (!isDefaultNamespace(ele)) {
return parseNestedCustomElement(ele, bd);
}
//如果子元素是 bean,则使用解析<Bean>元素的方法解析
else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
}
//如果子元素是 ref,ref 中只能有以下 3 个属性:bean、local、parent
else if (nodeNameEquals(ele, REF_ELEMENT)) {
//获取<property>元素中的 bean 属性值,引用其他解析的 Bean 的名称
//可以不再同一个 Spring 配置文件中,具体请参考 Spring 对 ref 的配置规则
String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
boolean toParent = false;
if (!StringUtils.hasLength(refName)) {
//获取<property>元素中的 local 属性值,引用同一个 Xml 文件中配置
//的 Bean 的 id,local 和 ref 不同,local 只能引用同一个配置文件中的 Bean
refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);
if (!StringUtils.hasLength(refName)) {
//获取<property>元素中 parent 属性值,引用父级容器中的 Bean
refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
toParent = true;
if (!StringUtils.hasLength(refName)) {
error("'bean', 'local' or 'parent' is required for <ref> element", ele);
return null;
}
}
}
//没有配置 ref 的目标属性值
if (!StringUtils.hasText(refName)) {
error("<ref> element contains empty target attribute", ele);
return null;
}
//创建 ref 类型数据,指向被引用的对象
RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
//设置引用类型值是被当前子元素所引用
ref.setSource(extractSource(ele));
return ref;
}
//如果子元素是<idref>,使用解析 ref 元素的方法解析
else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
return parseIdRefElement(ele);
}
//如果子元素是<value>,使用解析 value 元素的方法解析
else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
return parseValueElement(ele, defaultValueType);
}
//如果子元素是 null,为<property>设置一个封装 null 值的字符串数据
else if (nodeNameEquals(ele, NULL_ELEMENT)) {
TypedStringValue nullHolder = new TypedStringValue(null);
nullHolder.setSource(extractSource(ele));
return nullHolder;
}
//如果子元素是<array>,使用解析 array 集合子元素的方法解析
else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
return parseArrayElement(ele, bd);
}
//如果子元素是<list>,使用解析 list 集合子元素的方法解析
else if (nodeNameEquals(ele, LIST_ELEMENT)) {
return parseListElement(ele, bd);
}
//如果子元素是<set>,使用解析 set 集合子元素的方法解析
else if (nodeNameEquals(ele, SET_ELEMENT)) {
return parseSetElement(ele, bd);
}
//如果子元素是<map>,使用解析 map 集合子元素的方法解析
else if (nodeNameEquals(ele, MAP_ELEMENT)) {
return parseMapElement(ele, bd);
}
//如果子元素是<props>,使用解析 props 集合子元素的方法解析
else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
return parsePropsElement(ele);
}
//既不是 ref,又不是 value,也不是集合,则子元素配置错误,返回 null
else {
error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
return null;
}
}

通过上述源码分析,我们明白了在 Spring 配置文件中,对<property>元素中配置的 Array、List、Set、
Map、Prop 等各种集合子元素的都通过上述方法解析,生成对应的数据对象,比如 ManagedList、
ManagedArray、ManagedSet 等,这些 Managed 类是 Spring 对象 BeanDefiniton 的数据封装,对集合数据类型的具体解析有各自的解析方法实现,解析方法的命名非常规范,一目了然,我们对集合元素的解析方法进行源码分析,了解其实现过程。

¶解析<list>子元素

在 BeanDefinitionParserDelegate 类中的 parseListElement 方法就是具体实现解析<property>元素中的<list>集合子元素,源码如下:

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
//解析<list>集合子元素
public List parseListElement(Element collectionEle, BeanDefinition bd) {
//获取<list>元素中的 value-type 属性,即获取集合元素的数据类型
String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);
//获取<list>集合元素中的所有子节点
NodeList nl = collectionEle.getChildNodes();
//Spring 中将 List 封装为 ManagedList
ManagedList<Object> target = new ManagedList<Object>(nl.getLength());
target.setSource(extractSource(collectionEle));
//设置集合目标数据类型
target.setElementTypeName(defaultElementType);
target.setMergeEnabled(parseMergeAttribute(collectionEle));
//具体的<list>元素解析
parseCollectionElements(nl, target, bd, defaultElementType);
return target;
}
//具体解析<list>集合元素,<array>、<list>和<set>都使用该方法解析
protected void parseCollectionElements(
NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String
defaultElementType) {
//遍历集合所有节点
for (int i = 0; i < elementNodes.getLength(); i++) {
Node node = elementNodes.item(i);
//节点不是 description 节点
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) {
//将解析的元素加入集合中,递归调用下一个子元素
target.add(parsePropertySubElement((Element) node, bd, defaultElementType));
}
}
}

经过对 Spring Bean 定义资源文件转换的 Document 对象中的元素层层解析,Spring IOC 现在已经将
XML 形式定义的 Bean 定义资源文件转换为 Spring IOC 所识别的数据结构——BeanDefinition,它是
Bean 定义资源文件中配置的 POJO 对象在 Spring IOC 容器中的映射,我们可以通过
AbstractBeanDefinition 为入口,看到了 IOC 容器进行索引、查询和操作。

通过 Spring IOC 容器对 Bean 定义资源的解析后,IOC 容器大致完成了管理 Bean 对象的准备工作,即
初始化过程,但是最为重要的依赖注入还没有发生,现在在 IOC 容器中 BeanDefinition 存储的只是一
些静态信息,接下来需要向容器注册 Bean 定义信息才能全部完成 IoC 容器的初始化过程

¶解析过后的 BeanDefinition 在 IOC 容器中的注册

让我们继续跟踪程序的执行顺序,接下来会到我们第3步中分析DefaultBeanDefinitionDocumentReader
对 Bean 定义转换的 Document 对象解析的流程中,在其 parseDefaultElement 方法中完成对 Document
对象的解析后得到封装 BeanDefinition 的 BeanDefinitionHold 对象,然后调用
BeanDefinitionReaderUtils 的 registerBeanDefinition 方法向 IOC 容器注册解析的 Bean,BeanDefinitionReaderUtils 的注册的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//将解析的 BeanDefinitionHold 注册到容器中
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
//获取解析的 BeanDefinition 的名称
String beanName = definitionHolder.getBeanName();
//向 IOC 容器注册 BeanDefinition
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//如果解析的 BeanDefinition 有别名,向容器为其注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}

当调用 BeanDefinitionReaderUtils 向 IOC 容器注册解析的 BeanDefinition 时,真正完成注册功能的
是 DefaultListableBeanFactory。

¶DefaultListableBeanFactory 向 IOC 容器注册解析后的 BeanDefinition

DefaultListableBeanFactory 向 IOC 容器注册解析后的 BeanDefinition:

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
//存储注册信息的 BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
//向 IoC 容器注册解析的 BeanDefiniton
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
//校验解析的 BeanDefiniton
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(),
beanName,
"Validation of bean definition failed", ex);
}
}
//注册的过程中需要线程同步,以保证数据的一致性
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
//检查是否有同名的 BeanDefinition 已经在 IOC 容器中注册,如果已经注册,
//并且不允许覆盖已注册的 Bean,则抛出注册失败异常
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(),
beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" +
beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else {
//如果允许覆盖,则同名的 Bean,后注册的覆盖先注册的
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
//IOC 容器中没有已经注册同名的 Bean,按正常注册流程注册
else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
//重置所有已经注册过的 BeanDefinition 的缓存
resetBeanDefinition(beanName);
}
}

至此,Bean 定义资源文件中配置的 Bean 被解析过后,已经注册到 IOC 容器中,被容器管理起来,真正
完成了 IOC 容器初始化所做的全部工作。现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些
BeanDefinition 信息已经可以使用,并且可以被检索,IOC 容器的作用就是对这些注册的 Bean 定义信
息进行处理和维护。这些的注册的 Bean 定义信息是 IoC 容器控制反转的基础,正是有了这些注册的数
据,容器才可以进行依赖注入。

总结

现在通过上面的代码,总结一下 IOC 容器初始化的基本步骤:

  • 初始化的入口在容器实现中的 refresh()调用来完成
  • 对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,

其中的大致过程如下:
通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默
认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统,URL 等方式来
定为资源位置。如果是 XmlBeanFactory 作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说
bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的,容器通过 BeanDefinitionReader 来完成
定义信息的解析和 Bean 信息的注册,往往使用的是 XmlBeanDefinitionReader 来解析 bean 的 xml 定义
文件-实际的处理过程是委托给 BeanDefinitionParserDelegate来完成的,从而得到bean的定义信息,
这些信息在 Spring 中使用 BeanDefinition 对象来表示-这个名字可以让我们想到
loadBeanDefinition,RegisterBeanDefinition 这些相关方法-他们都是为处理BeanDefinitin 服务的,
容器解析得到 BeanDefinitionIoC 以后,需要把它在 IOC 容器中注册,这由 IOC 实
现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个 HashMap 来保存得
到的 BeanDefinition 的过程。这个 HashMap 是 IOC 容器持有 bean 信息的场所,以后对 bean 的操作都
是围绕这个 HashMap 来实现的.

然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 SpringIOC 的服务了,在使用 IOC
容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IOC 风格编写的应用程序代码完全不用关
心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已
知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring
本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在 ServletContext 中的框架
实现。

在使用 SpringIOC 容器的时候我们还需要区别两个概念

¶BeanFactory 和 FactoryBean

  • BeanFactory 功能性工厂,专门用来生产bean的,如果是生产车的就叫做CarFactory,是 IOC 容器的编程抽象,比如ApplicationContext,XmlBeanFactory 等,这些都是 IOC 容器的具体表现,需要使用什么样的容器由客户决定,但 Spring 为我们提供了丰富的选择。
  • FactoryBean 是由spring工厂生产出来的bean, 只是一个可以在 IOC 而容器中被管理的一个bean,是对各种处理过程和资源使用的抽象,FactoryBean 在需要时产生另一个对象,而不返回FactoryBean 本身,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的FactoryBean 都实现特殊的 org.springframework.beans.factory.FactoryBean 接口,当使用容器中FactoryBean 的时候,该容器不会返回 FactoryBean 本身,而是返回其生成的对象。Spring 包括了大部分的通用资源和服务访问抽象的 FactoryBean 的实现,其中包括:对 JNDI 查询的处理,对代理对象的处理,对事务性代理的处理,对 RMI 代理的处理等,这些我们都可以看成是具体的工厂,看成是 Spring 为我们建立好的工厂。也就是说 Spring 通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在 IOC 容器里配置好就能很方便的使用了

¶springboot

SpringApplication
AnnotationConfigServletWebServerApplicationContext
ServletWebServerApplicationContext
AbstractApplicationContext 在这个类中定义了扫描注解的执行器
ClassPathBeanDefinitionScanner
ClassPathScanningCandidateComponentProvider
AbstractTypeHierarchyTraversingFilter
AnnotationTypeFilter

fq软件安装

发表于 2019-10-22 | 更新于 2019-10-29 | 分类于 VPS | 评论数: | 阅读次数:

Shadowsocks 一键安装脚本(四合一)

¶使用root用户登录,运行以下命令:

1
wget --no-check-certificate -O shadowsocks-all.sh https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh
1
chmod +x shadowsocks-all.sh ./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log

¶安装完成后,脚本提示如下

1
2
3
4
5
6
7
8
9
10
11
Congratulations, your_shadowsocks_version install completed!
Your Server IP :your_server_ip
Your Server Port :your_server_port
Your Password :your_password
Your Encryption Method:your_encryption_method
Your QR Code: (For Shadowsocks Windows, OSX, Android and iOS clients)
ss://your_encryption_method:your_password@your_server_ip:your_server_port
Your QR Code has been saved as a PNG file path:
your_path.png
Welcome to visit:https://teddysun.com/486.html
Enjoy it!

¶卸载方法

若已安装多个版本,则卸载时也需多次运行(每次卸载一种)
使用root用户登录,运行以下命令:

1
./shadowsocks-all.sh uninstall

¶启动脚本

启动脚本后面的参数含义,从左至右依次为:启动,停止,重启,查看状态。

¶Shadowsocks-Python 版:

1
/etc/init.d/shadowsocks-python start | stop | restart | status

¶ShadowsocksR 版:

1
/etc/init.d/shadowsocks-r start | stop | restart | status

¶Shadowsocks-Go 版:

1
/etc/init.d/shadowsocks-go start | stop | restart | status

¶Shadowsocks-libev 版:

1
/etc/init.d/shadowsocks-libev start | stop | restart | status

¶各版本默认配置文件

¶Shadowsocks-Python 版:

/etc/shadowsocks-python/config.json

¶ShadowsocksR 版:

/etc/shadowsocks-r/config.json

¶Shadowsocks-Go 版:

/etc/shadowsocks-go/config.json

¶Shadowsocks-libev 版:

/etc/shadowsocks-libev/config.json

Kcptun 服务端安装脚本

GitHub 地址:https://github.com/kuoruan/shell-scripts

¶安装

注意:在配置之前请确认一下你的加速地址,大部分不能加速都是由于加速地址配置错误。
首先找到你的 Shadowsocks 端口,比如我的 Shadowsocks 端口为 8388,然后在命令行输入以下命令:

1
netstat -nl | grep 8388

如果提示以上命令不存在,请输入:

1
ss -nl | grep 8388

将 8388 替换为你的 Shadowsocks 端口。
然后你会看到类似下面的输出(着重看显示为红色的部分):
情况1:

1
2
tcp6       0      0     :::8388                 :::*                    LISTEN     
udp6 0 0 :::8388 :::*

情况2:

1
2
tcp       0      0     127.0.0.1:8388                 :::*                    LISTEN     
udp 0 0 127.0.0.1:8388 :::*

情况3:

1
2
tcp       0      0     0.0.0.0:8388                 :::*                    LISTEN     
udp 0 0 0.0.0.0:8388 :::*

情况4(假如 10.10.10.10 是当前服务器IP):

1
2
tcp       0      0     10.10.10.10:8388                 :::*                    LISTEN     
udp 0 0 10.10.10.10:8388 :::*

若为情况1、情况2和情况3,那么你的加速地址可以为:加速 IP 127.0.0.1,加速端口 8388(你的 Shadowsocks 端口)
若为情况4,那么你的加速地址为:加速IP 10.10.10.10(你的服务器IP),加速端口8388(你的 Shadowsocks 端口)

使用方法:

1
2
wget --no-check-certificate https://github.com/kuoruan/shell-scripts/raw/master/kcptun/kcptun.sh
chmod +x ./kcptun.sh
1
./kcptun.sh

¶设置 Kcptun 的服务端端口

1
2
请输入 Kcptun Server 端口 [1-65535]:
(默认: 29900):

请输入一个未被占用的端口,Kcptun 运行时将使用此端口。

¶设置加速的 IP

1
2
请输入需要加速的地址:
(默认: 127.0.0.1):

填入上面获取到的加速 IP。如果你想使用 IPv6,请直接填写 IPv6 地址,不需要加 [],脚本会自动添加。

¶设置需要加速的端口

1
2
请输入需要加速的端口 [1-65535]:
(默认: 12948):

填入上面获取到的加速端口。
程序会检查当前是不是有程序占用着此端口,如果你的 Shadowsocks 没在运行,或者没有软件使用此端口,会弹出如下提示:

1
当前没有软件使用此端口, 确定加速此端口?(y/n)

¶设置 Kcptun 密码

1
2
请输入 Kcptun 密码:
(如果不想使用密码请留空):

这就是说,你可以为 Kcptun 单独设置一个密码,防止被他人恶意使用。这个密码和 SS 的密码没有半毛钱关系,请不要把它们混淆了。
再提一句,Kcptun 和 Shadowsocks 没有任何关系,请不要脑补它们之间存在任何联系,Kcptun 你可以理解为一款网络加速软件,只不过它是通过将 TCP 协议转换为 UDP 协议,然后再通过大量的发送数据包,浪费了带宽以换取网速的提升。它能加速所有以 TCP 协议传输数据的软件,不单单是 Shadowsocks。只是大家都用来……你懂的
回到上面的密码设置问题,如果你这里选择直接回车,也就是代表你不自定义密码。但是 Kcptun 有一个默认的密码,这个密码是: it’s a secrect 。

¶禁用压缩

1
2
是否禁用数据压缩?
(默认: 不禁用) [y/n]:

Kcptun 默认是启用压缩的。如果你这里设置为 y,也就是配置为 nocomp:true,那么就是禁用压缩。
许多朋友这里设置的是保持默认(启用压缩),而偏偏在软件之中设置为禁用压缩,当然就连不上咯。
其他配置项不用我说了,如果你了解它是干什么的,可以自定义配置。如果不知道,那么直接回车使用默认参数。
但是,使用默认参数,是有可能浪费大量流量的,如果你想减少流量使用,你需要会调节参数:

  • 先将 client rcvwnd 和 server sndwnd 调到一个较小值;
  • 同时在两端逐步增大 client rcvwnd 和 server sndwnd ;
  • 尝试下载,观察如果带宽利用率(服务器+客户端两端都要观察)接近物理带宽则停止,否则跳转到第二步。

任何事物都是有两面性的,选择了速度,就只有放弃流量。
各参数详细信息请查看:https://github.com/xtaci/kcptun

安装成功,应该能看到如下输出信息:

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
恭喜, Kcptun 服务端配置完毕!

正在获取当前安装的 Kcptun 版本...

服务器IP: 10.10.10.10
端口: 29900
加速地址: 127.0.0.1:8388
密码: 123456
加密方式 Crypt: salsa20

当前安装的 Kcptun 版本为: v20160922

推荐的客户端配置为:
{
"localaddr": ":8388",
"remoteaddr": "10.10.10.10:29900",
"key": "123456",
"crypt": "salsa20",
"mode": "fast",
"mtu": 1350,
"sndwnd": 1024,
"rcvwnd": 1024,
"datashard": 10,
"parityshard": 3,
"dscp": 0,
"conn": 1,
"autoexpire": 60,
"nocomp": false
}

手机端参数可以使用:
*******

其他参数请自行计算或设置, 详细信息可以查看: https://github.com/xtaci/kcptun

Kcptun 安装目录: /usr/share/kcptun
Kcptun 日志文件目录: /var/log/kcptun/

请将以上的提示信息复制保存下来,后面配置客户端会用到这些提示信息。
注意:服务端的 sndwnd 对应的是客户端的 recwnd,所以请不要问我为什么输出的客户端参数和你配置的服务端参数是反的。
安装之后,Kcptun 服务交由 Supervisor 管理。

¶Supervisor 相关命令

1
service supervisord {start|stop|restart|status}

¶Kcptun 相关命令

1
supervisorctl {start|stop|restart|status} kcptun

Supervisor 启动的时候会同时启动 Kcptun,运行 kcptun 相关命令时先确保 Supervisor 已启动。

¶更新

./kcptun.sh update

Kcptun 客户端

¶先到下载一个启动 Kcptun 的工具。请注意,这只是用来启动 Kcptun 的工具,而不是 Kcptun 客户端。https://github.com/dfdragon/kcptun_gclient/releases

¶然后下载服务端对应版本的 Kcptun(保存下来的提示信息里有):

1
当前安装的 Kcptun 版本为: v20160922

https://github.com/xtaci/kcptun/releases
32位系统下载:kcptun-windows-386-20160922.tar.gz
64位系统下载:kcptun-windows-amd64-20160922.tar.gz
文件名带有 windows 的才是 Windows 版,dirwin 是给 Mac 用的,注意别下错了
注意看红字的版本号和服务端版本一致。然后将它们解压到一起:

1
2
3
kcptun_gclient.exe -- Kcptun 启动工具
client_windows_amd64.exe -- Kcptun 客户端程序
server_windows_amd64.exe -- Kcptun 服务端程序

打开 Kcptun 启动工具,界面如下,请按序号操作。

¶直接导入配置文件

我们可以将推荐参数保存为文件,找到如下这部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"localaddr": ":8388",
"remoteaddr": "10.10.10.10:29900",
"key": "123456",
"crypt": "salsa20",
"mode": "fast",
"mtu": 1350,
"sndwnd": 1024,
"rcvwnd": 1024,
"datashard": 10,
"parityshard": 3,
"dscp": 0,
"conn": 1,
"autoexpire": 60,
"nocomp": false
}

新建一个记事本文件,文件名随意(比如 config.txt 或者 config.json),然后将上面大括号里的内容复制到文件里边(包括大括号),确保它是标准的 json 格式
然后勾选“使用配置文件”,选择你新建的文件即可,下面的参数区域直接留空,点击启动。

¶手动配置参数

手动配置的时候只需要看保存下来的提示信息上面一部分(有标红部分,非常显眼):

1
2
3
4
服务器IP:  10.10.10.10
端口: 29900
加速地址: 127.0.0.1:8388
加密方式 Crypt: salsa20

为了规避错误,遵循较少配置原则,在配置服务端时没有修改过的选项都不需要配置。配置完毕,点击启动。
几项说明:

  • 本地监听端口,这个端口你可以随意设置,不是必须设置为 Shadowsocsk 的端口;
  • KCP服务器地址为你的服务器IP地址,不是 127.0.0.1,端口为服务端 Kcptun 的端口;
  • 如果你想使用IPv6协议,在填写服务器IP地址的时候需要用 [] 将IPv6地址括起来,如:[2000:0:0:0:0:0:0:1];
  • 通信密钥是你配置的 Kcptun 密码,不是 Shadowsocks 的密码;在配置 Kcptun 的时候,不用管 Shadowsocks 的配置参数;
  • 参数区配置的时候,只需要配置你修改过的部分就行了,其他部分都不用改,除非你了解每项参数的意义;
  • 日志区非常重要,在排查问题的时候,这是必看部分;
  • 右下角为 Kcptun 最低需求版本,更新服务端之后,也需要更新本地客户端,只需要替换客户端文件即可。

SSR配置Kcptun

在 Shadowsocks 客户端中添加一个选项,服务器IP固定填写 127.0.0.1,服务器端口填写 Kcptun 启动工具中配置的“本地监听端口”(即这里的 8388),密码和加密配置的是 Shadowsocks 的密码和加密。
基本原则,配置 Kcptun 的时候不用管 Shadowsocks 的参数,配置 Shadowsocks 的时候不用管 Kcptun 的参数,别把它们的配置参数搞混了。

安装魔改BBR

由于是官方BBR基础上的激进版本,所以优点与原版BBR基本一致,加速效果更为明显。
不支持OpenVZ架构的系统,不支持部分系统版本安装。

¶安装

¶Debian版:

1
wget --no-check-certificate https://github.com/tcp-nanqinlang/general/releases/download/3.4.2.1/tcp_nanqinlang-fool-1.3.0.sh
1
bash tcp_nanqinlang-fool-1.3.0.sh

¶CentOS版:

1
wget --no-check-certificate https://raw.githubusercontent.com/tcp-nanqinlang/general/master/General/CentOS/bash/tcp_nanqinlang-1.3.2.sh
1
bash tcp_nanqinlang-1.3.2.sh

出现下图提示时,输入数字1选择安装内核,然后回车:

接下来的安装过程中,部分系统可能会有如下提示,提示删除旧的内核,是否取消。
这时按方向右键,选择No,后回车,确认删除。

出现如下提示后,输入reboot回车重启系统:

系统重启完成后,重新连接,输入以下命令重新运行脚本:

¶Debian:

1
bash tcp_nanqinlang-fool-1.3.0.sh

¶Centos:

1
bash tcp_nanqinlang-1.3.2.sh

出现如下图提示后,输入2, 选择安装并开启算法:

稍等片刻,安装成功后的提示如下图:

魔改BBR卸载

Putty连接VPS服务器,运行如下命令:

¶Debian

1
bash tcp_nanqinlang-fool-1.3.0.sh

¶CentOS

1
bash tcp_nanqinlang-1.3.2.sh

出现下图提示后,选择4进行卸载:

卸载完成后重启。
注意:此卸载仅卸载算法,并不卸载内核。

安装adbyby

¶安装

系统要求:CentOS 6+/Debian 6+/Ubuntu 14.04 +,推荐Debian 7 x64。
执行下面的代码下载并运行脚本:

1
wget -N --no-check-certificate https://raw.githubusercontent.com/ToyoDAdoubiBackup/doubi/master/adbyby.sh && chmod +x adbyby.sh && bash adbyby.sh


运行脚本后会出现脚本操作菜单,选择并输入1就会开始安装。

¶其他操作

启动:service adbyby start
停止:service adbyby stop
重启:service adbyby restart
查看状态:service adbyby status
安装目录:/usr/local/adbyby
配置文件:/usr/local/adbyby/bin/adhook.ini

¶其他说明

ADbyby脚本支持开机启动。
ADbyby的广告过滤效果取决于广告过滤规则的完善程度,默认的规则并不是很全,所以想要更好的过滤效果请自行添加 广告过滤规则URL!

¶添加广告过滤/屏蔽规则URL

默认的配置文件,我已经加上了四个URL规则,ADbyby会自动定时更新的,如果你想要添加或者删除这些URL规则,那么可以运行脚本后选择6选项。
然后就会打开配置文件,在最后的[exrule]项目下面添加/删除广告过滤URL规则即可,一行一个。
脚本中我是用vim编辑器打开配置文件的,打开后按I键即可进入编辑模式,然后就可以修改了(注意不要使用键盘中的小键盘)。
修改后,按ESC键退出编辑模式,然后输入:wq保存并退出,如果不想保存,那就:q!不保存强行退出。
以下这些广告过滤规则URL,可以根据需求添加进去:

1
2
3
4
5
6
7
https://opt.cn2qq.com/opt-file/video.txt
https://opt.cn2qq.com/opt-file/lazy.txt
https://easylist-downloads.adblockplus.org/easylistchina.txt
https://easylist.to/easylist/easylist.txt
https://easylist-downloads.adblockplus.org/easyprivacy.txt
https://easylist-downloads.adblockplus.org/malwaredomains_full.txt
https://raw.githubusercontent.com/xinggsf/Adblock-Plus-Rule/master/ABP-FX.txt

spring - IOC高级特性

发表于 2019-10-22 | 更新于 2019-11-17 | 分类于 Java | 评论数: | 阅读次数:

介绍

Spring IOC 容器对 Bean 定
义资源的定位、读入和解析过程,同时也清楚了当用户通过 getBean 方法向 IOC 容器获取被管理的 Bean
时,IOC 容器对 Bean 进行的初始化和依赖注入过程,这些是 Spring IOC 容器的基本功能特性。
Spring IOC 容器还有一些高级特性,如使用 lazy-init 属性对 Bean 预初始化、FactoryBean 产生或者
修饰 Bean 对象的生成、IOC 容器初始化 Bean 过程中使用 BeanPostProcessor 后置处理器对 Bean 声明周
期事件管理和 IOC 容器的 autowiring 自动装配功能等。

Spring IOC 容器的 lazy-init 属性实现预实例化:

IOC 容器的初始化过程就是对 Bean 定义
资源的定位、载入和注册,此时容器对 Bean 的依赖注入并没有发生,依赖注入主要是在应用程序第一
次向容器索取 Bean 时,通过 getBean 方法的调用完成。

当 Bean 定义资源的元素中配置了 lazy-init 属性时,容器将会在初始化的时候对所配置的 Bean
进行预实例化,Bean 的依赖注入在容器初始化的时候就已经完成。这样,当应用程序第一次向容器索取
被管理的 Bean 时,就不用再初始化和对 Bean 进行依赖注入了,直接从容器中获取已经完成依赖注入的
现成 Bean,可以提高应用第一次向容器获取 Bean 的性能。

下面我们通过代码分析容器预实例化的实现过程:

¶refresh()

先从 IOC 容器的初始会过程开始,通过前面文章分析,我们知道 IOC 容器读入已经定位的 Bean 定义资
源是从 refresh 方法开始的,我们首先从 AbstractApplicationContext 类的 refresh 方法入手分析,
源码如下:

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
// 容器初始化的过程,读入 Bean 定义资源,并解析注册
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识.
prepareRefresh();

//告诉子类启动 refreshBeanFactory()方法,Bean 定义资源文件的载入从子类的 refreshBeanFactory()方法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

//为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);

try {
//以下三个方法其实都是加载一些所谓的监听器(ApplicationAware/SessionAware等等)

// Allows post-processing of the bean factory in context subclasses.
//为容器的某些子类指定特殊的 BeanPost 事件处理器
//比如监听spring是否启动,ApplicationAware中的setApplicationContext方法就可以进行一些容器初始化事件操作
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
//调用所有注册的 BeanFactoryPostProcessor 的 Bean
invokeBeanFactoryPostProcessors(beanFactory);

//为 BeanFactory 注册 BeanPost 事件处理器.
//BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

//初始化信息源,和国际化相关.
// Initialize message source for this context.
initMessageSource();

//初始化容器事件传播器.
// Initialize event multicaster for this context.
initApplicationEventMulticaster();

//调用子类的某些特殊 Bean 初始化方法
// Initialize other special beans in specific context subclasses.
onRefresh();

//为事件传播器注册事件监听器.
// Check for listener beans and register them.
registerListeners();

//初始化所有剩余的单例 Bean.
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
//销毁以创建的单态 Bean
// Destroy already created singletons to avoid dangling resources.
destroyBeans();

//取消 refresh 操作,重置容器的同步标识.
// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}
}
}

在 refresh 方法中 ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();
启动了 Bean 定义资源的载入、注册过程,而 finishBeanFactoryInitialization 方法是对注册后的 Bean
定义中的预实例化(lazy-init=false,Spring 默认就是预实例化,即为 true)的 Bean 进行处理的地方。

¶finishBeanFactoryInitialization 处理预实例化 Bean

当Bean定义资源被载入IOC容器之后,容器将Bean定义资源解析为容器内部的数据结构BeanDefinition
注册到容器中,AbstractApplicationContext 类中的 finishBeanFactoryInitialization 方法对配置了
预实例化属性的 Bean 进行预初始化过程,源码如下:

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
//对配置了 lazy-init 属性的 Bean 进行预实例化处理
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
//这是 Spring3 以后新加的代码,为容器指定一个转换服务(ConversionService)
//在对某些 Bean 属性进行转换时使用
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}

// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}

// Stop using the temporary ClassLoader for type matching.
//为了类型匹配,停止使用临时的类加载器
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
//缓存容器中所有注册的 BeanDefinition 元数据,以防被修改
beanFactory.freezeConfiguration();

// Instantiate all remaining (non-lazy-init) singletons.
//对配置了 lazy-init 属性的单态模式 Bean 进行预实例化处理
beanFactory.preInstantiateSingletons();
}

ConfigurableListableBeanFactory 是一个接口,其 preInstantiateSingletons 方法由其子类
DefaultListableBeanFactory 提供。

¶DefaultListableBeanFactory 对配置 lazy-init 属性单态 Bean 的预实例化

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
//对配置 lazy-init 属性单态 Bean 的预实例化
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}
List<String> beanNames;
//在对配置 lazy-init 属性单态 Bean 的预实例化过程中,必须多线程同步,以确保数据一致性
synchronized (this.beanDefinitionMap) {
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
beanNames = new ArrayList<String>(this.beanDefinitionNames);
}
for (String beanName : beanNames) {
//获取指定名称的 Bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//Bean 不是抽象的,是单态模式的,且 lazy-init 属性配置为 false
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//如果指定名称的 bean 是创建容器的 Bean
if (isFactoryBean(beanName)) {
//FACTORY_BEAN_PREFIX=”&”,当 Bean 名称前面加”&”符号
//时,获取的是产生容器对象本身,而不是容器产生的 Bean.
//调用 getBean 方法,触发容器对 Bean 实例化和依赖注入过程
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
//标识是否需要预实例化
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
//一个匿名内部类
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
//调用 getBean 方法,触发容器对 Bean 实例化和依赖注入过程
getBean(beanName);
}
}
else {
//调用 getBean 方法,触发容器对 Bean 实例化和依赖注入过程
getBean(beanName);
}
}
}
}

通过对 lazy-init 处理源码的分析,我们可以看出,如果设置了 lazy-init 属性,则容器在完成 Bean
定义的注册之后,会通过 getBean 方法,触发对指定 Bean 的初始化和依赖注入过程,这样当应用第一
次向容器索取所需的 Bean 时,容器不再需要对 Bean 进行初始化和依赖注入,直接从已经完成实例化和
依赖注入的 Bean 中取一个现成的 Bean,这样就提高了第一次获取 Bean 的性能。

FactoryBean 的实现

在 Spring 中,有两个很容易混淆的类:BeanFactory 和 FactoryBean。
BeanFactory:Bean 工厂,是一个工厂(Factory),我们 Spring IOC 容器的最顶层接口就是这个
BeanFactory,它的作用是管理 Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依
赖。

FactoryBean:工厂 Bean,是一个 Bean,作用是产生其他 bean 实例。通常情况下,这种 bean 没有什么
特别的要求,仅需要提供一个工厂方法,该方法用来返回其他 bean 实例。通常情况下,bean 无须自己
实现工厂模式,Spring 容器担任工厂角色;但少数情况下,容器中的 bean 本身就是工厂,其作用是产
生其它 bean 实例。

当用户使用容器本身时,可以使用转义字符”&”来得到 FactoryBean 本身,以区别通过 FactoryBean
产生的实例对象和 FactoryBean 对象本身。在 BeanFactory 中通过如下代码定义了该转义字符:
StringFACTORY_BEAN_PREFIX = “&”;

如果 myJndiObject 是一个 FactoryBean,则使用&myJndiObject 得到的是 myJndiObject 对象,而不是
myJndiObject 产生出来的对象。

¶FactoryBean 的源码如下

1
2
3
4
5
6
7
8
9
10
//工厂 Bean,用于产生其他对象
public interface FactoryBean<T> {
//获取容器管理的对象实例
T getObject() throws Exception;
//获取 Bean 工厂创建的对象的类型
Class<?> getObjectType();
//Bean 工厂创建的对象是否是单态模式,如果是单态模式,则整个容器中只有一个实例
//对象,每次请求都返回同一个实例对象
boolean isSingleton();
}

¶AbstractBeanFactory 的 getBean 方法调用 FactoryBean

在前面我们分析 Spring IOC 容器实例化 Bean 并进行依赖注入过程的源码时,提到在 getBean 方法触
发容器实例化 Bean 的时候会调用 AbstractBeanFactory 的 doGetBean 方法来进行实例化的过程,源码
如下

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//真正实现向 IOC 容器获取 Bean 的功能,也是触发依赖注入功能的地方
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
//根据指定的名称获取被管理 Bean 的名称,剥离指定名称中对容器的相关依赖
//如果指定的是别名,将别名转换为规范的 Bean 名称
final String beanName = transformedBeanName(name);
Object bean;

// Eagerly check singleton cache for manually registered singletons.
//先从缓存中取是否已经有被创建过的单态类型的 Bean
//对于单例模式的 Bean 整个 IOC 容器中只创建一次,不需要重复创建
Object sharedInstance = getSingleton(beanName);
//IOC 容器创建单例模式 Bean 实例对象
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
//如果指定名称的 Bean 在容器中已有单例模式的 Bean 被创建
//直接返回已经创建的 Bean
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//获取给定 Bean 的实例对象,主要是完成 FactoryBean 的相关处理
//注意:BeanFactory 是管理容器中 Bean 的工厂,而 FactoryBean 是创建创建对象的工厂 Bean,两者之间有区别
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
//缓存没有正在创建的单例模式 Bean
//缓存中已经有已经创建的原型模式 Bean
//但是由于循环引用的问题导致实例化对象失败
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

// Check if bean definition exists in this factory.
//对 IOC 容器中是否存在指定名称的 BeanDefinition 进行检查,首先检查是否
//能在当前的 BeanFactory 中获取的所需要的 Bean,如果不能则委托当前容器
//的父级容器去查找,如果还是找不到则沿着容器的继承体系向父级容器查找
// 为什么要委托父容器去找呢?
// ioc容器是可以被关联的:FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent)
BeanFactory parentBeanFactory = getParentBeanFactory();
//当前容器的父级容器存在,且当前容器中不存在指定名称的 Bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
//解析指定 Bean 名称的原始名称
// 因为有可能是通过别名去获取bean
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
//委派父级容器根据指定名称和显式的参数查找
// 为什么不传类型再去校验一次?
// 因为这一步是强转成T , 如果类型不一样, 在后续的调用中会报错ClassCastException
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
//委派父级容器根据指定名称和类型查找
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
//创建的 Bean 是否需要进行类型验证,一般不需要
if (!typeCheckOnly) {
//向容器标记指定的 Bean 已经被创建
markBeanAsCreated(beanName);
}

try {
//根据指定 Bean 名称获取其父级的 Bean 定义
//主要解决 Bean 继承时子类合并父类公共属性问题
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// Guarantee initialization of beans that the current bean depends on.
//获取当前 Bean 所有依赖 Bean 的名称
String[] dependsOn = mbd.getDependsOn();
//如果当前 Bean 有依赖 Bean
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
//递归调用 getBean 方法,获取当前 Bean 的依赖 Bean
getBean(dependsOnBean);
//把被依赖 Bean 注册给当前依赖的 Bean
registerDependentBean(dependsOnBean, beanName);
}
}

// Create bean instance.
//创建单例模式 Bean 的实例对象
if (mbd.isSingleton()) {
//这里使用了一个匿名内部类,创建 Bean 实例对象,并且注册给所依赖的对象
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
//创建一个指定 Bean 实例对象,如果有父级继承,则合并子类和父类的定义
// AbstractAutowireCapableBeanFactory 中实现了该方法
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
//显式地从容器单例模式 Bean 缓存中清除实例对象
// 其实就是从各种map中吧这个对象给remove掉
destroySingleton(beanName);
throw ex;
}
}
});
//获取给定 Bean 的实例对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//IOC 容器创建原型模式 Bean 实例对象
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
//原型模式(Prototype)是每次都会创建一个新的对象
Object prototypeInstance = null;
try {
//回调 beforePrototypeCreation 方法,默认的功能是注册当前创建的原型对象
beforePrototypeCreation(beanName);
//创建指定 Bean 对象实例
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//回调 afterPrototypeCreation 方法,默认的功能告诉 IoC 容器指定 Bean 的原型对象不再创建了
//就是该bean已经从ing状态变成了ed状态
afterPrototypeCreation(beanName);
}
//获取给定 Bean 的实例对象
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
//要创建的 Bean 既不是单例模式,也不是原型模式,则根据 Bean 定义资源中
//配置的生命周期范围,选择实例化 Bean 的合适方法,这种在 Web 应用程序中
//比较常用,如:request、session、application 等生命周期
else {
String scopeName = mbd.getScope();
//其实scopt也是用map去管理的, 这样的很多地方就能理解通了
final Scope scope = this.scopes.get(scopeName);
//Bean 定义资源中没有配置生命周期范围,则 Bean 定义不合法
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
//这里又使用了一个匿名内部类,获取一个指定生命周期范围的实例
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
// AbstractAutowireCapableBeanFactory 中实现了该方法
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
//获取给定 Bean 的实例对象
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; " +
"consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}

// Check if required type matches the type of the actual bean instance.
//对创建的 Bean 实例对象进行类型检查
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type [" +
ClassUtils.getQualifiedName(requiredType) + "]", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
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
//获取给定 Bean 的实例对象,主要是完成 FactoryBean 的相关处理
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

//容器已经得到了 Bean 实例对象,这个实例对象可能是一个普通的 Bean,
//也可能是一个工厂 Bean,如果是一个工厂 Bean,则使用它创建一个 Bean 实例对象,
//如果调用本身就想获得一个容器的引用,则指定返回这个工厂 Bean 实例对象
//如果指定的名称是容器的解引用(dereference,即是对象本身而非内存地址),
//且 Bean 实例也不是创建 Bean 实例对象的工厂 Bean
// Don't let calling code try to dereference the factory if the bean isn't a factory.
// name 是否是工厂bean标记
// beanInstance 是否继承factoryBean
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}

// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
// 1、true || false 普通类 纯的普通类
// 2、false || true 工厂类 纯的工厂
// 3、false || false 普通类 没有定义为工厂的普通工厂类
//如果 Bean 实例不是工厂 Bean,或者指定名称是容器的解引用,
//调用者向获取对容器的引用,则直接返回当前的 Bean 实例
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
//处理指定名称不是容器的解引用,或者根据名称获取的 Bean 实例对象是一个工厂 Bean
//使用工厂 Bean 创建一个 Bean 的实例对象
Object object = null;
if (mbd == null) {
// 真正的ioc容器
//从 Bean 工厂缓存中获取给定名称的 Bean 实例对象
object = getCachedObjectForFactoryBean(beanName);
}
//让 Bean 工厂生产给定名称的 Bean 对象实例
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
//如果从 Bean 工厂生产的 Bean 是单态模式的,则缓存
if (mbd == null && containsBeanDefinition(beanName)) {
//从容器中获取指定名称的 Bean 定义,如果继承基类,则合并基类相关属性
mbd = getMergedLocalBeanDefinition(beanName);
}
// 是否是合成的,意思就是是否当做工具类来使用
// 这里的synthetic实际上是为了给用户自定义一些BeanDefinition注册到容器中以当作工具类来使用。
// 什么是synthetic
// 就是内部类,java在编译的时候内部类也会编译成单独的一个文件,那实际上,原始类及时两个类的合成类
// 工具类就没有必要做一些封装、代理等工作,实际上是拿来就用,没有其他处理
// 对于synthetic类型的BeanDefinition,getObjectFromFactoryBean中是不会对FactoryBean生成的bean用post-processor进行后置处理的。
// 后置处理的实现是在AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean中,
// 它会调用容器中的BeanPostProcessor.postProcessAfterInitialization,这里提供了一个扩展点对FactoryBean生成的bean进行封装,代理等
//如果从容器得到 Bean 定义信息,并且 Bean 定义信息不是虚构的,
//则让工厂 Bean 生产 Bean 实例对象
boolean synthetic = (mbd != null && mbd.isSynthetic());
// bean实例化的缓存
//调用 FactoryBeanRegistrySupport 类的 getObjectFromFactoryBean 方法,
//实现工厂 Bean 生产 Bean 对象实例的过程
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}

在上面获取给定 Bean 的实例对象的 getObjectForBeanInstance 方法中,会调用
FactoryBeanRegistrySupport 类的 getObjectFromFactoryBean 方法,该方法实现了 Bean 工厂生产 Bean
实例对象。

Dereference(解引用):一个在 C/C中应用比较多的术语,在 C中,*是解引用符号,而&是
引用符号,解引用是指变量指向的是所引用对象的本身数据,而不是引用对象的内存地址。

¶AbstractBeanFactory 生产 Bean 实例对象

AbstractBeanFactory 类中生产 Bean 实例对象的主要源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Bean 工厂生产 Bean 实例对象
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
//Bean 工厂是单态模式,并且 Bean 工厂缓存中存在指定名称的 Bean 实例对象
if (factory.isSingleton() && containsSingleton(beanName)) {
//多线程同步,以防止数据不一致
synchronized (getSingletonMutex()) {
//直接从 Bean 工厂缓存中获取指定名称的 Bean 实例对象
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
//调用 Bean 工厂的 getObject 方法生产指定 Bean 的实例对象
object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
//将生产的实例对象添加到 Bean 工厂缓存中
this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
}
return (object != NULL_OBJECT ? object : null);
}
}
//调用 Bean 工厂的 getObject 方法生产指定 Bean 的实例对象
else {
return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
}
}
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
//调用 Bean 工厂的 getObject 方法生产指定 Bean 的实例对象
private Object doGetObjectFromFactoryBean(
final FactoryBean<?> factory, final String beanName, final boolean shouldPostProcess)
throws BeanCreationException {

Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
//实现 PrivilegedExceptionAction 接口的匿名内置类
//根据 JVM 检查权限,然后决定 BeanFactory 创建实例对象
object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
//调用 BeanFactory 接口实现类的创建对象方法
return factory.getObject();
}
}, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//调用 BeanFactory 接口实现类的创建对象方法
object = factory.getObject();
}
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}


// Do not accept a null value for a FactoryBean that's not fully
// initialized yet: Many FactoryBeans just return null then.
//创建出来的实例对象为 null,或者因为单态对象正在创建而返回 null
if (object == null && isSingletonCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(
beanName, "FactoryBean which is currently in creation returned null from getObject");
}
//为创建出来的 Bean 实例对象添加 BeanPostProcessor 后置处理器
if (object != null && shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex);
}
}

return object;
}

从上面的源码分析中,我们可以看出,BeanFactory 接口调用其实现类的 getObject 方法来实现创建 Bean
实例对象的功能

¶工厂 Bean 的实现类 getObject 方法创建 Bean 实例对象

FactoryBean的实现类有非常多,比如:Proxy、RMI、JNDI、ServletContextFactoryBean等等,FactoryBean
接口为 Spring 容器提供了一个很好的封装机制,具体的 getObject 有不同的实现类根据不同的实现策
略来具体提供,我们分析一个最简单的 AnnotationTestFactoryBean 的实现源码:

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
public class AnnotationTestBeanFactory implements FactoryBean<FactoryCreatedAnnotationTestBean> {

private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean();

public AnnotationTestBeanFactory() {
this.instance.setName("FACTORY");
}

@Override
//AnnotationTestBeanFactory 产生 Bean 实例对象的实现
public FactoryCreatedAnnotationTestBean getObject() throws Exception {
return this.instance;
}

@Override
public Class<? extends IJmxTestBean> getObjectType() {
return FactoryCreatedAnnotationTestBean.class;
}

@Override
public boolean isSingleton() {
return true;
}

}

其他的 Proxy,RMI,JNDI 等等,都是根据相应的策略提供 getObject 的实现。这里不做一一分析,这
已经不是 Spring 的核心功能,有需要的时候再去深入研究。

BeanPostProcessor 后置处理器的实现

BeanPostProcessor 后置处理器是 Spring IOC 容器经常使用到的一个特性,这个 Bean 后置处理器是一
个监听器,可以监听容器触发的 Bean 声明周期事件。后置处理器向容器注册以后,容器中管理的 Bean
就具备了接收 IOC 容器事件回调的能力。

BeanPostProcessor 的使用非常简单,只需要提供一个实现接口 BeanPostProcessor 的实现类,然后在
Bean 的配置文件中设置即可。

¶BeanPostProcessor 的源码如下

1
2
3
4
5
6
7
8
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanPostProcessor {
//为在 Bean 的初始化前提供回调入口
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//为在 Bean 的初始化之后提供回调入口
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

这两个回调的入口都是和容器管理的 Bean 的生命周期事件紧密相关,可以为用户提供在 Spring IOC
容器初始化 Bean 过程中自定义的处理操作。

¶AbstractAutowireCapableBeanFactory 类对容器生成的 Bean 添加后置处理器

BeanPostProcessor后置处理器的调用发生在Spring IOC容器完成对Bean实例对象的创建和属性的依
赖注入完成之后,在对 Spring 依赖注入的源码分析过程中我们知道,当应用程序第一次调用 getBean
方法(lazy-init 预实例化除外)向 Spring IOC 容器索取指定 Bean 时触发 Spring IOC 容器创建 Bean
实例对象并进行依赖注入的过程,其中真正实现创建 Bean 对象并进行依赖注入的方法是
AbstractAutowireCapableBeanFactory 类的 doCreateBean 方法,主要源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//真正创建 Bean 的方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
//创建 Bean 实例对象
……
try {
//对 Bean 属性进行依赖注入
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
//在对 Bean 实例对象生成和依赖注入完成以后,开始对 Bean 实例对象
//进行初始化 ,为 Bean 实例对象应用 BeanPostProcessor 后置处理器
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException)
ex).getBeanName())) {
throw (BeanCreationException) ex;
}
……
//为应用返回所需要的实例对象
return exposedObject;
}

从上面的代码中我们知道,为 Bean 实例对象添加 BeanPostProcessor 后置处理器的入口的是
initializeBean 方法。

¶initializeBean 方法为容器产生的 Bean 实例对象添加 BeanPostProcessor 后置处理器

同样在 AbstractAutowireCapableBeanFactory 类中,initializeBean 方法实现为容器创建的 Bean 实例
对象添加 BeanPostProcessor 后置处理器,源码如下:

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
//初始容器创建的 Bean 实例对象,为其添加 BeanPostProcessor 后置处理器
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
//JDK 的安全机制验证权限
if (System.getSecurityManager() != null) {
//实现 PrivilegedAction 接口的匿名内部类
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
//为 Bean 实例对象包装相关属性,如名称,类加载器,所属容器等信息
invokeAwareMethods(beanName, bean);
}

Object wrappedBean = bean;
//对 BeanPostProcessor 后置处理器的 postProcessBeforeInitialization
//回调方法的调用,为 Bean 实例初始化前做一些处理
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
//调用 Bean 实例对象初始化的方法,这个初始化方法是在 Spring Bean 定义配置
//文件中通过 init-method 属性指定的
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
//对 BeanPostProcessor 后置处理器的 postProcessAfterInitialization
//回调方法的调用,为 Bean 实例初始化之后做一些处理
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
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
//调用 BeanPostProcessor 后置处理器实例对象初始化之前的处理方法
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
//遍历容器为所创建的 Bean 添加的所有 BeanPostProcessor 后置处理器
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
//调用 Bean 实例所有的后置处理中的初始化前处理方法,为 Bean 实例对象在
//初始化之前做一些自定义的处理操作
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}

//调用 BeanPostProcessor 后置处理器实例对象初始化之后的处理方法
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
//遍历容器为所创建的 Bean 添加的所有 BeanPostProcessor 后置处理器
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
//调用 Bean 实例所有的后置处理中的初始化后处理方法,为 Bean 实例对象在
//初始化之后做一些自定义的处理操作
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}

BeanPostProcessor 是一个接口,其初始化前的操作方法和初始化后的操作方法均委托其实现子类来实
现,在 Spring 中,BeanPostProcessor 的实现子类非常的多,分别完成不同的操作,如:AOP 面向切面
编程的注册通知适配器、Bean 对象的数据校验、Bean 继承属性/方法的合并等等,我们以最简单的 AOP
切面织入来简单了解其主要的功能。

¶AdvisorAdapterRegistrationManager 在 Bean 对象初始化后注册通知适配器

AdvisorAdapterRegistrationManager 是 BeanPostProcessor 的一个实现类,其主要的作用为容器中管
理的 Bean 注册一个面向切面编程的通知适配器,以便在 Spring 容器为所管理的 Bean 进行面向切面编
程时提供方便,其源码如下:

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
//为容器中管理的 Bean 注册一个面向切面编程的通知适配器
public class AdvisorAdapterRegistrationManager implements BeanPostProcessor {
//容器中负责管理切面通知适配器注册的对象
private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();


/**
* Specify the AdvisorAdapterRegistry to register AdvisorAdapter beans with.
* Default is the global AdvisorAdapterRegistry.
* @see GlobalAdvisorAdapterRegistry
*/
public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) {
this.advisorAdapterRegistry = advisorAdapterRegistry;
}

//BeanPostProcessor 在 Bean 对象初始化前的操作
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//没有做任何操作,直接返回容器创建的 Bean 对象
return bean;
}
//BeanPostProcessor 在 Bean 对象初始化后的操作
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AdvisorAdapter){
//如果容器创建的 Bean 实例对象是一个切面通知适配器,则向容器的注册
this.advisorAdapterRegistry.registerAdvisorAdapter((AdvisorAdapter) bean);
}
return bean;
}

}

其他的 BeanPostProcessor 接口实现类的也类似,都是对 Bean 对象使用到的一些特性进行处理,或者
向 IOC 容器中注册,为创建的 Bean 实例对象做一些自定义的功能增加,这些操作是容器初始化 Bean 时
自动触发的,不需要认为的干预。

Spring IOC 容器 autowiring 实现原理

Spring IOC 容器提供了两种管理 Bean 依赖关系的方式:

  • 显式管理:通过 BeanDefinition 的属性值和构造方法实现 Bean 依赖关系管理。
  • autowiring:Spring IOC 容器的依赖自动装配功能,不需要对 Bean 属性的依赖关系做显式的声明,只需要在配置好 autowiring 属性,IOC 容器会自动使用反射查找属性的类型和名称,然后基于属性的类型或者名称来自动匹配容器中管理的 Bean,从而自动地完成依赖注入。

通过对 autowiring 自动装配特性的理解,我们知道容器对 Bean 的自动装配发生在容器对 Bean 依赖注
入的过程中。在前面对 Spring IOC 容器的依赖注入过程源码分析中,我们已经知道了容器对 Bean 实
例对象的属性注入的处理发生在 AbstractAutoWireCapableBeanFactory 类中的 populateBean 方法中,
我们通过程序流程分析 autowiring 的实现原理

¶AbstractAutoWireCapableBeanFactory 对 Bean 实例进行属性依赖注入

应用第一次通过 getBean 方法(配置了 lazy-init 预实例化属性的除外)向 IoC 容器索取 Bean 时,容器
创建 Bean 实例对象,并且对 Bean 实例对象进行属性依赖注入,AbstractAutoWireCapableBeanFactory
的 populateBean 方法就是实现 Bean 属性依赖注入的功能,其主要源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) {
//获取 Bean 定义的属性值,并对属性值进行处理
PropertyValues pvs = mbd.getPropertyValues();
……
//对依赖注入处理,首先处理 autowiring 自动装配的依赖注入
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {

MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
//根据 Bean 名称进行 autowiring 自动装配处理
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
//根据 Bean 类型进行 autowiring 自动装配处理
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
}
//对非 autowiring 的属性进行依赖注入处理
……
}

¶Spring IOC 容器根据 Bean 名称或者类型进行 autowiring 自动依赖注入:

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
//根据名称对属性进行自动依赖注入
protected void autowireByName(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
//对 Bean 对象中非简单属性(不是简单继承的对象,如 8 中原始类型,字符串,URL 等都是简单属性)进行处理
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
//如果 Spring IOC 容器中包含指定名称的 Bean
if (containsBean(propertyName)) {
//使用当前Bean的属性名,在IoC容器中获取对应的bean,让将获取的bean设置为当前的Bean的属性值
//调用 getBean 方法向 IoC 容器索取指定名称的 Bean 实例,迭代触发属性的初始化和依赖注入
Object bean = getBean(propertyName);
//为指定名称的属性赋予属性值
pvs.add(propertyName, bean);
//指定名称属性注册依赖 Bean 名称,进行属性依赖注入
registerDependentBean(propertyName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Added autowiring by name from bean name '" + beanName +
"' via property '" + propertyName + "' to bean named '" + propertyName + "'");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
"' by name: no matching bean found");
}
}
}
}


//根据类型对属性进行自动依赖注入
protected void autowireByType(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
//获取用户定义的类型转换器
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
//存放解析的要注入的属性
Set<String> autowiredBeanNames = new LinkedHashSet<String>(4);
//对 Bean 对象中非简单属性(不是简单继承的对象,如 8 中原始类型,字符
//URL 等都是简单属性)进行处理
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
try {
//获取指定属性名称的属性描述器
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
// Don't try autowiring by type for type Object: never makes sense,
// even if it technically is a unsatisfied, non-simple property.
//不对 Object 类型的属性进行 autowiring 自动依赖注入
if (!Object.class.equals(pd.getPropertyType())) {
//获取属性的 setter 方法
MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
// Do not allow eager init for type matching in case of a prioritized post-processor.
//检查指定类型是否可以被转换为目标对象的类型
boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass());
//创建一个要被注入的依赖描述
DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
//根据容器的 Bean 定义解析依赖关系,返回所有要被注入的 Bean 对象
Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
if (autowiredArgument != null) {
//为属性赋值所引用的对象
pvs.add(propertyName, autowiredArgument);
}
for (String autowiredBeanName : autowiredBeanNames) {
//指定名称属性注册依赖 Bean 名称,进行属性依赖注入
registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
propertyName + "' to bean named '" + autowiredBeanName + "'");
}
}
//释放已自动注入的属性
autowiredBeanNames.clear();
}
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
}
}
}

通过上面的源码分析,我们可以看出来通过属性名进行自动依赖注入的相对比通过属性类型进行自动依
赖注入要稍微简单一些,但是真正实现属性注入的是 DefaultSingletonBeanRegistry 类的
registerDependentBean 方法。

¶DefaultSingletonBeanRegistry 的 registerDependentBean 方法对属性注入

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
//为指定的 Bean 注入依赖的 Bean
public void registerDependentBean(String beanName, String dependentBeanName) {
//处理 Bean 名称,将别名转换为规范的 Bean 名称
String canonicalName = canonicalName(beanName);
// 翻译一下就是:set里的bean都要依赖 key
//先从容器中:bean 名称-->全部依赖 Bean 名称集合找查找给定名称 Bean 的依赖 Bean
//多线程同步,保证容器内数据的一致性
synchronized (this.dependentBeanMap) {
//获取给定名称 Bean 的所有依赖 Bean 名称
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
//为 Bean 设置依赖 Bean 信息
dependentBeans = new LinkedHashSet<String>(8);
this.dependentBeanMap.put(canonicalName, dependentBeans);
}
//向容器中:bean 名称-->全部依赖 Bean 名称集合添加 Bean 的依赖信息
//即,将 Bean 所依赖的 Bean 添加到容器的集合中
dependentBeans.add(dependentBeanName);
}
// 翻译一下就是:key需要依赖set中的bean
//从容器中:bean 名称-->指定名称 Bean 的依赖 Bean 集合找查找给定名称 Bean 的依赖 Bean
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
if (dependenciesForBean == null) {
dependenciesForBean = new LinkedHashSet<String>(8);
this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
}
//向容器中:bean 名称-->指定 Bean 的依赖 Bean 名称集合添加 Bean 的依赖信息
//即,将 Bean 所依赖的 Bean 添加到容器的集合中
dependenciesForBean.add(canonicalName);
}
}

通过对 autowiring 的源码分析,我们可以看出,autowiring 的实现过程:

  • 对 Bean 的属性代调用 getBean 方法,完成依赖 Bean 的初始化和依赖注入。
  • 将依赖 Bean 的属性引用设置到被依赖的 Bean 属性上。
  • 将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IOC 容器的集合中。

Spring IOC 容器的 autowiring 属性自动依赖注入是一个很方便的特性,可以简化开发时的配置,但是凡是都有两面性,
自动属性依赖注入也有不足,首先,Bean 的依赖关系在配置文件中无法很清楚地看出来,对于维护造成一定困难。其
次,由于自动依赖注入是 Spring 容器自动执行的,容器是不会智能判断的,如果配置不当,将会带来无法预料的后果,
所以自动依赖注入特性在使用时还是综合考虑。

软件架构设计的七大原则

发表于 2019-10-22 | 更新于 2019-10-31 | 分类于 Java | 评论数: | 阅读次数:

开闭原则

开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对
扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调
的是用抽象构建框架,用实现扩展细节。可以提高软件系统的可复用性及可维护性。开
闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统,
例如:我们版本更新,我尽可能不修改源代码,但是可以增加新功能。
在现实生活中对于开闭原则也有体现。比如,很多互联网公司都实行弹性制作息时间,
规定每天工作 8 小时。意思就是说,对于每天工作 8 小时这个规定是关闭的,但是你什
么时候来,什么时候走是开放的。早来早走,晚来晚走。

实现开闭原则的核心思想就是面向抽象编程,接下来我们来看一段代码
首先创建一个课程接口 ICourse:

1
2
3
4
5
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}

整个课程生态有 Java 架构、大数据、人工智能、前端、软件测试等,我们来创建一个 Java
架构课程的类 JavaCourse:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JavaCourse implements ICourse{
private Integer Id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
this.Id = id;
this.name = name;
this.price = price;
}
public Integer getId() {
return this.Id;
}
public String getName() {
return this.name;
}
public Double getPrice() {
return this.price;
}
}

现在我们要给 Java 架构课程做活动,价格优惠。如果修改 JavaCourse 中的 getPrice()
方法,则会存在一定的风险,可能影响其他地方的调用结果。我们如何在不修改原有代
码前提前下,实现价格优惠这个功能呢?现在,我们再写一个处理优惠逻辑的类,
JavaDiscountCourse 类:

1
2
3
4
5
6
7
8
9
10
11
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getOriginPrice(){
return super.getPrice();
}
public Double getPrice(){
return super.getPrice() * 0.61;
}
}

回顾一下,简单一下类结构图:

依赖倒置原则

常用的设计模式

发表于 2019-10-22 | 更新于 2019-11-08 | 分类于 Java | 评论数: | 阅读次数:

代理模式

关心的执行的过程
主语对象是被代理对象(就是被调方)
或者说代理模式代理的开头和结尾,因为可以动态的

委派模式

区别于代理模式
关心的是结果,不关心过程
主语对象是委托人(就是调用方)
或者说委派模式代理的中间过程,因为中间过程可以是动态的


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
public interface IExector {
void doing();
}

public class ExectorA implements IExector {
@Override
public void doing() {
System.out.println("A开始执行任务");
}
}

public class ExectorB implements IExector{
@Override
public void doing() {
System.out.println("B开始执行任务");
}
}

public class Dispatcher implements IExector{
IExector exector;
Dispatcher(IExector exector){
this.exector = exector;
}
public void doing() {
this.exector.doing();
}
}

工厂模式

主语对象是公共的方法或公共的行为 和 某个具体的bean

  • 结果论:把实现过程封装起来,对于调用者来说,只关心结果
    就比如使用ofo:使用的人只关心我能用到车,至于这个车怎么造的,谁造的,什么时间造的等等,你是不关心的
  • 便于扩展

策略模式

强调:最终的目的都是一样的
主语对象是策略
比如解决一道数学问题,可以有多种解答方式,但是最终的结果都是一样的,这几种方式可以任意切换
再比如超市搞优惠,可以使用会员积分,优惠券、满减等不能优惠方式,但最终结果都是优惠价格
区别于委派模式,策略模式更注重的策略的多样性,委派模式注重的某一种策略
委派模式注重:谁来做
测试模式注重:有多少方式来做

¶简单工厂

内容大而全,所有的创建方法都在这个类里面

¶工厂方法

其实就是对简单工厂的一个抽象封装,将各自不同创建内容进行单独封装,并抽象成接口形式



¶抽象工厂

其实就是对工厂方法的一个再封装,将公共的东西封装成抽象类,将不同的部分由各自子工厂自行实现
其实就是变相的静态代理



单例模式

1.保证系统启动到停止,只产生一个实例(节省资源)
2.在应用中遇到功能性冲突的时候,需要使用单例模式(保证功能不冲突)

  • 配置文件
    针对某一种功能,如果两个配置文件是相同的,那其中有一个就是浪费的,如果不一样,就不知道该用哪个
  • 直系领导
    两个领导给你发命令,你都不知道该听谁的
  • 公历
    是为了和国际同步,如果不同步,那就会有时间差问题
    等等…

¶单例的几种写法

  • 饿汉模式
1
2
3
4
5
6
7
8
9
10
11
public class Singleton implements java.io.Serializable {

public static Singleton INSTANCE = new Singleton();

protected Singleton() { }

private Object getInstance() {
return INSTANCE;
}

}
  • 懒汉模式 (线程不安全)
1
2
3
4
5
6
7
8
9
10
11
public class Singleton1 {
private Singleton1() {}
private static Singleton1 single = null;

public static Singleton1 getInstance() {
if (single == null) {
single = new Singleton1();
}
return single;
}
}
  • 饿汉模式(静态代码块)
1
2
3
4
5
6
7
8
9
10
11
12
13
pubic class Singleton {
private Singleton instance = null;
static {
instance = new Singleton;
}
private Singleton () {};
public static Singleton getInstance() {
return this.instance;
}
}
// 从上往下(必须声明在前,使用在后)
// 先属性、后方法
// 先静态、后动态
  • 饿汉模式(线程安全,但是每次都需要上锁,大部分情况下用不到)
1
2
3
4
5
6
7
8
9
10
11
public class Singleton2 {
private Singleton2() {}
private static Singleton2 single=null;

public static synchronized Singleton2 getInstance() {
if (single == null) {
single = new Singleton2();
}
return single;
}
}
  • 懒汉模式(双重锁检查)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton3 {
//1、第一步先将构造方法私有化
private Singleton3() {}
//2、然后声明一个静态变量保存单例的引用
private static Singleton3 single=null;
//3、通过提供一个静态方法来获得单例的引用
//为了保证多线程环境下的另一种实现方式,双重锁检查
//性能,第一次的时候
public static Singleton3 getInstance() {
if (single == null) {
synchronized (Singleton3.class) {
if (single == null) {
single = new Singleton3();
}
}
}
return single;
}
}
  • 内部类方式 (既解决了线程安全问题,有解决的性能问题,因为是有classLoad保证线程安装的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton4 {
//1、先声明一个静态内部类
//private 私有的保证别人不能修改
//static 保证全局唯一
private static class LazyHolder {
//final 为了防止内部误操作,代理模式,GgLib的代理模式
private static final Singleton4 INSTANCE = new Singleton4();
}
//2、将默认构造方法私有化
private Singleton4 (){}
//相当于有一个默认的public的无参的构造方法,就意味着在代码中随时都可以new出来

//3、同样提供静态方法获取实例
//final 确保别人不能覆盖
public static final Singleton4 getInstance() {

//方法中的逻辑,是要在用户调用的时候才开始执行的
//方法中实现逻辑需要分配内存,也是调用时才分配的
return LazyHolder.INSTANCE;
}
}
  • 枚举
1
2
3
4
5
6
7
public enum Singleton {
INSTANCE;

Singleton(){

}
}

原型模式

区别于多例,基于拷贝(深拷贝、浅拷贝,反射)
能够直接拷贝其实际内容的数据类型,只支持8个基本数据类型和string,其余都是复制的地址引用。
克隆实际上是一种字节码克隆,而不是重新new一个对象,不走构造方法
反射:性能并不高,一般用在初始化,不会再运行时使用
浅拷贝:只是复制了地址引用,不复制实际的值

1
2
//默认浅克隆,只克隆八大基本数据类型和String
super.clone();

¶字节码克隆

克隆是不走构造方法的,直接是字节码复制

模板模式

template 就是一个固定的模板,就类似:

  • spring mvc的RestTemplate, restful请求接口的流程是一样的,都是基于http的。
  • java jdbc也定义了一套模板
    1.加载驱动类DriverManager
    2.建立连接
    3.创建语句集(标准语句集、预处理语句集)(Mysql/Oracle/SqlServer等等)
    4.执行语句集
    5.获取结果集ResultSet
    6.ORM

说白了就是对一个东西整个流程的抽象和封装


线程

发表于 2019-10-22 | 更新于 2019-10-24 | 分类于 Java | 评论数: | 阅读次数:

线程的状态

¶新建初始状态(new)

实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

¶就绪状态(runnable)

也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。

  • 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
  • 调用线程的start()方法,此线程进入就绪状态。
  • 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
  • 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。

¶阻塞状态(Blocked)

阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:

  • ¶等待阻塞

处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。通过调用线程的wait()方法,让线程等待某工作的完成。

  • ¶同步阻塞

线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。

  • ¶其他阻塞

通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。

¶死亡状态(Dead)

线程执行完了或因异常退出了run()方法,该线程结束生命周期。

每个对象都有的方法(机制)

synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们

¶monitor

他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。
wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。
当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

¶volatile

多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。

针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。

基本线程类

基本线程类指的是Thread类,Runnable接口,Callable接口

¶Thread

Thread 类实现了Runnable接口
Thread类相关方法:

1
2
3
4
5
6
7
8
//当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)
public static Thread.yield()
//暂停一段时间
public static Thread.sleep()
//在一个线程中调用other.join(),将等待other执行完后才继续本线程。    
public join()
//中断线程
public interrupte()

关于中断:
它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。
Thread.interrupted()检查当前线程是否发生中断,返回boolean
synchronized在获锁的过程中是不能被中断的。
中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体

¶join实现原理

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
public class Thread implements Runnable {
...
public final void join() throws InterruptedException {
join(0);
}
...
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { //判断是否携带阻塞的超时时间,等于0表示没有设置超时时间
while (isAlive()) {//isAlive获取线程状态,无线等待直到previousThread线程结束
wait(0); //调用Object中的wait方法实现线程的阻塞
}
} else { //阻塞直到超时
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
...

我们需要知道的是,调用wait方法必须要获取锁,所以join方法是被synchronized修饰的,synchronized修饰在方法层面相当于synchronized(this),this就是previousThread本身的实例。

join为什么阻塞的是主线程呢? 不理解的原因是阻塞主线程的方法是放在previousThread这个实例作用,让大家误以为应该阻塞previousThread线程。实际上主线程会持有previousThread这个对象的锁,然后调用wait方法去阻塞,而这个方法的调用者是在主线程中的。所以造成主线程阻塞。

为什么previousThread线程执行完毕就能够唤醒住线程呢?或者说是在什么时候唤醒的?
要了解这个问题,我们又得翻jdk的源码,但是如果大家对线程有一定的基本了解的话,通过wait方法阻塞的线程,需要通过notify或者notifyall来唤醒。所以在线程执行完毕以后会有一个唤醒的操作,只是我们不需要关心。

接下来在hotspot的源码中找到 thread.cpp,看看线程退出以后有没有做相关的事情来证明我们的猜想.

1
2
3
4
5
6
7
8
9
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
assert(this == JavaThread::current(), "thread consistency check");
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
assert(!this->has_pending_exception(), "ensure_join should have cleared");
...

观察一下 ensure_join(this)这行代码上的注释,唤醒处于等待的线程对象,这个是在线程终止之后做的清理工作,这个方法的定义代码片段如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
//这里是清除native线程,这个操作会导致isAlive()方法返回false
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);//注意这里
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}

ensure_join方法中,调用 lock.notify_all(thread); 唤醒所有等待thread锁的线程,意味着调用了join方法被阻塞的主线程会被唤醒; 到目前为止,我们基本上对join的原理做了一个比较详细的分析

总结,Thread.join其实底层是通过wait/notifyall来实现线程的通信达到线程阻塞的目的;当线程执行结束以后,会触发两个事情,第一个是设置native线程对象为null、第二个是通过notifyall方法,让等待在previousThread对象锁上的wait方法被唤醒。

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
public class Test {
public static void main(String[] args) throws InterruptedException {

Test test = new Test();
test.test();

System.out.println(10000);

}

public void test(){
print();
}

public synchronized void print(){
System.out.println("aaaaaaaaaaaaa");
try {
// Thread.sleep(2000);
wait();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

Thread类最佳实践:
写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。

如何获取线程中的异常

¶Runnable

与Thread类似

¶Callable

future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态

1
2
3
4
5
ExecutorService e = Executors.newFixedThreadPool(3);
//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束

高级多线程控制类

¶ThreadLocal类

用处:保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。

主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。

¶原子类(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized
该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。此时c给b2元,但是b的加十元代码约为:

1
2
3
4
5
6
if(b.value.compareAndSet(old, value)){
return ;
}else{
//try again
// if that fails, rollback and log
}

¶Lock

lock: 在java.util.concurrent包内。共有三个实现:

1
2
3
ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:

  • lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)
  • 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
  • 本质上和监视器锁(即synchronized是一样的)
  • 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。
  • 和Condition类的结合。
  • 性能更高,对比如下图:

    具体请看:synchronized 和 Lock

¶容器类

这里就讨论比较常用的两个:

  • BlockingQueue
  • ConcurrentHashMap

¶BlockingQueue

阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管  道,特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue(优先队列)

BlockingQueue在队列的基础上添加了多线程协作的功能:

除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。

常见的阻塞队列有:

  • ArrayListBlockingQueue
  • LinkedListBlockingQueue
  • DelayQueue
  • SynchronousQueue

¶ConcurrentHashMap

高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap

¶管理类

管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。
了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBean

¶ThreadPoolExecutor

1
2
3
4
5
6
7
8
ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
// 第一种是可变大小线程池,按照任务数来分配线程,
// 第二种是单线程池,相当于FixedThreadPool(1)
// 第三种是固定大小线程池。
// 然后运行
e.execute(new MyRunnableImpl());

该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc:

ThreadPoolExecutor参数解释

1
2
3
4
5
6
7
8
9
10
11
12
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。

maximumPoolSize:线程最大值,线程的增长始终不会超过该值。

keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态

unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS 

workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)

threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现
ThreadFactory接口并作为参数传入。

git和svn的区别

发表于 2019-10-22 | 更新于 2019-10-31 | 分类于 Java | 评论数: | 阅读次数:

svn

  • 在提交版本的时候,version1保存第一版,但是version2的时候保存的就是在前一个版本上的变化内容,如果没有变化就不会保存
  • 如果svn的磁盘损坏了,那就导致push和pull的操作无法实现
  • svn实现的CVCS(centralized version control server) 集中式的版本控制

git

  • git的原作者是linux的开发者,而liunx是一个全球性的组织,很多人都会在push和pull代码
  • git实现的是DVCS(distributed version control server) 分布式的版本控制
  • version1保存第一版,但是version2的时候保存的是version1变化后的内容,如果没有变化那存储的是version1的指针引用
  • 实现了去中心
  • 每个操作者本地都存有该仓库所有版本迭代和操作数据
    git的实现,类似区块链的存储、分布式账本等一些数据的存储

¶merge

对比代码的时候比较繁琐,因为有多个线路,但是保存的所有的数据

¶rebase

对比分支的是后会很清晰,因为只有一条线,但是部分分支数据就会丢失

spring - AOP

发表于 2019-10-22 | 更新于 2019-11-26 | 分类于 Java | 评论数: | 阅读次数:

Spring P AOP 设计原理及具体实践

¶SpringAOP 应用示例

AOP 是 OOP 的延续,是 Aspect Oriented Programming 的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代
理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP 设计模式孜孜不倦追求的是调用者和被调用者之
间的解耦,AOP 可以说也是这种目标的一种实现。

我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些
代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP 就实现了把这些业务需求与系统需求分开来做。这
种解决的方式也称代理机制。

先来了解一下 AOP 的相关概念

  • 切面(Aspect ):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在 ApplicationContext 中<aop:aspect>来配置。
  • 连接点( (Joinpoint ):程序执行过程中的某一行为,例如,MemberService .get 的调用或者 MemberService .delete抛出异常等行为。
  • 通知(Advice) ) :“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。
  • 切入点( (Pointcut ) :匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
  • 目标对象(Target Object) ) :被一个或者多个切面所通知的对象。例如,AServcieImpl 和 BServiceImpl,当然在实际运行时,Spring AOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
  • AOP 理 代理( (AOP Proxy ):在 Spring AOP 中有两种代理方式,JDK 动态代理和 CGLIB 代理。默认情况下,TargetObject实现了接口时,则采用 JDK 动态代理,例如,AServiceImpl;反之,采用 CGLIB 代理,例如,BServiceImpl。强制使用 CGLIB 代理需要将 <aop:config>的 proxy-target-class 属性设为 true。

通知(Advice )类型:

  • 前置通知(Before advice ):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect 中的 doBefore 方法。
  • 后置通知( (After advice) ): :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,ServiceAspect 中的 returnAfter 方法,所以 Teser 中调用UserService.delete 抛出异常时,returnAfter 方法仍然执行。
  • 返回后通知( (After return advice) ): :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用元素进行声明。
  • 环绕通知( (Around advice) ): :包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,ServiceAspect 中的 around 方法。
  • 抛出异常后通知( (After throwing advice) ): :在方法抛出异常退出时执行的通知。ApplicationContext 中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,ServiceAspect 中的 returnThrow 方法。

可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。

使用 Spring AOP 可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的 xml 配置方式。
先说注解,使用注解配置 Spring AOP 总体分为两步,第一步是在 xml 文件中声明激活自动扫描组件功能,同时激活自动代理功能(来测试 AOP 的注解功能):

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
//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
public class AnnotaionAspect {
private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* com.jphoebe.aop.service..*(..))")
public void aspect(){ }
/*
* 配置前置通知,使用在方法aspect()上注册的切入点
* 同时接受JoinPoint切入点对象,可以没有该参数
*/
@Before("aspect()")
public void before(JoinPoint joinPoint){
log.info("before " + joinPoint);
}
//配置后置通知,使用在方法aspect()上注册的切入点
@After("aspect()")
public void after(JoinPoint joinPoint){
log.info("after " + joinPoint);
}
//配置环绕通知,使用在方法aspect()上注册的切入点
@Around("aspect()")
public void around(JoinPoint joinPoint){
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " +
e.getMessage());
}
}
//配置后置返回通知,使用在方法aspect()上注册的切入点
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint){
log.info("afterReturn " + joinPoint);
}
//配置抛出异常后通知,使用在方法aspect()上注册的切入点
@AfterThrowing(pointcut="aspect()", throwing="ex")
public void afterThrow(JoinPoint joinPoint, Exception ex){
log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
}
}

执行结果可以看到,正如我们预期的那样,虽然我们并没有对 Service 类包括其调用方式做任何改变,但是 Spring 仍然拦截到了其中方法的调用,或许这正是 AOP 的魔力所在。

应该说学习 Spring AOP 有两个难点,第一点在于理解 AOP 的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。
概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

1
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?
  • modifiers-pattern:方法的操作权限
  • ret-type-pattern:返回值
  • declaring-type-pattern:方法所在的包
  • name-pattern:方法名
  • parm-pattern:参数名
  • throws-pattern:异常

其中,除 ret-type-pattern 和 name-pattern 之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(…))表示
com.spring.service 包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

每个通知方法第一个参数都是 JoinPoint。其实,在 Spring 中,任何通知(Advice)方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型用以接受当前连接点对象。JoinPoint 接口提供了一系列有用的方法, 比如

  • getArgs() (返回方法参数)
  • getThis() (返回代理对象)
  • getTarget() (返回目标)
  • getSignature() (返回正在被通知的方法相关信息) * toString() (打印出正在被通知的方法的有用信息)

SpringAOP 设计原理及源码分析

先看看 Spring 中主要的 AOP 组件

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据
AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。下面我们来研究一下 Spring 如何使用 JDK 来生成代理对象,具体的生成代码放在JdkDynamicAopProxy 这个类中,直接上相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* <ol>
* <li>获取代理类要实现的接口,除了 Advised 对象中配置的,还会加上 SpringProxy, Advised(opaque=false)
* <li>检查上面得到的接口中有没有定义 equals 或者 hashcode 的接口
* <li>调用 Proxy.newProxyInstance 创建代理对象
* </ol>
*/
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

那这个其实很明了,注释上我也已经写清楚了,不再赘述。

下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道 InvocationHandler 是 JDK 动态代理的核心,生成的代理对象的方法调用都会委托到 InvocationHandler.invoke()
方法。而通过 JdkDynamicAopProxy 的签名我们可以看到这个类其实也实现了 InvocationHandler,下面我们就通过分析这个类中实现的 invoke()方法来具体看下 Spring AOP 是如何织入切面的。

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
86
87
88
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;

TargetSource targetSource = this.advised.targetSource;
Class targetClass = null;
Object target = null;

try {
//eqauls()方法,具目标对象未实现此方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
//hashCode()方法,具目标对象未实现此方法
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
//Advised 接口或者其父接口中定义的方法,直接反射调用,不应用通知
if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}

Object retVal;

if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}

// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
//获得目标对象的类
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}

//获取可以应用到此方法上的 Interceptor 列表
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
//如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
//创建 MethodInvocation
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}

// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行 joinpoint; 如
果没有,则直接反射执行 joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过 Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

1
2
3
4
5
6
7
8
9
10
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}

可以看到实际的获取工作其实是由 AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。

下面来分析下这个方法的实现:

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
/**
* 从提供的配置实例 config 中获取 advisor 列表,遍历处理这些 advisor.如果是 IntroductionAdvisor,
* 则判断此 Advisor 能否应用到目标类 targetClass 上.如果是 PointcutAdvisor,则判断
* 此 Advisor 能否应用到目标方法 method 上.将满足条件的 Advisor 通过 AdvisorAdaptor 转化成 Interceptor
列表返回.
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class targetClass) {

// This is somewhat tricky... we have to process introductions first,
// but we need to preserve order in the ultimate list.
List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
//查看是否包含 IntroductionAdvisor
boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
//这里实际上注册一系列 AdvisorAdapter,用于将 Advisor 转化成 MethodInterceptor
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
//这个地方这两个方法的位置可以互换下
//将 Advisor 转化成 Interceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
//检查当前 advisor 的 pointcut 是否可以匹配当前方法
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}

这个方法执行完成后,Advised 中配置能够应用到连接点或者目标类的 Advisor 全部被转化成了 MethodInterceptor.接下来我们再看下得到的拦截器链是怎么起作用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
......
//如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
//创建 MethodInvocation
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
......

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建 MethodInvocation,调用其 proceed方法,触发拦截器链的执行,来看下具体代码

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
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
//如果 Interceptor 执行完了,则执行 joinPoint
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}

Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
//如果要动态匹配 joinPoint
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
//动态匹配:运行时参数是否满足匹配条件
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
//执行当前 Intercetpor
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
//动态匹配失败时,略过当前 Intercetpor,调用下一个 Interceptor
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
//执行当前 Intercetpor
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

Spring C JDBC 设计原理及二次开发

12…5<i class="fa fa-angle-right" aria-label="下一页"></i>
Jeff-Eric

Jeff-Eric

42 日志
12 分类
28 标签
<i class="fa fa-fw fa-github"></i>GitHub <i class="fa fa-fw fa-envelope"></i>E-Mail
<img src="/blog/images/cc-by-nc-sa.svg" alt="Creative Commons"/>
0%
© 2019 Jeff-Eric
由 Hexo 强力驱动 v4.0.0
|
主题 – NexT.Gemini v7.0.1