-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathatom.xml
588 lines (328 loc) · 915 KB
/
atom.xml
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>AnFrank's Blog</title>
<subtitle>世界真的很小,好像一转身,就不知道会遇见谁。W世界真的很大,好像一转身,就不知道谁会消失。W成熟是给你陌生人看的,逗比是给朋友看的,幼稚是给喜欢的人看的。</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://enfangzhong.github.io/"/>
<updated>2020-02-08T15:41:37.856Z</updated>
<id>http://enfangzhong.github.io/</id>
<author>
<name>AnFrank</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>2020 Happy Lantern Festival</title>
<link href="http://enfangzhong.github.io/2020/02/08/2020Happy%20Lantern%20Festival/"/>
<id>http://enfangzhong.github.io/2020/02/08/2020Happy Lantern Festival/</id>
<published>2020-02-08T03:03:11.000Z</published>
<updated>2020-02-08T15:41:37.856Z</updated>
<content type="html"><![CDATA[<h1 id="2020-Happy-Lantern-Festival"><a href="#2020-Happy-Lantern-Festival" class="headerlink" title="2020 Happy Lantern Festival"></a>2020 Happy Lantern Festival</h1><p><img src="/2020/02/08/2020Happy Lantern Festival/Lantern Festival.png" alt="1525658740266"></p>]]></content>
<summary type="html">
<h1 id="2020-Happy-Lantern-Festival"><a href="#2020-Happy-Lantern-Festival" class="headerlink" title="2020 Happy Lantern Festival"></a>2020
</summary>
<category term="元宵节快乐" scheme="http://enfangzhong.github.io/categories/%E5%85%83%E5%AE%B5%E8%8A%82%E5%BF%AB%E4%B9%90/"/>
<category term="节日" scheme="http://enfangzhong.github.io/tags/%E8%8A%82%E6%97%A5/"/>
</entry>
<entry>
<title>图解设计模式</title>
<link href="http://enfangzhong.github.io/2020/01/16/%E5%9B%BE%E8%A7%A3%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<id>http://enfangzhong.github.io/2020/01/16/图解设计模式/</id>
<published>2020-01-16T03:03:11.000Z</published>
<updated>2020-01-18T08:34:06.237Z</updated>
<content type="html"><![CDATA[<h1 id="图解设计模式"><a href="#图解设计模式" class="headerlink" title="图解设计模式"></a>图解设计模式</h1>]]></content>
<summary type="html">
<h1 id="图解设计模式"><a href="#图解设计模式" class="headerlink" title="图解设计模式"></a>图解设计模式</h1>
</summary>
<category term="设计模式" scheme="http://enfangzhong.github.io/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="设计模式" scheme="http://enfangzhong.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>咖啡小知识</title>
<link href="http://enfangzhong.github.io/2020/01/13/%E5%92%96%E5%95%A1%E5%B0%8F%E7%9F%A5%E8%AF%86/"/>
<id>http://enfangzhong.github.io/2020/01/13/咖啡小知识/</id>
<published>2020-01-13T15:03:11.000Z</published>
<updated>2020-01-12T18:01:14.281Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="咖啡小知识:"><a href="#咖啡小知识:" class="headerlink" title="咖啡小知识:"></a>咖啡小知识:</h1><p>咖啡现在已经成为越来越多上班族不可或缺的“提神续命”饮料,让我们来了解一下咖啡的小知识叭。</p><p><img src="/2020/01/13/咖啡小知识/image-20200113010250405.png" alt="image-20200113010250405"></p><p>①咖啡分类:</p><p>目前市场上的咖啡一般分为4大类:</p><p>(1)美式咖啡:</p><blockquote><p>是以一种咖啡为原料,然后再加水制成的,萃取而来浓度高,制作时间长;</p></blockquote><p>(2)意式咖啡:</p><blockquote><p>意式咖啡 Espresso</p><p>是一种咖啡粉加倍,咖啡机高压研磨,浓缩咖啡,强烈浓郁。</p></blockquote><p>(3)混合咖啡(花式咖啡):</p><blockquote><p>意式咖啡加入花样而来。是以多种咖啡为原料,然后再加奶、水等制成的,如拿铁、摩卡、卡布奇诺、焦糖玛奇朵。</p></blockquote><p>(4)单品咖啡</p><blockquote><p> 由原产地出产的单一咖啡豆磨制而成的纯正咖啡。</p><p>有蓝山咖啡、巴西咖啡、哥伦比亚咖啡等等。单品咖啡有强烈的特性口感特别或清新柔和或香醇顺滑。</p><p>适合对咖啡品质有追求的人</p></blockquote><p>②咖啡成分:</p><p>咖啡中含有少量的脂肪、蛋白质、糖、矿物质和粗纤维,另外,还含有<strong>咖啡因、绿原酸</strong>、单宁等成分。其中,咖啡因可以加速人体新陈代谢,使人保持头脑清醒;绿原酸具有抗氧化、抗炎、抗菌、抗病毒等生物活性。</p><p>对于除了咖啡和水,什么都没加的美式咖啡来说,它含有丰富的钾和大量的抗氧化物质,热量也很低,算是比较健康的饮品。</p><p>如果在咖啡基础上加入奶(如拿铁、卡布奇诺),将可以增加钙、蛋白质等营养成分的含量,但具体有多少,还要看奶的比例,一般来说,拿铁中奶的比例会比卡布奇诺高一些。</p><p>除了奶,一些糖、脂肪也经常被加到咖啡里去,例如摩卡中会加入巧克力酱和鲜奶油,焦糖玛奇朵中会加入焦糖,这些咖啡在口感上是更丰富了,但因为糖和脂肪的含量上升了,从营养的角度,却更逊色了。</p><h2 id="一、美式咖啡-咖啡豆研磨成粉萃取,制作时间长"><a href="#一、美式咖啡-咖啡豆研磨成粉萃取,制作时间长" class="headerlink" title="一、美式咖啡: 咖啡豆研磨成粉萃取,制作时间长"></a>一、美式咖啡: 咖啡豆研磨成粉萃取,制作时间长</h2><p><img src="/2020/01/13/咖啡小知识/image-20200113004228514.png" alt="image-20200113004228514"></p><h2 id="二、意式咖啡-咖啡粉加倍,高压研磨,浓缩咖啡,强烈浓郁"><a href="#二、意式咖啡-咖啡粉加倍,高压研磨,浓缩咖啡,强烈浓郁" class="headerlink" title="二、意式咖啡:咖啡粉加倍,高压研磨,浓缩咖啡,强烈浓郁"></a>二、意式咖啡:咖啡粉加倍,高压研磨,浓缩咖啡,强烈浓郁</h2><p>意式咖啡 Espresso</p><p><img src="/2020/01/13/咖啡小知识/image-20200113004636506.png" alt="image-20200113004636506"></p><h2 id="三、花式咖啡"><a href="#三、花式咖啡" class="headerlink" title="三、花式咖啡:"></a>三、花式咖啡:</h2><p>是以多种咖啡为原料,然后再加奶、水等制成的,如拿铁、摩卡、卡布奇诺、焦糖玛奇朵。</p><h3 id="1-一份Espresso-1份水-美式咖啡"><a href="#1-一份Espresso-1份水-美式咖啡" class="headerlink" title="1.一份Espresso+1份水=美式咖啡"></a>1.一份Espresso+1份水=美式咖啡</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113004900088.png" alt="image-20200113004900088"></p><h3 id="2-一份Espresso-1-5份热牛奶-半分奶泡-拿铁"><a href="#2-一份Espresso-1-5份热牛奶-半分奶泡-拿铁" class="headerlink" title="2.一份Espresso+1.5份热牛奶+半分奶泡=拿铁"></a>2.一份Espresso+1.5份热牛奶+半分奶泡=拿铁</h3><p>(牛奶多点,奶泡少一点)</p><p><img src="/2020/01/13/咖啡小知识/image-20200113005036498.png" alt="image-20200113005036498"></p><h3 id="3-一份Espresso-1-5份热牛奶-半分奶泡-糖浆-某糖浆拿铁(例-焦糖拿铁、香草拿铁、抹茶拿铁、榛果拿铁)"><a href="#3-一份Espresso-1-5份热牛奶-半分奶泡-糖浆-某糖浆拿铁(例-焦糖拿铁、香草拿铁、抹茶拿铁、榛果拿铁)" class="headerlink" title="3.一份Espresso+1.5份热牛奶+半分奶泡+糖浆=某糖浆拿铁(例:焦糖拿铁、香草拿铁、抹茶拿铁、榛果拿铁)"></a>3.一份Espresso+1.5份热牛奶+半分奶泡+糖浆=某糖浆拿铁(例:焦糖拿铁、香草拿铁、抹茶拿铁、榛果拿铁)</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113005123624.png" alt="image-20200113005123624"></p><h3 id="4-一份Espresso-0-5份热牛奶-一分奶泡-卡布奇诺"><a href="#4-一份Espresso-0-5份热牛奶-一分奶泡-卡布奇诺" class="headerlink" title="4.一份Espresso+0.5份热牛奶+一分奶泡=卡布奇诺"></a>4.一份Espresso+0.5份热牛奶+一分奶泡=卡布奇诺</h3><p>(牛奶少点,奶泡多一点,和拿铁相反)</p><p><img src="/2020/01/13/咖啡小知识/image-20200113005317697.png" alt="image-20200113005317697"></p><h3 id="5-一份Espresso-1-5份热牛奶-半分奶泡-巧克力酱-鲜奶油-可可粉等-摩卡"><a href="#5-一份Espresso-1-5份热牛奶-半分奶泡-巧克力酱-鲜奶油-可可粉等-摩卡" class="headerlink" title="5.一份Espresso+1.5份热牛奶+半分奶泡+巧克力酱+鲜奶油+可可粉等=摩卡"></a>5.一份Espresso+1.5份热牛奶+半分奶泡+巧克力酱+鲜奶油+可可粉等=摩卡</h3><p>摩卡是在拿铁的基础上加入巧克力酱,鲜奶油,或者可可粉、肉桂粉等。</p><p><img src="/2020/01/13/咖啡小知识/image-20200113005744908.png" alt="image-20200113005744908"></p><h3 id="6-一份Espresso-两份奶泡-玛奇朵"><a href="#6-一份Espresso-两份奶泡-玛奇朵" class="headerlink" title="6.一份Espresso+两份奶泡=玛奇朵"></a>6.一份Espresso+两份奶泡=玛奇朵</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113005811551.png" alt="image-20200113005811551"></p><h3 id="7-一份Espresso-两份奶泡-糖浆-焦糖玛奇朵"><a href="#7-一份Espresso-两份奶泡-糖浆-焦糖玛奇朵" class="headerlink" title="7.一份Espresso+两份奶泡+糖浆=焦糖玛奇朵"></a>7.一份Espresso+两份奶泡+糖浆=焦糖玛奇朵</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113005857674.png" alt="image-20200113005857674"></p><h3 id="8-一份Espresso-鲜奶油-康宝蓝"><a href="#8-一份Espresso-鲜奶油-康宝蓝" class="headerlink" title="8.一份Espresso+鲜奶油=康宝蓝"></a>8.一份Espresso+鲜奶油=康宝蓝</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113005944961.png" alt="image-20200113005944961"></p><h3 id="9-一份Espresso-半份牛奶-半份奶油-少量奶泡-布雷卫"><a href="#9-一份Espresso-半份牛奶-半份奶油-少量奶泡-布雷卫" class="headerlink" title="9.一份Espresso+半份牛奶+半份奶油+少量奶泡=布雷卫"></a>9.一份Espresso+半份牛奶+半份奶油+少量奶泡=布雷卫</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113010022994.png" alt="image-20200113010022994"></p><h3 id="10-一份Espresso-少量爱尔兰威士忌-鲜奶油-爱尔兰咖啡"><a href="#10-一份Espresso-少量爱尔兰威士忌-鲜奶油-爱尔兰咖啡" class="headerlink" title="10.一份Espresso+少量爱尔兰威士忌+鲜奶油=爱尔兰咖啡"></a>10.一份Espresso+少量爱尔兰威士忌+鲜奶油=爱尔兰咖啡</h3><p><img src="/2020/01/13/咖啡小知识/image-20200113010042071.png" alt="image-20200113010042071"></p><p>最简单的理解就是:</p><p>1、美式咖啡:现在做的美式咖啡主要是在意式浓缩咖啡的基础上兑热水,只是味道淡一些;<br>味道特点:纯咖啡,口味淡</p><p>2、意式咖啡:espresso,咖啡粉加倍,咖啡机高压研磨,浓缩咖啡,强烈浓郁</p><p>3、花式咖啡</p><p>3.1 拿铁咖啡:意式浓缩咖啡大量加热牛奶+少量奶泡</p><p>口味特点:带有咖啡味的牛奶。<strong>牛奶多,奶泡少。</strong></p><p>3.2 卡布奇诺:意式浓缩咖啡加入少量热牛奶,大量奶泡<br>口味特点:咖啡味道稍浓一些,奶沫口感。<strong>牛奶少,奶泡多。</strong></p><p>3.3 摩卡咖啡:意式浓缩咖啡加奶、泡沫奶油、再加巧克力酱,<br>口味特点:带有<strong>巧克力味</strong>的奶咖啡,泡沫奶油口感。</p><p>3.4 玛奇朵:意式浓缩咖啡加入<strong>两份奶泡</strong></p><p>上述4款是我平时比较喜欢喝的,平时加班提提神叭。</p><h1 id="四、课代表总结"><a href="#四、课代表总结" class="headerlink" title="四、课代表总结"></a>四、课代表总结</h1><div class="table-container"><table><thead><tr><th style="text-align:center"></th><th></th></tr></thead><tbody><tr><td style="text-align:center">美式咖啡</td><td>萃取,咖啡因浓度高,制作时间长</td></tr><tr><td style="text-align:center">意式咖啡:</td><td>也就是平时说Espresso,咖啡粉加倍,咖啡机高压研磨,浓缩咖啡,强烈浓郁。</td></tr><tr><td style="text-align:center">花式咖啡:</td><td>( ̄▽ ̄)</td></tr><tr><td style="text-align:center"></td><td>1.一份Espresso+1份水=美式咖啡</td></tr><tr><td style="text-align:center"></td><td>2.一份Espresso+1.5份热牛奶+半分奶泡=拿铁</td></tr><tr><td style="text-align:center"></td><td>3.一份Espresso+1.5份热牛奶+半分奶泡+某糖浆=某糖浆拿铁(例:抹茶拿铁)</td></tr><tr><td style="text-align:center"></td><td>4.一份Espresso+0.5份热牛奶+一分奶泡=卡布奇诺</td></tr><tr><td style="text-align:center"></td><td>5.一份Espresso+1.5份热牛奶+半分奶泡+巧克力酱+鲜奶油+可可粉等=摩卡</td></tr><tr><td style="text-align:center"></td><td>6.一份Espresso+两份奶泡=玛琪朵</td></tr><tr><td style="text-align:center"></td><td>7.一份Espresso+两份奶泡+糖浆=焦糖玛奇朵</td></tr><tr><td style="text-align:center"></td><td>8.一份Espresso+鲜奶油=康宝蓝</td></tr><tr><td style="text-align:center"></td><td>9.一份Espresso+半份牛奶+半份奶油+少量奶泡=布雷卫</td></tr><tr><td style="text-align:center"></td><td>10.一份Espresso+少量爱尔兰威士忌+鲜奶油=爱尔兰咖啡</td></tr><tr><td style="text-align:center"></td><td>( ̄▽ ̄)</td></tr></tbody></table></div><p>在知乎看到的的图:</p><p> <img src="/2020/01/13/咖啡小知识/v2-a4ec16095249d8e70e58b8515941f897_hd.jpg" alt="img"> </p><p> <img src="/2020/01/13/咖啡小知识/v2-71a9c396c38e96e12528bac64ece1132_hd.jpg" alt="img"> </p><h1 id="五、引用"><a href="#五、引用" class="headerlink" title="五、引用"></a>五、引用</h1><blockquote><p>1、哔哩哔哩上的咖啡小知识:</p></blockquote><iframe src="//player.bilibili.com/player.html?aid=25386867&cid=43133805&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><blockquote><p>2、知乎上的咖啡小知识:</p></blockquote><p> <a href="https://www.zhihu.com/question/27113144" target="_blank" rel="noopener">https://www.zhihu.com/question/27113144</a> </p><h1 id="六、我了解的东西也不一定对,欢迎大家一起讨论。"><a href="#六、我了解的东西也不一定对,欢迎大家一起讨论。" class="headerlink" title="六、我了解的东西也不一定对,欢迎大家一起讨论。"></a>六、我了解的东西也不一定对,欢迎大家一起讨论。</h1>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1 id="咖啡小知识:"><a href="#咖啡小知识:" class="headerlink" title="咖啡小知识:"></a>咖啡小知识:</h1><p>咖啡现在已经成为越来越多上班族不可或缺的“提神续命”饮料,让我们来了解一下咖啡的小
</summary>
<category term="咖啡" scheme="http://enfangzhong.github.io/categories/%E5%92%96%E5%95%A1/"/>
<category term="生活" scheme="http://enfangzhong.github.io/categories/%E5%92%96%E5%95%A1/%E7%94%9F%E6%B4%BB/"/>
<category term="生活" scheme="http://enfangzhong.github.io/tags/%E7%94%9F%E6%B4%BB/"/>
<category term="咖啡" scheme="http://enfangzhong.github.io/tags/%E5%92%96%E5%95%A1/"/>
</entry>
<entry>
<title>如何给长辈选茶叶</title>
<link href="http://enfangzhong.github.io/2020/01/12/%E5%A6%82%E4%BD%95%E7%BB%99%E9%95%BF%E8%BE%88%E9%80%89%E8%8C%B6%E5%8F%B6/"/>
<id>http://enfangzhong.github.io/2020/01/12/如何给长辈选茶叶/</id>
<published>2020-01-12T15:03:11.000Z</published>
<updated>2020-01-12T15:22:47.844Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><p>今天为了给长辈挑选茶叶挑了一下午,这篇文章主要是博主了解茶叶做的笔记。博主是茶叶的行外之人,请有懂茶叶的朋友推荐一些茶叶叭。</p><h1 id="一、茶叶分类"><a href="#一、茶叶分类" class="headerlink" title="一、茶叶分类"></a>一、茶叶分类</h1><p>市面上的茶叶品种非常多,按照发酵程度可以分为六大类:</p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112222401533.png" alt="image-20200112222401533"></p><p>绿茶、白茶、黄茶、青茶、红茶和黑茶。</p><p>如:龙井为绿茶。铁观音为青茶叶。</p><p>龙井、碧螺春、铁观音、普洱</p><h2 id="绿茶"><a href="#绿茶" class="headerlink" title="绿茶"></a>绿茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112222505951.png" alt="image-20200112222505951"></p><p>绿茶品牌</p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112224712958.png" alt="image-20200112224712958"></p><h2 id="白茶"><a href="#白茶" class="headerlink" title="白茶"></a>白茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112223208959.png" alt="image-20200112223208959"></p><p>白茶品牌</p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112224926034.png" alt="image-20200112224926034"></p><h2 id="黄茶"><a href="#黄茶" class="headerlink" title="黄茶"></a>黄茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112223322293.png" alt="image-20200112223322293"></p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225047954.png" alt="image-20200112225047954"></p><h2 id="青茶"><a href="#青茶" class="headerlink" title="青茶"></a>青茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112223936649.png" alt="image-20200112223936649"></p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225135782.png" alt="image-20200112225135782"></p><h2 id="红茶"><a href="#红茶" class="headerlink" title="红茶"></a>红茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225221150.png" alt="image-20200112225221150"></p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225341009.png" alt="image-20200112225341009"></p><h2 id="黑茶"><a href="#黑茶" class="headerlink" title="黑茶"></a>黑茶</h2><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225447103.png" alt="image-20200112225447103"></p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112225624727.png" alt="image-20200112225624727"></p><h1 id="二、口感总结"><a href="#二、口感总结" class="headerlink" title="二、口感总结"></a>二、口感总结</h1><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112230417278.png" alt="image-20200112230417278"></p><p>茶叶包装分类:</p><p><img src="/2020/01/12/如何给长辈选茶叶/image-20200112231046155.png" alt="image-20200112231046155"></p><p>灌装茶、砖茶、饼茶、袋泡茶(茶包)。</p><p>三、茶叶纪录片</p><p>央视纪录片《茶,一片树叶的故事》【共六集】</p><p><a href="https://www.bilibili.com/video/av21397586/" target="_blank" rel="noopener">https://www.bilibili.com/video/av21397586/</a></p><iframe src="//player.bilibili.com/player.html?aid=21397586&cid=35216223&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>]]></content>
<summary type="html">
<p>[TOC]</p>
<p>今天为了给长辈挑选茶叶挑了一下午,这篇文章主要是博主了解茶叶做的笔记。博主是茶叶的行外之人,请有懂茶叶的朋友推荐一些茶叶叭。</p>
<h1 id="一、茶叶分类"><a href="#一、茶叶分类" class="headerlink" titl
</summary>
<category term="茶叶" scheme="http://enfangzhong.github.io/categories/%E8%8C%B6%E5%8F%B6/"/>
<category term="生活" scheme="http://enfangzhong.github.io/categories/%E8%8C%B6%E5%8F%B6/%E7%94%9F%E6%B4%BB/"/>
<category term="茶叶" scheme="http://enfangzhong.github.io/tags/%E8%8C%B6%E5%8F%B6/"/>
<category term="生活" scheme="http://enfangzhong.github.io/tags/%E7%94%9F%E6%B4%BB/"/>
</entry>
<entry>
<title>Activiti7工作流引擎</title>
<link href="http://enfangzhong.github.io/2020/01/02/Activiti7%E5%B7%A5%E4%BD%9C%E6%B5%81%E5%BC%95%E6%93%8E/"/>
<id>http://enfangzhong.github.io/2020/01/02/Activiti7工作流引擎/</id>
<published>2020-01-02T01:37:00.000Z</published>
<updated>2020-01-02T01:37:46.549Z</updated>
<summary type="html">
</summary>
<category term="工作流" scheme="http://enfangzhong.github.io/categories/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
<category term="工作流" scheme="http://enfangzhong.github.io/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
</entry>
<entry>
<title>架构与优化之JVM优化第03课Tomcat8的优化 看懂Java底层字节码 编码的优化建议</title>
<link href="http://enfangzhong.github.io/2019/12/20/%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BC%98%E5%8C%96%E4%B9%8BJVM%E4%BC%98%E5%8C%96%E7%AC%AC03%E7%AF%87Tomcat8%E7%9A%84%E4%BC%98%E5%8C%96%20%E7%9C%8B%E6%87%82Java%E5%BA%95%E5%B1%82%E5%AD%97%E8%8A%82%E7%A0%81%20%E7%BC%96%E7%A0%81%E7%9A%84%E4%BC%98%E5%8C%96%E5%BB%BA%E8%AE%AE/"/>
<id>http://enfangzhong.github.io/2019/12/20/架构与优化之JVM优化第03篇Tomcat8的优化 看懂Java底层字节码 编码的优化建议/</id>
<published>2019-12-20T12:03:11.000Z</published>
<updated>2019-12-20T03:51:16.230Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>Tomcat8的优化</li><li>看懂Java底层字节码</li><li>编码的优化建议</li></ul><h1 id="1、Tomcat8优化"><a href="#1、Tomcat8优化" class="headerlink" title="1、Tomcat8优化"></a>1、Tomcat8优化</h1><p>tomcat服务器在JavaEE项目中使用率非常高,所以在生产环境对tomcat的优化也变得非 常重要了。<br>对于tomcat的优化,主要是从2个方面入手,一是,tomcat自身的配置,另一个是<br>tomcat所运行的jvm虚拟机的调优。<br>下面我们将从这2个方面进行讲解。</p><h2 id="1-1、Tomcat配置优化1-1-1、部署安装tomcat8-下载并安装:"><a href="#1-1、Tomcat配置优化1-1-1、部署安装tomcat8-下载并安装:" class="headerlink" title="1.1、Tomcat配置优化1.1.1、部署安装tomcat8 下载并安装:"></a>1.1、Tomcat配置优化1.1.1、部署安装tomcat8 下载并安装:</h2><p><a href="https://tomcat.apache.org/download-80.cgicd" target="_blank" rel="noopener">https://tomcat.apache.org/download-80.cgicd</a> /tmp<br>wget <a href="http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat‐" target="_blank" rel="noopener">http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat‐</a> 8/v8.5.34/bin/apache‐tomcat‐8.5.34.tar.gz</p><p>tar ‐xvf apache‐tomcat‐8.5.34.tar.gz cd apache‐tomcat‐8.5.34/conf</p><h1 id="修改配置文件,配置tomcat的管理用户"><a href="#修改配置文件,配置tomcat的管理用户" class="headerlink" title="修改配置文件,配置tomcat的管理用户"></a>修改配置文件,配置tomcat的管理用户</h1><p>vim tomcat‐users.xml #写入如下内容:</p><h1 id="保存退出"><a href="#保存退出" class="headerlink" title="保存退出"></a>保存退出</h1><h1 id="如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访问不了,提示403"><a href="#如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访问不了,提示403" class="headerlink" title="如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访问不了,提示403"></a>如果是tomcat7,配置了tomcat用户就可以登录系统了,但是tomcat8中不行,还需要修改另一个配置文件,否则访问不了,提示403</h1><p>vim webapps/manager/META‐INF/context.xml</p><h1 id="将-lt-Valve的内容注释掉"><a href="#将-lt-Valve的内容注释掉" class="headerlink" title="将<Valve的内容注释掉"></a>将<Valve的内容注释掉</h1><p><!‐‐ ‐‐></p><h1 id="保存退出即可"><a href="#保存退出即可" class="headerlink" title="保存退出即可"></a>保存退出即可</h1><h1 id="启动tomcat"><a href="#启动tomcat" class="headerlink" title="启动tomcat"></a>启动tomcat</h1><p>cd /tmp/apache‐tomcat‐8.5.34/bin/<br>./startup.sh && tail ‐f …/logs/catalina.out</p><h1 id="打开浏览器进行测试访问http-192-168-40-133-8080"><a href="#打开浏览器进行测试访问http-192-168-40-133-8080" class="headerlink" title="打开浏览器进行测试访问http://192.168.40.133:8080/"></a>打开浏览器进行测试访问<a href="http://192.168.40.133:8080/" target="_blank" rel="noopener">http://192.168.40.133:8080/</a></h1><p>点击“Server Status”,输入用户名、密码进行登录,tomcat/tomcat进入之后即可看到服务的信息。</p><p>1.1.2、禁用AJP连接<br>在服务状态页面中可以看到,默认状态下会启用AJP服务,并且占用8009端口。<br>什么是AJP呢?<br>AJP(Apache JServer Protocol)<br>AJPv13协议是面向包的。WEB服务器和Servlet容器通过TCP连接来交互;为了节省SOCKET创建的昂贵代价,WEB服务器会尝试维护一个永久TCP连接到servlet容器,并且在多个请求和响应周期过程会重用连接。我们一般是使用Nginx+tomcat的架构,所以用不着AJP协议,所以把AJP连接器禁用。修改conf下的server.xml文件,将AJP服务禁用掉即可。</p><p><connector port="8009" protocol="AJP/1.3" redirectport="8443"><br>1</connector></p><p>重启tomcat,查看效果。可以看到AJP服务以及不存在了。<br>1.1.3、执行器(线程池)<br>在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。<br>修改server.xml文件:</p><p><!‐‐将注释打开‐‐></p><p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="500" minsparethreads="50" prestartminsparethreads="true" maxqueuesize="100"><br><!‐‐<br>参数说明:<br>maxThreads:最大并发数,默认设置 200,一般建议在 500 ~ 1000,根据硬件设施和业务来判断<br>minSpareThreads:Tomcat 初 始 化 时 创 建 的 线 程 数 , 默 认 设 置 25 prestartminSpareThreads: 在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,如果不等于 true,minSpareThreads 的值就没啥效果了<br>maxQueueSize,最大的等待队列数,超过则拒绝请求<br>‐‐></executor></p><p><!‐‐在Connector中设置executor属性指向上面的执行器‐‐></p><connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectiontimeout="20000" redirectport="8443"><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>保存退出,重启tomcat,查看效果。<br>在页面中显示最大线程数为-1,这个是正常的,仅仅是显示的问题,实际使用的指定的值。</p><p>也有人遇到这样的问<br>题:<a href="https://blog.csdn.net/weixin_38278878/article/details/80144397" target="_blank" rel="noopener">https://blog.csdn.net/weixin_38278878/article/details/80144397</a><br>1.1.4、3种运行模式<br>tomcat的运行模式有3种:<br>1.bio<br>默认的模式,性能非常低下,没有经过任何优化处理和支持.<br>2.nio<br>nio(new I/O),是Java SE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Java nio是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,因此nio 也被看成是non-blocking I/O的缩写。它拥有比传统I/O操作(bio)更好的并发运行性能。<br>3.apr<br>安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.<br>推荐使用nio,不过,在tomcat8中有最新的nio2,速度更快,建议使用nio2. 设置nio2:</p><p><connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectiontimeout="20000" redirectport="8443"><br>1<br>2</connector></p><p>可以看到已经设置为nio2了。</p><p>1.2、部署测试用的java web项目<br>为了方便测试性能,我们将部署一个java web项目,这个项目本身和本套课程没有什么关系,仅仅用于测试。<br>注意:这里在测试时,我们使用一个新的tomcat,进行测试,后面再对其进行优化 调整,再测试。<br>1.2.1、创建dashboard数据库<br>在资料中找到sql脚本文件dashboard.sql,在linux服务器上执行。</p><p>cat dashboard.sql | mysql ‐uroot ‐proot<br>1<br>创建完成后,可以看到有3张表。<br>1.2.2、部署web应用<br>在资料中找到itcat-dashboard-web.war,上传到linux服务器,进行部署安装。</p><p>cd /tmp/apache‐tomcat‐8.5.34/webapps<br>rm ‐rf *<br>mkdir ROOT<br> cd ROOT/<br>rz上传war包<br>jar ‐xvf itcat‐dashboard‐web.war rm ‐rf itcat‐dashboard‐web.war</p><h1 id="修改数据库配置文件"><a href="#修改数据库配置文件" class="headerlink" title="修改数据库配置文件"></a>修改数据库配置文件</h1><p>cd /tmp/apache‐tomcat‐8.5.34/webapps/ROOT/WEB‐INF/classes vim jdbc.properties</p><h1 id="这里根据自己的实际情况进行配置"><a href="#这里根据自己的实际情况进行配置" class="headerlink" title="这里根据自己的实际情况进行配置"></a>这里根据自己的实际情况进行配置</h1><p>jdbc.driverClassName=com.mysql.jdbc.Driver<br>jdbc.url=jdbc:mysql://node01:3306/dashboard? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueri es=true<br>jdbc.username=root</p><p>jdbc.password=root<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>重新启动tomcat。<br>访问首页,查看是否已经启动成功:<a href="http://192.168.40.133:8080/index" target="_blank" rel="noopener">http://192.168.40.133:8080/index</a></p><p>1.3、使用Apache JMeter进行测试<br>Apache Jmeter是开源的压力测试工具,我们借助于此工具进行测试,将测试出tomcat<br>的吞吐量等信息。</p><p>1.3.1、下载安装<br>下载地址:<a href="http://jmeter.apache.org/download_jmeter.cgi安装:直接将下载好的zip压缩包进行解压即可。进入bin目录,找到jmeter.bat文件,双机打开即可启动。1.3.2、修改主题和语言" target="_blank" rel="noopener">http://jmeter.apache.org/download_jmeter.cgi安装:直接将下载好的zip压缩包进行解压即可。进入bin目录,找到jmeter.bat文件,双机打开即可启动。1.3.2、修改主题和语言</a><br>默认的主题是黑色风格的主题并且语言是英语,这样不太方便使用,所以需要修改下主题和中文语言。主题修改完成。</p><p>接下来设置语言为简体中文。1.3.3、创建首页的测试用例<br>第一步:保存测试用例<br>第二步:添加线程组,使用线程模拟用户的并发<br>1000个线程,每个线程循环10次,也就是tomcat会接收到10000个请求。<br>第三步:添加http请求第四步:添加请求监控1.3.4、启动、进行测试1.3.5、聚合报告<br>在聚合报告中,重点看吞吐量。</p><p>1.4、调整tomcat参数进行优化<br>通过上面测试可以看出,tomcat在不做任何调整时,吞吐量为73次/秒。<br>1.4.1、禁用AJP服务<br>可以看到,禁用AJP服务后,吞吐量会有所提升。<br>当然了,测试不一定准确,需要多测试几次才能看出是否有提升。</p><p>1.4.2、设置线程池<br>通过设置线程池,调整线程池相关的参数进行测试tomcat的性能。<br>1.4.2.1、最大线程数为500,初始为50</p><p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="500" minsparethreads="50" prestartminsparethreads="true"><br>1<br>2<br>测试结果:</executor></p><p>吞吐量为128次/秒,性能有所提升。<br>1.4.2.2、最大线程数为1000,初始为200</p><p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="1000" minsparethreads="200" prestartminsparethreads="true"><br>1<br>2</executor></p><p>吞吐量为151,性能有所提升。<br>1.4.2.3、最大线程数为5000,初始为1000<br>是否是线程数最多,速度越快呢? 我们来测试下。</p><p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="5000" minsparethreads="1000" prestartminsparethreads="true"><br>1<br>2</executor></p><p>可以看到,虽然最大线程已经设置到5000,但是实际测试效果并不理想,并且平均的响 应时间也边长了,所以单纯靠提升线程数量是不能一直得到性能提升的。</p><p>1.4.2.4、设置最大等待队列数<br>默认情况下,请求发送到tomcat,如果tomcat正忙,那么该请求会一直等待。这样虽然 可以保证每个请求都能请求到,但是请求时间就会边长。</p><p>有些时候,我们也不一定要求请求一定等待,可以设置最大等待队列大小,如果超过就不等待了。这样虽然有些请求是失败的,但是请求时间会虽短。典型的应用:12306。</p><p><!‐‐最大等待数为100‐‐></p><p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="500" minsparethreads="100" prestartminsparethreads="true" maxqueuesize="100"><br>1<br>2<br>3<br>4<br>5<br>6<br>7</executor></p><p>测试结果:</p><p>平均响应时间:3.1秒响应时间明显缩短<br>错误率:49.88%<br>错误率提升到一半,也可以理解,最大线程为500,测试的并发为1000 吞吐量:238次/秒<br>吞吐量明显提升<br>结论:响应时间、吞吐量这2个指标需要找到平衡才能达到更好的性能。<br>1.4.3、设置nio2的运行模式<br>将最大线程设置为500进行测试:</p><executor name="tomcatThreadPool" nameprefix="catalina‐exec‐" maxthreads="500" minsparethreads="50" prestartminsparethreads="true"><p><!‐‐ 设置nio2 ‐‐></p><connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectiontimeout="20000" redirectport="8443"><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8</p><p>可以看到,平均响应时间有缩短,吞吐量有提升,可以得出结论:nio2的性能要高于<br>nio。</p><p>1.5、调整JVM参数进行优化<br>接下来,测试通过jvm参数进行优化,为了测试一致性,依然将最大线程数设置为500, 启用nio2运行模式。<br>1.5.1、设置并行垃圾回收器</p><h1 id="年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M-JAVA-OPTS-”‐XX-UseParallelGC-‐XX-UseParallelOldGC-‐Xms64m-‐Xmx512m-‐-XX-PrintGCDetails-‐XX-PrintGCTimeStamps-‐XX-PrintGCDateStamps-‐-XX-PrintHeapAtGC-‐Xloggc-logs-gc-log”"><a href="#年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M-JAVA-OPTS-”‐XX-UseParallelGC-‐XX-UseParallelOldGC-‐Xms64m-‐Xmx512m-‐-XX-PrintGCDetails-‐XX-PrintGCTimeStamps-‐XX-PrintGCDateStamps-‐-XX-PrintHeapAtGC-‐Xloggc-logs-gc-log”" class="headerlink" title="年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M JAVA_OPTS=”‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐Xms64m ‐Xmx512m ‐ XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐ XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log”"></a>年轻代、老年代均使用并行收集器,初始堆内存64M,最大堆内存512M JAVA_OPTS=”‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐Xms64m ‐Xmx512m ‐ XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐ XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log”</h1><p>1</p><p>测试结果与默认的JVM参数结果接近。(执行了2次测试,结果是第二次测试的结果)<br>1.5.2、查看gc日志文件<br>将gc.log文件上传到gceasy.io查看gc中是否存在问题。在报告中显示,在5次GC时,系统所消耗的时间大于用户时间,这反应出的服务器的性能存在瓶颈,调度CPU等资源所消耗的时间要长一些。<br>问题二:<br>可以关键指标中可以看出,吞吐量表现不错,但是gc时,线程的暂停时间稍有点长。<br>问题三:通过GC的统计可以看出:<br>年轻代的gc有74次,次数稍有多,说明年轻代设置的大小不合适需要调整<br>FullGC有8次,说明堆内存的大小不合适,需要调整<br>问题四:<br>从GC原因的可以看出,年轻代大小设置不合理,导致了多次GC。<br>1.5.3、调整年轻代大小</p><p>JAVA_OPTS=”‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐Xms128m ‐Xmx1024m ‐ XX:NewSize=64m ‐XX:MaxNewSize=256m ‐XX:+PrintGCDetails ‐ XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐ Xloggc:../logs/gc.log”<br>1<br>将初始堆大小设置为128m,最大为1024m<br>初始年轻代大小64m,年轻代最大256m<br>从测试结果来看,吞吐量以及响应时间均有提升。<br>查看gc日志:</p><p>可以看到GC次数要明显减少,说明调整是有效的。<br>1.5.4、设置G1垃圾回收器</p><h1 id="设置了最大停顿时间100毫秒,初始堆内存128m,最大堆内存1024m-JAVA-OPTS-”‐XX-UseG1GC-‐XX-MaxGCPauseMillis-100-‐Xms128m-‐Xmx1024m-‐-XX-PrintGCDetails-‐XX-PrintGCTimeStamps-‐XX-PrintGCDateStamps-‐-XX-PrintHeapAtGC-‐Xloggc-logs-gc-log”"><a href="#设置了最大停顿时间100毫秒,初始堆内存128m,最大堆内存1024m-JAVA-OPTS-”‐XX-UseG1GC-‐XX-MaxGCPauseMillis-100-‐Xms128m-‐Xmx1024m-‐-XX-PrintGCDetails-‐XX-PrintGCTimeStamps-‐XX-PrintGCDateStamps-‐-XX-PrintHeapAtGC-‐Xloggc-logs-gc-log”" class="headerlink" title="设置了最大停顿时间100毫秒,初始堆内存128m,最大堆内存1024m JAVA_OPTS=”‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xms128m ‐Xmx1024m ‐ XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐ XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log”"></a>设置了最大停顿时间100毫秒,初始堆内存128m,最大堆内存1024m JAVA_OPTS=”‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xms128m ‐Xmx1024m ‐ XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐ XX:+PrintHeapAtGC ‐Xloggc:../logs/gc.log”</h1><p>1<br>测试结果:</p><p>可以看到,吞吐量有所提升,评价响应时间也有所缩短。</p><p>1.5.5、小结<br>通过上述的测试,可以总结出,对tomcat性能优化就是需要不断的进行调整参数,然后 测试结果,可能会调优也可能会调差,这时就需要借助于gc的可视化工具来看gc的情 况。再帮我我们做出决策应该调整哪些参数。</p><p>2、JVM字节码<br>前面我们通过tomcat本身的参数以及jvm的参数对tomcat做了优化,其实要想将应用程 序跑的更快、效率更高,除了对tomcat容器以及jvm优化外,应用程序代码本身如果写 的效率不高的,那么也是不行的,所以,对于程序本身的优化也就很重要了。</p><p>对于程序本身的优化,可以借鉴很多前辈们的经验,但是有些时候,在从源码角度方面 分析的话,不好鉴别出哪个效率高,如对字符串拼接的操作,是直接“+”号拼接效率高还 是使用StringBuilder效率高?<br>这个时候,就需要通过查看编译好的class文件中字节码,就可以找到答案。<br>我们都知道,java编写应用,需要先通过javac命令编译成class文件,再通过jvm执行,<br>jvm执行时是需要将class文件中的字节码载入到jvm进行运行的。</p><p>2.1、通过javap命令查看class文件的字节码内容<br>首先,看一个简单的Test1类的代码:</p><p>package cn.itcast.jvm;<br>public class Test1 {<br>public static void main(String[] args) {<br>int a = 2; int b = 5; int c = b ‐ a;<br>System.out.println(c);<br>}<br>}<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>通过javap命令查看class文件中的字节码内容:</p><p>javap ‐v Test1.class > Test1.txt<br>javap用法: javap <options> <classes><br>其中, 可能的选项包括:<br>‐help ‐‐help ‐?<br>‐version<br>‐v ‐verbose<br>‐l<br>‐public<br>‐protected<br>‐package<br>‐p ‐private<br>‐c<br>‐s<br>‐sysinfo</classes></options></p><p>‐constants<br>‐classpath <path></path><br>‐cp <path></path><br>‐bootclasspath <path></path><br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>查看Test1.txt文件,内容如下:</p><p>Classfile /F:/code/itcast‐jvm/itcast‐jvm‐ test/target/classes/cn/itcast/jvm/Test1.class<br>Last modified 2018‐9‐27; size 577 bytes<br>MD5 checksum 4214859db3543c0c783ec8a216a4795f Compiled from “Test1.java”<br>public class cn.itcast.jvm.Test1 minor version: 0<br>major version: 52<br>flags: ACC_PUBLIC, ACC_SUPER<br>Constant pool:</p><h1 id="1-Methodref-5-23-java-lang-Object-”“-V"><a href="#1-Methodref-5-23-java-lang-Object-”“-V" class="headerlink" title="1 = Methodref #5.#23 // java/lang/Object.”“: ()V"></a>1 = Methodref #5.#23 // java/lang/Object.”<init>“: ()V</init></h1><h1 id="2-Fieldref-24-25-java-lang-System-out-Ljava-io-PrintStream"><a href="#2-Fieldref-24-25-java-lang-System-out-Ljava-io-PrintStream" class="headerlink" title="2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;"></a>2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;</h1><h1 id="3-Methodref-26-27-java-io-PrintStream-println-I-V"><a href="#3-Methodref-26-27-java-io-PrintStream-println-I-V" class="headerlink" title="3 = Methodref #26.#27 // java/io/PrintStream.println: (I)V"></a>3 = Methodref #26.#27 // java/io/PrintStream.println: (I)V</h1><h1 id="28-Utf8-cn-itcast-jvm-Test1"><a href="#28-Utf8-cn-itcast-jvm-Test1" class="headerlink" title="28 = Utf8 cn/itcast/jvm/Test1"></a>28 = Utf8 cn/itcast/jvm/Test1</h1><h1 id="29-Utf8-java-lang-Object"><a href="#29-Utf8-java-lang-Object" class="headerlink" title="29 = Utf8 java/lang/Object"></a>29 = Utf8 java/lang/Object</h1><h1 id="30-Utf8-java-lang-System"><a href="#30-Utf8-java-lang-System" class="headerlink" title="30 = Utf8 java/lang/System"></a>30 = Utf8 java/lang/System</h1><h1 id="31-Utf8-out"><a href="#31-Utf8-out" class="headerlink" title="31 = Utf8 out"></a>31 = Utf8 out</h1><h1 id="32-Utf8-Ljava-io-PrintStream"><a href="#32-Utf8-Ljava-io-PrintStream" class="headerlink" title="32 = Utf8 Ljava/io/PrintStream;"></a>32 = Utf8 Ljava/io/PrintStream;</h1><h1 id="33-Utf8-java-io-PrintStream"><a href="#33-Utf8-java-io-PrintStream" class="headerlink" title="33 = Utf8 java/io/PrintStream"></a>33 = Utf8 java/io/PrintStream</h1><h1 id="34-Utf8-println"><a href="#34-Utf8-println" class="headerlink" title="34 = Utf8 println"></a>34 = Utf8 println</h1><h1 id="35-Utf8-I-V"><a href="#35-Utf8-I-V" class="headerlink" title="35 = Utf8 (I)V"></a>35 = Utf8 (I)V</h1><p>{<br>public cn.itcast.jvm.Test1(); descriptor: ()V<br>flags: ACC_PUBLIC Code:<br>stack=1, locals=1, args_size=1 0: aload_0<br>1: invokespecial #1 // Method<br>java/lang/Object.”<init>“:()V<br>4: return LineNumberTable:<br>line 3: 0 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 5 0 this Lcn/itcast/jvm/Test1;</init></p><p>public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V<br>flags: ACC_PUBLIC, ACC_STATIC<br>Code:<br>stack=2, locals=4, args_size=1 0: iconst_2<br>1: istore_1<br>2: iconst_5<br>3: istore_2<br>4: iload_2<br>5: iload_1<br>6: isub<br>7: istore_3<br>8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;<br>11: iload_3</p><p>12: invokevirtual #3 // Method<br>java/io/PrintStream.println:(I)V<br>15: return LineNumberTable:<br>line 6: 0<br>line 7: 2<br>line 8: 4<br>line 9: 8<br>line 10: 15<br>LocalVariableTable:<br>Start<br>0 Length<br>16 Slot<br>0 Name<br>args Signature<br>[Ljava/lang/String;<br>2 14 1 a I<br>4 12 2 b I<br>8 8 3 c I<br>}<br>SourceFile: “Test1.java”<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>47<br>48<br>49<br>50<br>51<br>52<br>53<br>54<br>55<br>56<br>57<br>58<br>59<br>60<br>61<br>62<br>63<br>内容大致分为4个部分:<br>第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。第二部分:显示了该类中所涉及到常量池,共35个常量。<br>第三部分:显示该类的构造器,编译器自动插入的。</p><p>第四部分:显示了main方的信息。(这个是需要我们重点关注的)</p><p>2.2、常量池<br>官网文档:</p><p><a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140</a></p><p>Constant Type Value 说明<br>CONSTANT_Class 7 类或接口的符号引用<br>CONSTANT_Fieldref 9 字段的符号引用<br>CONSTANT_Methodref 10 类中方法的符号引用<br>CONSTANT_InterfaceMethodref 11 接口中方法的符号引用<br>CONSTANT_String 8 字符串类型常量<br>CONSTANT_Integer 3 整形常量<br>CONSTANT_Float 4 浮点型常量<br>CONSTANT_Long 5 长整型常量<br>CONSTANT_Double 6 双精度浮点型常量<br>CONSTANT_NameAndType 12 字段或方法的符号引用<br>CONSTANT_Utf8 1 UTF-8编码的字符串<br>CONSTANT_MethodHandle 15 表示方法句柄<br>CONSTANT_MethodType 16 标志方法类型<br>CONSTANT_InvokeDynamic 18 表示一个动态方法调用点<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>2.3、描述符<br>2.3.1、字段描述符<br>官网:<a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2</a></p><p>FieldType term<br>Type<br>Interpretation<br>B byte signed byte</p><p>C<br>char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16<br>D double double-precision floating-point value<br>F float single-precision floating-point value<br>I int integer<br>J long long integer<br>LClassName; reference an instance of class ClassName<br>S short signed short<br>Z boolean true or false<br>[ reference one array dimension<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>2.3.2、方法描述符<br>官网:<a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3</a></p><p>示例:</p><p>The method descriptor for the method:</p><p>Object m(int i, double d, Thread t) {…}<br>1<br>is:</p><p>(IDLjava/lang/Thread;)Ljava/lang/Object;<br>1<br>2.4、解读方法字节码</p><p>public static void main(java.lang.String[]);<br>descriptor: ([Ljava/lang/String;)V //方法描述,V表示该方法的放回值为void<br>flags: ACC_PUBLIC, ACC_STATIC // 方法修饰符,public、static的<br>Code:<br>// stack=2,操作栈的大小为2、locals=4,本地变量表大小,args_size=1, 参数</p><p>的个数</p><p>stack=2, locals=4, args_size=1<br>0: iconst_2 //将数字2值压入操作栈,位于栈的最上面<br>1: istore_1 //从操作栈中弹出一个元素(数字2),放入到本地变量表中,位</p><p>于下标为1的位置(下标为0的是this)<br>2: iconst_5 //将数字5值压入操作栈,位于栈的最上面<br>3: istore_2 //从操作栈中弹出一个元素(5),放入到本地变量表中,位于第下标为2个位置<br>4: iload_2 //将本地变量表中下标为2的位置元素压入操作栈(5)<br>5: iload_1 //将本地变量表中下标为1的位置元素压入操作栈(2)<br>6: isub //操作栈中的2个数字相减<br>7: istore_3 // 将相减的结果压入到本地本地变量表中,位于下标为3的位置<br>// 通过#2号找到对应的常量,即可找到对应的引用<br>8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;<br>11: iload_3 //将本地变量表中下标为3的位置元素压入操作栈(3)<br>// 通过#3号找到对应的常量,即可找到对应的引用,进行方法调用<br>12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V<br>15: return //返回<br>LineNumberTable: //行号的列表</p><p>line 6: 0<br>line 7: 2<br>line 8: 4<br>line 9: 8<br>line 10: 15 LocalVariableTable: // 本地变量表<br>Start<br>0 Length<br>16 Slot<br>0 Name<br>args Signature<br>[Ljava/lang/String;<br>2 14 1 a I<br>4 12 2 b I<br>8 8 3 c I<br>}</p><p>SourceFile: “Test1.java”</p><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>2.4.1、图解</p><p>2.5、研究 i++ 与 ++i 的不同<br>我们都知道,i++表示,先返回再+1,++i表示,先+1再返回。它的底层是怎么样的呢? 我们一起探究下。</p><p>编写测试代码:</p><p>public class Test2 {</p><p>public static void main(String[] args) {<br>new Test2().method1();<br> new Test2().method2();<br>}<br>public void method1(){<br>int i = 1; int a = i++;<br>System.out.println(a); //打印1<br>}<br>public void method2(){<br>int i = 1; int a = ++i;<br>System.out.println(a);//打印2<br>}<br>}<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>2.5.1、查看class字节码</p><p>Classfile /F:/code/itcast‐jvm/itcast‐jvm‐ test/target/classes/cn/itcast/jvm/Test2.class<br>MD5 checksum 901660fc11c43b6daadd0942150960ed Compiled from “Test2.java”<br>public class cn.itcast.jvm.Test2 minor version: 0<br>major version: 52<br>flags: ACC_PUBLIC, ACC_SUPER<br>Constant pool:</p><h1 id="1-Methodref-8-27-java-lang-Object-”“-V"><a href="#1-Methodref-8-27-java-lang-Object-”“-V" class="headerlink" title="1 = Methodref #8.#27 // java/lang/Object.”“: ()V"></a>1 = Methodref #8.#27 // java/lang/Object.”<init>“: ()V</init></h1><h1 id="2-Class-28-cn-itcast-jvm-Test2"><a href="#2-Class-28-cn-itcast-jvm-Test2" class="headerlink" title="2 = Class #28 // cn/itcast/jvm/Test2"></a>2 = Class #28 // cn/itcast/jvm/Test2</h1><h1 id="3-Methodref-2-27-cn-itcast-jvm-Test2-”"><a href="#3-Methodref-2-27-cn-itcast-jvm-Test2-”" class="headerlink" title="3 = Methodref #2.#27 // cn/itcast/jvm/Test2.”"></a>3 = Methodref #2.#27 // cn/itcast/jvm/Test2.”</h1><p><init>“:()V</init></p><h1 id="4-Methodref-2-29-cn-itcast-jvm-Test2-method1-V"><a href="#4-Methodref-2-29-cn-itcast-jvm-Test2-method1-V" class="headerlink" title="4 = Methodref #2.#29 // cn/itcast/jvm/Test2.method1: ()V"></a>4 = Methodref #2.#29 // cn/itcast/jvm/Test2.method1: ()V</h1><h1 id="5-Methodref-2-30-cn-itcast-jvm-Test2-method2-V"><a href="#5-Methodref-2-30-cn-itcast-jvm-Test2-method2-V" class="headerlink" title="5 = Methodref #2.#30 // cn/itcast/jvm/Test2.method2: ()V"></a>5 = Methodref #2.#30 // cn/itcast/jvm/Test2.method2: ()V</h1><h1 id="6-Fieldref-31-32-java-lang-System-out-Ljava-io-PrintStream"><a href="#6-Fieldref-31-32-java-lang-System-out-Ljava-io-PrintStream" class="headerlink" title="6 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;"></a>6 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;</h1><h1 id="7-Methodref-33-34-java-io-PrintStream-println-I-V"><a href="#7-Methodref-33-34-java-io-PrintStream-println-I-V" class="headerlink" title="7 = Methodref #33.#34 // java/io/PrintStream.println: (I)V"></a>7 = Methodref #33.#34 // java/io/PrintStream.println: (I)V</h1><h1 id="26-Utf8-Test2-java"><a href="#26-Utf8-Test2-java" class="headerlink" title="26 = Utf8 Test2.java"></a>26 = Utf8 Test2.java</h1><h1 id="27-NameAndType-9-10-““-V"><a href="#27-NameAndType-9-10-““-V" class="headerlink" title="27 = NameAndType #9:#10 // ““:()V"></a>27 = NameAndType #9:#10 // “<init>“:()V</init></h1><h1 id="28-Utf8-cn-itcast-jvm-Test2"><a href="#28-Utf8-cn-itcast-jvm-Test2" class="headerlink" title="28 = Utf8 cn/itcast/jvm/Test2"></a>28 = Utf8 cn/itcast/jvm/Test2</h1><h1 id="29-NameAndType-20-10-method1-V"><a href="#29-NameAndType-20-10-method1-V" class="headerlink" title="29 = NameAndType #20:#10 // method1:()V"></a>29 = NameAndType #20:#10 // method1:()V</h1><h1 id="30-NameAndType-24-10-method2-V"><a href="#30-NameAndType-24-10-method2-V" class="headerlink" title="30 = NameAndType #24:#10 // method2:()V"></a>30 = NameAndType #24:#10 // method2:()V</h1><h1 id="31-Class-36-java-lang-System"><a href="#31-Class-36-java-lang-System" class="headerlink" title="31 = Class #36 // java/lang/System"></a>31 = Class #36 // java/lang/System</h1><h1 id="32-NameAndType-37-38-out-Ljava-io-PrintStream"><a href="#32-NameAndType-37-38-out-Ljava-io-PrintStream" class="headerlink" title="32 = NameAndType #37:#38 // out:Ljava/io/PrintStream;"></a>32 = NameAndType #37:#38 // out:Ljava/io/PrintStream;</h1><h1 id="33-Class-39-java-io-PrintStream"><a href="#33-Class-39-java-io-PrintStream" class="headerlink" title="33 = Class #39 // java/io/PrintStream"></a>33 = Class #39 // java/io/PrintStream</h1><h1 id="34-NameAndType-40-41-println-I-V"><a href="#34-NameAndType-40-41-println-I-V" class="headerlink" title="34 = NameAndType #40:#41 // println:(I)V"></a>34 = NameAndType #40:#41 // println:(I)V</h1><h1 id="35-Utf8-java-lang-Object"><a href="#35-Utf8-java-lang-Object" class="headerlink" title="35 = Utf8 java/lang/Object"></a>35 = Utf8 java/lang/Object</h1><h1 id="36-Utf8-java-lang-System"><a href="#36-Utf8-java-lang-System" class="headerlink" title="36 = Utf8 java/lang/System"></a>36 = Utf8 java/lang/System</h1><h1 id="37-Utf8-out"><a href="#37-Utf8-out" class="headerlink" title="37 = Utf8 out"></a>37 = Utf8 out</h1><h1 id="38-Utf8-Ljava-io-PrintStream"><a href="#38-Utf8-Ljava-io-PrintStream" class="headerlink" title="38 = Utf8 Ljava/io/PrintStream;"></a>38 = Utf8 Ljava/io/PrintStream;</h1><h1 id="39-Utf8-java-io-PrintStream"><a href="#39-Utf8-java-io-PrintStream" class="headerlink" title="39 = Utf8 java/io/PrintStream"></a>39 = Utf8 java/io/PrintStream</h1><h1 id="40-Utf8-println"><a href="#40-Utf8-println" class="headerlink" title="40 = Utf8 println"></a>40 = Utf8 println</h1><h1 id="41-Utf8-I-V"><a href="#41-Utf8-I-V" class="headerlink" title="41 = Utf8 (I)V"></a>41 = Utf8 (I)V</h1><p>{<br>public cn.itcast.jvm.Test2(); descriptor: ()V<br>flags: ACC_PUBLIC Code:<br>stack=1, locals=1, args_size=1 0: aload_0<br>1: invokespecial #1 // Method java/lang/Object.”<init>“:()V<br>4: return LineNumberTable:<br>line 3: 0 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 5 0 this Lcn/itcast/jvm/Test2;</init></p><p>public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V<br>flags: ACC_PUBLIC, ACC_STATIC<br>Code:<br>stack=2, locals=1, args_size=1<br>0: new #2 // class cn/itcast/jvm/Test2<br>3: dup</p><p>4: invokespecial #3 // Method “<init>“:()V<br>7: invokevirtual #4 // Method method1:()V<br>10: new #2 // class cn/itcast/jvm/Test2<br>13: dup<br>14: invokespecial #3 // Method “<init>“:()V<br>17: invokevirtual #5 // Method method2:()V<br>20: return LineNumberTable:<br>line 6: 0<br>line 7: 10<br>line 8: 20 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 21 0 args [Ljava/lang/String;</init></init></p><p>public void method1(); descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=2, locals=3, args_size=1 0: iconst_1<br>1: istore_1<br>2: iload_1<br>3: iinc 1, 1<br>6: istore_2<br>7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;<br>10: iload_2<br>11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V<br>14: return LineNumberTable:<br>line 11: 0<br>line 12: 2<br>line 13: 7<br>line 14: 14 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 15 0 this Lcn/itcast/jvm/Test2; 2 13 1 i I<br>7 8 2 a I</p><p>public void method2(); descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=2, locals=3, args_size=1 0: iconst_1<br>1: istore_1<br>2: iinc 1, 1<br>5: iload_1<br>6: istore_2<br>7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;<br>10: iload_2<br>11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V<br>14: return LineNumberTable:<br>line 17: 0<br>line 18: 2<br>line 19: 7<br>line 20: 14 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 15 0 this Lcn/itcast/jvm/Test2; 2 13 1 i I<br>7 8 2 a I<br>}<br>SourceFile: “Test2.java”</p><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>47<br>48<br>49<br>50<br>51<br>52<br>53<br>54<br>55<br>56<br>57<br>58<br>59<br>60<br>61<br>62<br>63<br>64<br>65<br>66<br>67<br>68<br>69<br>70<br>71<br>72<br>73<br>74<br>75<br>76<br>77<br>78<br>79<br>80<br>81<br>82<br>83<br>84<br>85<br>86<br>87<br>88<br>89<br>90<br>91<br>92<br>93<br>94<br>95<br>96<br>97<br>98<br>99<br>2.5.2、对比<br>i++:</p><p>0: iconst_1 //将数字1压入到操作栈<br>1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1<br>2: iload_1 //从本地变量表中获取下标为1的数据,压入到操作栈中<br>3: iinc 1, 1 // 将本地变量中的1,再+1<br>6: istore_2 // 将数字1从操作栈弹出,压入到本地变量表中,下标为2 7: getstatic #6 // Field<br>java/lang/System.out:Ljava/io/PrintStream;<br>10: iload_2 //从本地变量表中获取下标为2的数据,压入到操作栈中<br>11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V<br>14: return<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>++i:</p><p>0: iconst_1 //将数字1压入到操作栈<br>1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1<br>2: iinc 1, 1// 将本地变量中的1,再+1<br>5: iload_1 //从本地变量表中获取下标为1的数据(2),压入到操作栈中<br>6: istore_2 //将数字2从操作栈弹出,压入到本地变量表中,下标为2<br>7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;<br>10: iload_2 //从本地变量表中获取下标为2的数据(2),压入到操作栈中<br>11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V<br>14: return<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>区别:</p><p>i++<br>只是在本地变量中对数字做了相加,并没有将数据压入到操作栈<br>将前面拿到的数字1,再次从操作栈中拿到,压入到本地变量中<br>++i<br>将本地变量中的数字做了相加,并且将数据压入到操作栈将操作栈中的数据,再次压入到本地变量中<br>小结:可以通过查看字节码的方式对代码的底层做研究,探究其原理。</p><p>2.6、字符串拼接<br>字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:</p><p>+号拼接: str+“456”<br>StringBuilder拼接<br>StringBuffer拼接</p><p>StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到 线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。<br>那么,问题来了,StringBuilder和“+”号拼接,哪个效率高呢?接下来我们通过字节码的 方式进行探究。</p><p>首先,编写个示例:</p><p>package cn.itcast.jvm;<br>public class Test3 {<br>public static void main(String[] args) {<br>new Test3().m1();<br>new Test3().m2();<br>public void m1(){<br>String s1 = “123”; String s2 = “456”; String s3 = s1 + s2; System.out.println(s3);<br>}</p><p>public void m2(){<br>String s1 = “123”; String s2 = “456”;<br>StringBuilder sb = new StringBuilder(); sb.append(s1);<br>sb.append(s2);<br>String s3 = sb.toString(); System.out.println(s3);<br>}<br>}<br>}</p><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>查看Test3.class的字节码</p><p>Classfile /F:/code/itcast‐jvm/itcast‐jvm‐ test/target/classes/cn/itcast/jvm/Test3.class<br>MD5 checksum b3f7629e7e37768b9b5581be01df40d6 Compiled from “Test3.java”<br>public class cn.itcast.jvm.Test3 minor version: 0<br>major version: 52<br>flags: ACC_PUBLIC, ACC_SUPER<br>Constant pool:</p><h1 id="1-Methodref-14-36-java-lang-Object-”“-V"><a href="#1-Methodref-14-36-java-lang-Object-”“-V" class="headerlink" title="1 = Methodref #14.#36 // java/lang/Object.”“: ()V"></a>1 = Methodref #14.#36 // java/lang/Object.”<init>“: ()V</init></h1><h1 id="2-Class-37-cn-itcast-jvm-Test3"><a href="#2-Class-37-cn-itcast-jvm-Test3" class="headerlink" title="2 = Class #37 // cn/itcast/jvm/Test3"></a>2 = Class #37 // cn/itcast/jvm/Test3</h1><h1 id="3-Methodref-2-36-cn-itcast-jvm-Test3-”"><a href="#3-Methodref-2-36-cn-itcast-jvm-Test3-”" class="headerlink" title="3 = Methodref #2.#36 // cn/itcast/jvm/Test3.”"></a>3 = Methodref #2.#36 // cn/itcast/jvm/Test3.”</h1><p><init>“:()V</init></p><h1 id="4-Methodref-2-38-cn-itcast-jvm-Test3-m1-V"><a href="#4-Methodref-2-38-cn-itcast-jvm-Test3-m1-V" class="headerlink" title="4 = Methodref #2.#38 // cn/itcast/jvm/Test3.m1:()V"></a>4 = Methodref #2.#38 // cn/itcast/jvm/Test3.m1:()V</h1><h1 id="5-Methodref-2-39-cn-itcast-jvm-Test3-m2-V"><a href="#5-Methodref-2-39-cn-itcast-jvm-Test3-m2-V" class="headerlink" title="5 = Methodref #2.#39 // cn/itcast/jvm/Test3.m2:()V"></a>5 = Methodref #2.#39 // cn/itcast/jvm/Test3.m2:()V</h1><h1 id="6-String-40-123"><a href="#6-String-40-123" class="headerlink" title="6 = String #40 // 123"></a>6 = String #40 // 123</h1><h1 id="7-String-41-456"><a href="#7-String-41-456" class="headerlink" title="7 = String #41 // 456"></a>7 = String #41 // 456</h1><h1 id="8-Class-42-java-lang-StringBuilder"><a href="#8-Class-42-java-lang-StringBuilder" class="headerlink" title="8 = Class #42 // java/lang/StringBuilder"></a>8 = Class #42 // java/lang/StringBuilder</h1><h1 id="9-Methodref-8-36-java-lang-StringBuilder-”"><a href="#9-Methodref-8-36-java-lang-StringBuilder-”" class="headerlink" title="9 = Methodref #8.#36 // java/lang/StringBuilder.”"></a>9 = Methodref #8.#36 // java/lang/StringBuilder.”</h1><p><init>“:()V</init></p><h1 id="10-Methodref-8-43-java-lang-StringBuilder-append-Ljava-lang-String-Ljava-lang-StringBuilder"><a href="#10-Methodref-8-43-java-lang-StringBuilder-append-Ljava-lang-String-Ljava-lang-StringBuilder" class="headerlink" title="10 = Methodref #8.#43 // java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;"></a>10 = Methodref #8.#43 // java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;</h1><h1 id="11-Methodref-8-44-java-lang-StringBuilder-toString-Ljava-lang-String"><a href="#11-Methodref-8-44-java-lang-StringBuilder-toString-Ljava-lang-String" class="headerlink" title="11 = Methodref #8.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String;"></a>11 = Methodref #8.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String;</h1><h1 id="12-Fieldref-45-46-java-lang-System-out-Ljava-io-PrintStream"><a href="#12-Fieldref-45-46-java-lang-System-out-Ljava-io-PrintStream" class="headerlink" title="12 = Fieldref #45.#46 // java/lang/System.out:Ljava/io/PrintStream;"></a>12 = Fieldref #45.#46 // java/lang/System.out:Ljava/io/PrintStream;</h1><h1 id="13-Methodref-47-48-java-io-PrintStream-println-Ljava-lang-String-V"><a href="#13-Methodref-47-48-java-io-PrintStream-println-Ljava-lang-String-V" class="headerlink" title="13 = Methodref #47.#48 // java/io/PrintStream.println: (Ljava/lang/String;)V"></a>13 = Methodref #47.#48 // java/io/PrintStream.println: (Ljava/lang/String;)V</h1><h1 id="14-Class-49-java-lang-Object"><a href="#14-Class-49-java-lang-Object" class="headerlink" title="14 = Class #49 // java/lang/Object"></a>14 = Class #49 // java/lang/Object</h1><h1 id="15-Utf8"><a href="#15-Utf8" class="headerlink" title="15 = Utf8 "></a>15 = Utf8 <init></init></h1><h1 id="16-Utf8-V"><a href="#16-Utf8-V" class="headerlink" title="16 = Utf8 ()V"></a>16 = Utf8 ()V</h1><h1 id="17-Utf8-Code"><a href="#17-Utf8-Code" class="headerlink" title="17 = Utf8 Code"></a>17 = Utf8 Code</h1><h1 id="18-Utf8-LineNumberTable"><a href="#18-Utf8-LineNumberTable" class="headerlink" title="18 = Utf8 LineNumberTable"></a>18 = Utf8 LineNumberTable</h1><h1 id="19-Utf8-LocalVariableTable"><a href="#19-Utf8-LocalVariableTable" class="headerlink" title="19 = Utf8 LocalVariableTable"></a>19 = Utf8 LocalVariableTable</h1><h1 id="20-Utf8-this"><a href="#20-Utf8-this" class="headerlink" title="20 = Utf8 this"></a>20 = Utf8 this</h1><h1 id="21-Utf8-Lcn-itcast-jvm-Test3"><a href="#21-Utf8-Lcn-itcast-jvm-Test3" class="headerlink" title="21 = Utf8 Lcn/itcast/jvm/Test3;"></a>21 = Utf8 Lcn/itcast/jvm/Test3;</h1><h1 id="22-Utf8-main"><a href="#22-Utf8-main" class="headerlink" title="22 = Utf8 main"></a>22 = Utf8 main</h1><h1 id="23-Utf8-Ljava-lang-String-V"><a href="#23-Utf8-Ljava-lang-String-V" class="headerlink" title="23 = Utf8 ([Ljava/lang/String;)V"></a>23 = Utf8 ([Ljava/lang/String;)V</h1><h1 id="24-Utf8-args"><a href="#24-Utf8-args" class="headerlink" title="24 = Utf8 args"></a>24 = Utf8 args</h1><h1 id="25-Utf8-Ljava-lang-String"><a href="#25-Utf8-Ljava-lang-String" class="headerlink" title="25 = Utf8 [Ljava/lang/String;"></a>25 = Utf8 [Ljava/lang/String;</h1><h1 id="26-Utf8-m1"><a href="#26-Utf8-m1" class="headerlink" title="26 = Utf8 m1"></a>26 = Utf8 m1</h1><h1 id="27-Utf8-s1"><a href="#27-Utf8-s1" class="headerlink" title="27 = Utf8 s1"></a>27 = Utf8 s1</h1><h1 id="28-Utf8-Ljava-lang-String"><a href="#28-Utf8-Ljava-lang-String" class="headerlink" title="28 = Utf8 Ljava/lang/String;"></a>28 = Utf8 Ljava/lang/String;</h1><h1 id="29-Utf8-s2"><a href="#29-Utf8-s2" class="headerlink" title="29 = Utf8 s2"></a>29 = Utf8 s2</h1><h1 id="30-Utf8-s3"><a href="#30-Utf8-s3" class="headerlink" title="30 = Utf8 s3"></a>30 = Utf8 s3</h1><h1 id="31-Utf8-m2"><a href="#31-Utf8-m2" class="headerlink" title="31 = Utf8 m2"></a>31 = Utf8 m2</h1><h1 id="32-Utf8-sb"><a href="#32-Utf8-sb" class="headerlink" title="32 = Utf8 sb"></a>32 = Utf8 sb</h1><h1 id="33-Utf8-Ljava-lang-StringBuilder"><a href="#33-Utf8-Ljava-lang-StringBuilder" class="headerlink" title="33 = Utf8 Ljava/lang/StringBuilder;"></a>33 = Utf8 Ljava/lang/StringBuilder;</h1><h1 id="34-Utf8-SourceFile"><a href="#34-Utf8-SourceFile" class="headerlink" title="34 = Utf8 SourceFile"></a>34 = Utf8 SourceFile</h1><h1 id="35-Utf8-Test3-java"><a href="#35-Utf8-Test3-java" class="headerlink" title="35 = Utf8 Test3.java"></a>35 = Utf8 Test3.java</h1><h1 id="36-NameAndType-15-16-““-V"><a href="#36-NameAndType-15-16-““-V" class="headerlink" title="36 = NameAndType #15:#16 // ““:()V"></a>36 = NameAndType #15:#16 // “<init>“:()V</init></h1><h1 id="37-Utf8-cn-itcast-jvm-Test3"><a href="#37-Utf8-cn-itcast-jvm-Test3" class="headerlink" title="37 = Utf8 cn/itcast/jvm/Test3"></a>37 = Utf8 cn/itcast/jvm/Test3</h1><h1 id="38-NameAndType-26-16-m1-V"><a href="#38-NameAndType-26-16-m1-V" class="headerlink" title="38 = NameAndType #26:#16 // m1:()V"></a>38 = NameAndType #26:#16 // m1:()V</h1><h1 id="39-NameAndType-31-16-m2-V"><a href="#39-NameAndType-31-16-m2-V" class="headerlink" title="39 = NameAndType #31:#16 // m2:()V"></a>39 = NameAndType #31:#16 // m2:()V</h1><h1 id="40-Utf8-123"><a href="#40-Utf8-123" class="headerlink" title="40 = Utf8 123"></a>40 = Utf8 123</h1><h1 id="41-Utf8-456"><a href="#41-Utf8-456" class="headerlink" title="41 = Utf8 456"></a>41 = Utf8 456</h1><h1 id="42-Utf8-java-lang-StringBuilder"><a href="#42-Utf8-java-lang-StringBuilder" class="headerlink" title="42 = Utf8 java/lang/StringBuilder"></a>42 = Utf8 java/lang/StringBuilder</h1><h1 id="43-NameAndType-50-51-append"><a href="#43-NameAndType-50-51-append" class="headerlink" title="43 = NameAndType #50:#51 // append:"></a>43 = NameAndType #50:#51 // append:</h1><p>(Ljava/lang/String;)Ljava/lang/StringBuilder;</p><h1 id="44-NameAndType-52-53-toString"><a href="#44-NameAndType-52-53-toString" class="headerlink" title="44 = NameAndType #52:#53 // toString:"></a>44 = NameAndType #52:#53 // toString:</h1><p>()Ljava/lang/String; </p><h1 id="45-Class-54-java-lang-System"><a href="#45-Class-54-java-lang-System" class="headerlink" title="45 = Class #54 // java/lang/System"></a>45 = Class #54 // java/lang/System</h1><h1 id="46-NameAndType-55-56-out-Ljava-io-PrintStream"><a href="#46-NameAndType-55-56-out-Ljava-io-PrintStream" class="headerlink" title="46 = NameAndType #55:#56 // out:Ljava/io/PrintStream;"></a>46 = NameAndType #55:#56 // out:Ljava/io/PrintStream;</h1><h1 id="47-Class-57-java-io-PrintStream"><a href="#47-Class-57-java-io-PrintStream" class="headerlink" title="47 = Class #57 // java/io/PrintStream"></a>47 = Class #57 // java/io/PrintStream</h1><h1 id="48-NameAndType-58-59-println"><a href="#48-NameAndType-58-59-println" class="headerlink" title="48 = NameAndType #58:#59 // println:"></a>48 = NameAndType #58:#59 // println:</h1><p>(Ljava/lang/String;)V</p><pre><code>#49 = Utf8 java/lang/Object#50 = Utf8 append#51 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;#52 = Utf8 toString#53 = Utf8 ()Ljava/lang/String;#54 = Utf8 java/lang/System#55 = Utf8 out#56 = Utf8 Ljava/io/PrintStream;#57 = Utf8 java/io/PrintStream#58 = Utf8 println#59 = Utf8 (Ljava/lang/String;)V</code></pre><p>{<br>public cn.itcast.jvm.Test3();<br>descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=1, locals=1, args_size=1 0: aload_0<br>1: invokespecial #1 // Method java/lang/Object.”<init>“:()V<br>4: return LineNumberTable:<br>line 3: 0 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 5 0 this Lcn/itcast/jvm/Test3;</init></p><p>public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V<br>flags: ACC_PUBLIC, ACC_STATIC<br>Code:<br>stack=2, locals=1, args_size=1<br>0: new #2 // class cn/itcast/jvm/Test3<br>3: dup<br>4: invokespecial #3 // Method “<init>“:()V<br>7: invokevirtual #4 // Method m1:()V<br>10: new #2 // class cn/itcast/jvm/Test3<br>13: dup<br>14: invokespecial #3 // Method “<init>“:()V<br>17: invokevirtual #5 // Method m2:()V<br>20: return LineNumberTable:<br>line 6: 0<br>line 7: 10<br>line 8: 20 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 21 0 args [Ljava/lang/String;</init></init></p><p>public void m1(); descriptor: ()V<br>flags: ACC_PUBLIC<br>Code:<br>stack=2, locals=4, args_size=1</p><p>0: ldc #6 // String 123<br>2: astore_1<br>3: ldc #7 // String 456<br>5: astore_2<br>6: new #8 // class<br>java/lang/StringBuilder 9: dup<br>10: invokespecial #9 // Method java/lang/StringBuilder.”<init>“:()V<br>13: aload_1<br>14: invokevirtual #10 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;<br>17: aload_2<br>18: invokevirtual #10 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;<br>21: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;<br>24: astore_3<br>25: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;<br>28: aload_3<br>29: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V<br>32: return LineNumberTable:<br>line 11: 0<br>line 12: 3<br>line 13: 6<br>line 14: 25<br>line 15: 32 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 33 0 this Lcn/itcast/jvm/Test3;<br>3 30 1 s1 Ljava/lang/String;<br>6 27 2 s2 Ljava/lang/String;<br>25 8 3 s3 Ljava/lang/String;<br>public void m2(); descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=2, locals=5, args_size=1<br>0: ldc #6 // String 123<br>2: astore_1<br>3: ldc #7 // String 456<br>5: astore_2<br>6: new #8 // class java/lang/StringBuilder<br>9: dup<br>10: invokespecial #9 // Method java/lang/StringBuilder.”<init>“:()V<br>13: astore_3<br>14: aload_3<br>15: aload_1<br>16: invokevirtual #10 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;<br>19: pop<br>20: aload_3<br>21: aload_2<br>22: invokevirtual #10 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;<br>25: pop<br>26: aload_3<br>27: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;<br>30: astore 4<br>32: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;<br>35: aload 4<br>37: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V<br>40: return LineNumberTable:<br>line 18: 0<br>line 19: 3</init></init></p><p>line 20: 6<br>LocalVariableTable:<br>}<br>SourceFile: “Test3.java”</p><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>47<br>48<br>49<br>50<br>51<br>52<br>53<br>54<br>55<br>56<br>57<br>58<br>59<br>60<br>61<br>62<br>63<br>64<br>65<br>66<br>67<br>68<br>69<br>70<br>71<br>72<br>73<br>74<br>75<br>76<br>77<br>78<br>79<br>80<br>81<br>82<br>83<br>84<br>85<br>86<br>87<br>88<br>89<br>90<br>91<br>92<br>93<br>94<br>95<br>96<br>97<br>98<br>99<br>100<br>101<br>102<br>103<br>104<br>105<br>106<br>107<br>108<br>109<br>110<br>111<br>112<br>113<br>114<br>115<br>116<br>117<br>118<br>119<br>120<br>121<br>122<br>123<br>124<br>125<br>126<br>127<br>128<br>129<br>130<br>131<br>132<br>133<br>134<br>135<br>136<br>137<br>138<br>139<br>140<br>141<br>142<br>143<br>144<br>145<br>146<br>147<br>148<br>149<br>150<br>151<br>152<br>153<br>154<br>155<br>156<br>157<br>158<br>159<br>160<br>161<br>162<br>163<br>164<br>165<br>从解字节码中可以看出,m1()方法源码中是使用+号拼接,但是在字节码中也被编译成了<br>StringBuilder方式。<br>所以,可以得出结论,字符串拼接,+号和StringBuilder是相等的,效率一样。<br>接下来,我们再看一个案例:</p><p>package cn.itcast.jvm; public class Test4 {<br>public static void main(String[] args) { new Test4().m1();<br>new Test4().m2();<br>}</p><p>public void m1(){ String str = “”;<br>for (int i = 0; i < 5; i++) { str = str + i;<br>}<br>System.out.println(str);<br>}</p><p>public void m2(){<br>StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) {<br>sb.append(i);<br>}<br>System.out.println(sb.toString());<br>}<br>}<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>m1() 与 m2() 哪个方法的效率高? 依然是通过字节码的方式进行探究。</p><p>Classfile /F:/code/itcast‐jvm/itcast‐jvm‐ test/target/classes/cn/itcast/jvm/Test4.class<br>MD5 checksum f87a55446b8b6cd88b6e54bd5edcc9dc Compiled from “Test4.java”<br>public class cn.itcast.jvm.Test4 minor version: 0<br>major version: 52<br>flags: ACC_PUBLIC, ACC_SUPER<br>Constant pool:</p><h1 id="1-Methodref-14-39-java-lang-Object-”“-V"><a href="#1-Methodref-14-39-java-lang-Object-”“-V" class="headerlink" title="1 = Methodref #14.#39 // java/lang/Object.”“: ()V"></a>1 = Methodref #14.#39 // java/lang/Object.”<init>“: ()V</init></h1><h1 id="2-Class-40-cn-itcast-jvm-Test4"><a href="#2-Class-40-cn-itcast-jvm-Test4" class="headerlink" title="2 = Class #40 // cn/itcast/jvm/Test4"></a>2 = Class #40 // cn/itcast/jvm/Test4</h1><h1 id="3-Methodref-2-39-cn-itcast-jvm-Test4-”"><a href="#3-Methodref-2-39-cn-itcast-jvm-Test4-”" class="headerlink" title="3 = Methodref #2.#39 // cn/itcast/jvm/Test4.”"></a>3 = Methodref #2.#39 // cn/itcast/jvm/Test4.”</h1><p><init>“:()V</init></p><h1 id="4-Methodref-2-41-cn-itcast-jvm-Test4-m1-V"><a href="#4-Methodref-2-41-cn-itcast-jvm-Test4-m1-V" class="headerlink" title="4 = Methodref #2.#41 // cn/itcast/jvm/Test4.m1:()V"></a>4 = Methodref #2.#41 // cn/itcast/jvm/Test4.m1:()V</h1><h1 id="5-Methodref-2-42-cn-itcast-jvm-Test4-m2-V"><a href="#5-Methodref-2-42-cn-itcast-jvm-Test4-m2-V" class="headerlink" title="5 = Methodref #2.#42 // cn/itcast/jvm/Test4.m2:()V"></a>5 = Methodref #2.#42 // cn/itcast/jvm/Test4.m2:()V</h1><h1 id="6-String-43"><a href="#6-String-43" class="headerlink" title="6 = String #43 //"></a>6 = String #43 //</h1><h1 id="7-Class-44-java-lang-StringBuilder"><a href="#7-Class-44-java-lang-StringBuilder" class="headerlink" title="7 = Class #44 // java/lang/StringBuilder"></a>7 = Class #44 // java/lang/StringBuilder</h1><h1 id="8-Methodref-7-39-java-lang-StringBuilder-”"><a href="#8-Methodref-7-39-java-lang-StringBuilder-”" class="headerlink" title="8 = Methodref #7.#39 // java/lang/StringBuilder.”"></a>8 = Methodref #7.#39 // java/lang/StringBuilder.”</h1><p><init>“:()V</init></p><h1 id="9-Methodref-7-45-java-lang-StringBuilder-append-Ljava-lang-String-Ljava-lang-StringBuilder"><a href="#9-Methodref-7-45-java-lang-StringBuilder-append-Ljava-lang-String-Ljava-lang-StringBuilder" class="headerlink" title="9 = Methodref #7.#45 // java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;"></a>9 = Methodref #7.#45 // java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;</h1><h1 id="10-Methodref-7-46-java-lang-StringBuilder-append-I-Ljava-lang-StringBuilder"><a href="#10-Methodref-7-46-java-lang-StringBuilder-append-I-Ljava-lang-StringBuilder" class="headerlink" title="10 = Methodref #7.#46 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;"></a>10 = Methodref #7.#46 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;</h1><h1 id="11-Methodref-7-47-java-lang-StringBuilder-toString-Ljava-lang-String"><a href="#11-Methodref-7-47-java-lang-StringBuilder-toString-Ljava-lang-String" class="headerlink" title="11 = Methodref #7.#47 // java/lang/StringBuilder.toString:()Ljava/lang/String;"></a>11 = Methodref #7.#47 // java/lang/StringBuilder.toString:()Ljava/lang/String;</h1><h1 id="12-Fieldref-48-49-java-lang-System-out-Ljava-io-PrintStream"><a href="#12-Fieldref-48-49-java-lang-System-out-Ljava-io-PrintStream" class="headerlink" title="12 = Fieldref #48.#49 // java/lang/System.out:Ljava/io/PrintStream;"></a>12 = Fieldref #48.#49 // java/lang/System.out:Ljava/io/PrintStream;</h1><h1 id="13-Methodref-50-51-java-io-PrintStream-println-Ljava-lang-String-V"><a href="#13-Methodref-50-51-java-io-PrintStream-println-Ljava-lang-String-V" class="headerlink" title="13 = Methodref #50.#51 // java/io/PrintStream.println: (Ljava/lang/String;)V"></a>13 = Methodref #50.#51 // java/io/PrintStream.println: (Ljava/lang/String;)V</h1><h1 id="14-Class-52-java-lang-Object"><a href="#14-Class-52-java-lang-Object" class="headerlink" title="14 = Class #52 // java/lang/Object"></a>14 = Class #52 // java/lang/Object</h1><h1 id="15-Utf8-1"><a href="#15-Utf8-1" class="headerlink" title="15 = Utf8 "></a>15 = Utf8 <init></init></h1><h1 id="16-Utf8-V-1"><a href="#16-Utf8-V-1" class="headerlink" title="16 = Utf8 ()V"></a>16 = Utf8 ()V</h1><h1 id="17-Utf8-Code-1"><a href="#17-Utf8-Code-1" class="headerlink" title="17 = Utf8 Code"></a>17 = Utf8 Code</h1><h1 id="18-Utf8-LineNumberTable-1"><a href="#18-Utf8-LineNumberTable-1" class="headerlink" title="18 = Utf8 LineNumberTable"></a>18 = Utf8 LineNumberTable</h1><h1 id="19-Utf8-LocalVariableTable-1"><a href="#19-Utf8-LocalVariableTable-1" class="headerlink" title="19 = Utf8 LocalVariableTable"></a>19 = Utf8 LocalVariableTable</h1><h1 id="20-Utf8-this-1"><a href="#20-Utf8-this-1" class="headerlink" title="20 = Utf8 this"></a>20 = Utf8 this</h1><h1 id="21-Utf8-Lcn-itcast-jvm-Test4"><a href="#21-Utf8-Lcn-itcast-jvm-Test4" class="headerlink" title="21 = Utf8 Lcn/itcast/jvm/Test4;"></a>21 = Utf8 Lcn/itcast/jvm/Test4;</h1><h1 id="22-Utf8-main-1"><a href="#22-Utf8-main-1" class="headerlink" title="22 = Utf8 main"></a>22 = Utf8 main</h1><h1 id="23-Utf8-Ljava-lang-String-V-1"><a href="#23-Utf8-Ljava-lang-String-V-1" class="headerlink" title="23 = Utf8 ([Ljava/lang/String;)V"></a>23 = Utf8 ([Ljava/lang/String;)V</h1><h1 id="24-Utf8-args-1"><a href="#24-Utf8-args-1" class="headerlink" title="24 = Utf8 args"></a>24 = Utf8 args</h1><h1 id="25-Utf8-Ljava-lang-String-1"><a href="#25-Utf8-Ljava-lang-String-1" class="headerlink" title="25 = Utf8 [Ljava/lang/String;"></a>25 = Utf8 [Ljava/lang/String;</h1><h1 id="26-Utf8-m1-1"><a href="#26-Utf8-m1-1" class="headerlink" title="26 = Utf8 m1"></a>26 = Utf8 m1</h1><h1 id="27-Utf8-i"><a href="#27-Utf8-i" class="headerlink" title="27 = Utf8 i"></a>27 = Utf8 i</h1><h1 id="28-Utf8-I"><a href="#28-Utf8-I" class="headerlink" title="28 = Utf8 I"></a>28 = Utf8 I</h1><h1 id="29-Utf8-str"><a href="#29-Utf8-str" class="headerlink" title="29 = Utf8 str"></a>29 = Utf8 str</h1><h1 id="30-Utf8-Ljava-lang-String"><a href="#30-Utf8-Ljava-lang-String" class="headerlink" title="30 = Utf8 Ljava/lang/String;"></a>30 = Utf8 Ljava/lang/String;</h1><h1 id="31-Utf8-StackMapTable"><a href="#31-Utf8-StackMapTable" class="headerlink" title="31 = Utf8 StackMapTable"></a>31 = Utf8 StackMapTable</h1><h1 id="32-Class-53-java-lang-String"><a href="#32-Class-53-java-lang-String" class="headerlink" title="32 = Class #53 // java/lang/String"></a>32 = Class #53 // java/lang/String</h1><h1 id="33-Utf8-m2"><a href="#33-Utf8-m2" class="headerlink" title="33 = Utf8 m2"></a>33 = Utf8 m2</h1><h1 id="34-Utf8-sb"><a href="#34-Utf8-sb" class="headerlink" title="34 = Utf8 sb"></a>34 = Utf8 sb</h1><h1 id="35-Utf8-Ljava-lang-StringBuilder"><a href="#35-Utf8-Ljava-lang-StringBuilder" class="headerlink" title="35 = Utf8 Ljava/lang/StringBuilder;"></a>35 = Utf8 Ljava/lang/StringBuilder;</h1><h1 id="36-Class-44-java-lang-StringBuilder"><a href="#36-Class-44-java-lang-StringBuilder" class="headerlink" title="36 = Class #44 // java/lang/StringBuilder"></a>36 = Class #44 // java/lang/StringBuilder</h1><h1 id="37-Utf8-SourceFile"><a href="#37-Utf8-SourceFile" class="headerlink" title="37 = Utf8 SourceFile"></a>37 = Utf8 SourceFile</h1><h1 id="38-Utf8-Test4-java"><a href="#38-Utf8-Test4-java" class="headerlink" title="38 = Utf8 Test4.java"></a>38 = Utf8 Test4.java</h1><h1 id="39-NameAndType-15-16-““-V"><a href="#39-NameAndType-15-16-““-V" class="headerlink" title="39 = NameAndType #15:#16 // ““:()V"></a>39 = NameAndType #15:#16 // “<init>“:()V</init></h1><h1 id="40-Utf8-cn-itcast-jvm-Test4"><a href="#40-Utf8-cn-itcast-jvm-Test4" class="headerlink" title="40 = Utf8 cn/itcast/jvm/Test4"></a>40 = Utf8 cn/itcast/jvm/Test4</h1><h1 id="41-NameAndType-26-16-m1-V"><a href="#41-NameAndType-26-16-m1-V" class="headerlink" title="41 = NameAndType #26:#16 // m1:()V"></a>41 = NameAndType #26:#16 // m1:()V</h1><h1 id="42-NameAndType-33-16-m2-V"><a href="#42-NameAndType-33-16-m2-V" class="headerlink" title="42 = NameAndType #33:#16 // m2:()V"></a>42 = NameAndType #33:#16 // m2:()V</h1><h1 id="43-Utf8"><a href="#43-Utf8" class="headerlink" title="43 = Utf8"></a>43 = Utf8</h1><h1 id="44-Utf8-java-lang-StringBuilder"><a href="#44-Utf8-java-lang-StringBuilder" class="headerlink" title="44 = Utf8 java/lang/StringBuilder"></a>44 = Utf8 java/lang/StringBuilder</h1><h1 id="45-NameAndType-54-55-append"><a href="#45-NameAndType-54-55-append" class="headerlink" title="45 = NameAndType #54:#55 // append:"></a>45 = NameAndType #54:#55 // append:</h1><p>(Ljava/lang/String;)Ljava/lang/StringBuilder;</p><h1 id="46-NameAndType-54-56-append-I-Ljava-lang-StringBuilder"><a href="#46-NameAndType-54-56-append-I-Ljava-lang-StringBuilder" class="headerlink" title="46 = NameAndType #54:#56 // append: (I)Ljava/lang/StringBuilder;"></a>46 = NameAndType #54:#56 // append: (I)Ljava/lang/StringBuilder;</h1><h1 id="47-NameAndType-57-58-toString"><a href="#47-NameAndType-57-58-toString" class="headerlink" title="47 = NameAndType #57:#58 // toString:"></a>47 = NameAndType #57:#58 // toString:</h1><p>()Ljava/lang/String; </p><h1 id="48-Class-59-java-lang-System"><a href="#48-Class-59-java-lang-System" class="headerlink" title="48 = Class #59 // java/lang/System"></a>48 = Class #59 // java/lang/System</h1><h1 id="49-NameAndType-60-61-out-Ljava-io-PrintStream"><a href="#49-NameAndType-60-61-out-Ljava-io-PrintStream" class="headerlink" title="49 = NameAndType #60:#61 // out:Ljava/io/PrintStream;"></a>49 = NameAndType #60:#61 // out:Ljava/io/PrintStream;</h1><h1 id="50-Class-62-java-io-PrintStream"><a href="#50-Class-62-java-io-PrintStream" class="headerlink" title="50 = Class #62 // java/io/PrintStream"></a>50 = Class #62 // java/io/PrintStream</h1><h1 id="51-NameAndType-63-64-println"><a href="#51-NameAndType-63-64-println" class="headerlink" title="51 = NameAndType #63:#64 // println:"></a>51 = NameAndType #63:#64 // println:</h1><p>(Ljava/lang/String;)V</p><h1 id="52-Utf8-java-lang-Object"><a href="#52-Utf8-java-lang-Object" class="headerlink" title="52 = Utf8 java/lang/Object"></a>52 = Utf8 java/lang/Object</h1><h1 id="53-Utf8-java-lang-String"><a href="#53-Utf8-java-lang-String" class="headerlink" title="53 = Utf8 java/lang/String"></a>53 = Utf8 java/lang/String</h1><h1 id="54-Utf8-append"><a href="#54-Utf8-append" class="headerlink" title="54 = Utf8 append"></a>54 = Utf8 append</h1><h1 id="55-Utf8-Ljava-lang-String-Ljava-lang-StringBuilder"><a href="#55-Utf8-Ljava-lang-String-Ljava-lang-StringBuilder" class="headerlink" title="55 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;"></a>55 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;</h1><h1 id="56-Utf8-I-Ljava-lang-StringBuilder"><a href="#56-Utf8-I-Ljava-lang-StringBuilder" class="headerlink" title="56 = Utf8 (I)Ljava/lang/StringBuilder;"></a>56 = Utf8 (I)Ljava/lang/StringBuilder;</h1><h1 id="57-Utf8-toString"><a href="#57-Utf8-toString" class="headerlink" title="57 = Utf8 toString"></a>57 = Utf8 toString</h1><h1 id="58-Utf8-Ljava-lang-String"><a href="#58-Utf8-Ljava-lang-String" class="headerlink" title="58 = Utf8 ()Ljava/lang/String;"></a>58 = Utf8 ()Ljava/lang/String;</h1><h1 id="59-Utf8-java-lang-System"><a href="#59-Utf8-java-lang-System" class="headerlink" title="59 = Utf8 java/lang/System"></a>59 = Utf8 java/lang/System</h1><h1 id="60-Utf8-out"><a href="#60-Utf8-out" class="headerlink" title="60 = Utf8 out"></a>60 = Utf8 out</h1><h1 id="61-Utf8-Ljava-io-PrintStream"><a href="#61-Utf8-Ljava-io-PrintStream" class="headerlink" title="61 = Utf8 Ljava/io/PrintStream;"></a>61 = Utf8 Ljava/io/PrintStream;</h1><h1 id="62-Utf8-java-io-PrintStream"><a href="#62-Utf8-java-io-PrintStream" class="headerlink" title="62 = Utf8 java/io/PrintStream"></a>62 = Utf8 java/io/PrintStream</h1><h1 id="63-Utf8-println"><a href="#63-Utf8-println" class="headerlink" title="63 = Utf8 println"></a>63 = Utf8 println</h1><h1 id="64-Utf8-Ljava-lang-String-V"><a href="#64-Utf8-Ljava-lang-String-V" class="headerlink" title="64 = Utf8 (Ljava/lang/String;)V"></a>64 = Utf8 (Ljava/lang/String;)V</h1><p>{<br>public cn.itcast.jvm.Test4(); descriptor: ()V<br>flags: ACC_PUBLIC Code:<br>stack=1, locals=1, args_size=1 0: aload_0<br>1: invokespecial #1 // Method<br>java/lang/Object.”<init>“:()V<br>4: return LineNumberTable:<br>line 3: 0 LocalVariableTable:<br>Start Length Slot Name Signature<br>0 5 0 this Lcn/itcast/jvm/Test4;</init></p><p>public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V<br>flags: ACC_PUBLIC, ACC_STATIC<br>Code:<br>stack=2, locals=1, args_size=1<br>0: new #2 // class cn/itcast/jvm/Test4<br>3: dup<br>4: invokespecial #3 // Method “<init>“:()V<br>7: invokevirtual #4 // Method m1:()V<br>10: new #2 // class cn/itcast/jvm/Test4<br>13: dup<br>14: invokespecial #3 // Method “<init>“:()V<br>17: invokevirtual #5 // Method m2:()V<br>20: return LineNumberTable:<br>line 6: 0<br>line 7: 10</init></init></p><p>line 8: 20</p><p>LocalVariableTable:<br>Start Length Slot Name Signature<br>0 21 0 args [Ljava/lang/String;<br>public void m1(); descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=2, locals=3, args_size=1<br>0: ldc #6 // String<br>2: astore_1 // 将空字符串压入到本地变量表中的下标为1的位置<br>3: iconst_0 // 将数字0压入操作栈顶<br>4: istore_2 // 将栈顶数字0压入到本地变量表中的下标为2的位置<br>5: iload_2 // 将本地变量中下标为2的数字0压入操作栈顶<br>6: iconst_5 // 将数字5压入操作栈顶<br>7: if_icmpge 35 //比较栈顶两int型数值大小,当结果大于等于0时跳</p><p>转到35</p><p>10: new #7 // class</p><p>java/lang/StringBuilder<br>13: dup //复制栈顶数值并将复制值压入栈顶(数字5)<br>14: invokespecial #8 // Method java/lang/StringBuilder.”<init>“:()V<br>17: aload_1<br>18: invokevirtual #9 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder;<br>21: iload_2 //将本地变量中下标为2的数字0压入操作栈顶<br>22: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;<br>25: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;<br>28: astore_1<br>29: iinc 2, 1<br>32: goto 5<br>35: getstatic #12 // Field<br>java/lang/System.out:Ljava/io/PrintStream; 38: aload_1<br>39: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V<br>42: return LineNumberTable:</init></p><p>line 11: 0<br>line 12: 3<br>line 13: 10<br>line 12: 29<br>line 15: 35<br>line 16: 42<br>LocalVariableTable:<br>Start Length Slot Name Signature<br>5 30 2 i I<br>0 43 0 this Lcn/itcast/jvm/Test4;<br>3 40 1 str Ljava/lang/String;<br>StackMapTable: number_of_entries = 2 frame_type = 253 /<em> append </em>/<br>offset_delta = 5<br>locals = [ class java/lang/String, int ] frame_type = 250 /<em> chop </em>/<br>offset_delta = 29</p><p>public void m2(); descriptor: ()V flags: ACC_PUBLIC Code:<br>stack=2, locals=3, args_size=1<br>0: new #7 // class java/lang/StringBuilder<br>3: dup<br>4: invokespecial #8 // Method java/lang/StringBuilder.”<init>“:()V<br>7: astore_1<br>8: iconst_0<br>9: istore_2<br>10: iload_2<br>11: iconst_5<br>12: if_icmpge 27<br>15: aload_1<br>16: iload_2<br>17: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;<br>20: pop<br>21: iinc 2, 1<br>24: goto 10<br>27: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;<br>30: aload_1<br>31: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;<br>34: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V<br>37: return LineNumberTable:<br>line 19: 0<br>line 20: 8<br>line 21: 15<br>line 20: 21<br>line 23: 27<br>line 24: 37 LocalVariableTable:<br>Start Length Slot Name Signature 10 17 2 i I<br>0 38 0 this Lcn/itcast/jvm/Test4;<br>8 30 1 sb Ljava/lang/StringBuilder;<br>StackMapTable: number_of_entries = 2 frame_type = 253 /<em> append </em>/<br>offset_delta = 10<br>locals = [ class java/lang/StringBuilder, int ] frame_type = 250 /<em> chop </em>/<br>offset_delta = 16<br>}<br>SourceFile: “Test4.java”</init></p><p>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>46<br>47<br>48<br>49<br>50<br>51<br>52<br>53<br>54<br>55<br>56<br>57<br>58<br>59<br>60<br>61<br>62<br>63<br>64<br>65<br>66<br>67<br>68<br>69<br>70<br>71<br>72<br>73<br>74<br>75<br>76<br>77<br>78<br>79<br>80<br>81<br>82<br>83<br>84<br>85<br>86<br>87<br>88<br>89<br>90<br>91<br>92<br>93<br>94<br>95<br>96<br>97<br>98<br>99<br>100<br>101<br>102<br>103<br>104<br>105<br>106<br>107<br>108<br>109<br>110<br>111<br>112<br>113<br>114<br>115<br>116<br>117<br>118<br>119<br>120<br>121<br>122<br>123<br>124<br>125<br>126<br>127<br>128<br>129<br>130<br>131<br>132<br>133<br>134<br>135<br>136<br>137<br>138<br>139<br>140<br>141<br>142<br>143<br>144<br>145<br>146<br>147<br>148<br>149<br>150<br>151<br>152<br>153<br>154<br>155<br>156<br>157<br>158<br>159<br>160<br>161<br>162<br>163<br>164<br>165<br>166<br>167<br>168<br>169<br>170<br>171<br>172<br>173<br>174<br>175<br>176<br>177<br>178<br>179<br>180<br>181<br>182<br>183<br>184<br>185<br>186<br>187<br>188<br>189<br>190<br>191<br>192<br>193<br>194<br>195<br>可以看到,m1()方法中的循环体内,每一次循环都会创建StringBuilder对象,效率低于<br>m2()方法。</p><p>2.7、小结<br>使用字节码的方式可以很好查看代码底层的执行,从而可以看出哪些实现效率高,哪些 实现效率低。可以更好的对我们的代码做优化。让程序执行效率更高。</p><p>3、代码优化<br>优化,不仅仅是在运行环境进行优化,还需要在代码本身做优化,如果代码本身存在性 能问题,那么在其他方面再怎么优化也不可能达到效果最优的。</p><p>3.1、尽可能使用局部变量<br>调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变 量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随 着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。</p><p>3.2、尽量减少对变量的重复计算<br>明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的。所以例如下 面的操作:</p><p>for (int i = 0; i < list.size(); i++)<br>{…}<br>1<br>2<br>建议替换为:</p><p>int length = list.size();<br>for (int i = 0, i < length; i++)<br>{…}</p><p>1<br>2<br>3<br>4<br>这样,在list.size()很大的时候,就减少了很多的消耗。</p><p>3.3、尽量采用懒加载的策略,即在需要的时候才创建<br>String str = “aaa”;<br>if (i == 1){<br>list.add(str);<br>}<br>//建议替换成<br>if (i == 1){<br>String str = “aaa”; list.add(str);<br>}<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>3.4、异常不应该用来控制程序流程<br>异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用 名为fillInStackTrace()的本地同步方 法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建 了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。</p><p>3.5、不要将数组声明为public static final<br>因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的, 将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。</p><p>3.6、不要创建一些不使用的对象,不要导入一些不使用的类<br>这毫无意义,如果代码中出现”The value of the local variable i is not used”、“The import java.util is never used”,那么请删除这些无用的内容</p><p>3.7、程序运行过程中避免使用反射<br>反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是 Method的invoke方法。<br>如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候 通过反射实例化出一个对象并放入内存。</p><p>3.8、使用数据库连接池和线程池<br>这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频 繁地创建和销毁线程。</p><p>3.9、容器初始化时尽可能指定长度<br>容器初始化时尽可能指定长度,如:new ArrayList<>(10); new HashMap<>(32); 避免容器长度不足时,扩容带来的性能损耗。</p><p>3.10、ArrayList随机遍历快,LinkedList添加删除快<br>3.11、使用Entry遍历Map<br>Map<string,string> map = new HashMap<>();<br>for (Map.Entry<string,string> entry : map.entrySet()) { String key = entry.getKey();<br>String value = entry.getValue();<br>}<br>1<br>2<br>3<br>4<br>避免使用这种方式:</string,string></string,string></p><p>Map<string,string> map = new HashMap<>(); for (String key : map.keySet()) {<br>String value = map.get(key);<br>}<br>1<br>2<br>3<br>3.12、不要手动调用System.gc();<br>3.13、String尽量少用正则表达式<br>正则表达式虽然功能强大,但是其效率较低,除非是有需要,否则尽可能少用。</string,string></p><p>replace() 不支持正则<br>replaceAll() 支持正则<br>如果仅仅是字符的替换建议使用replace()。</p><p>3.14、日志的输出要注意级别<br>// 当 前 的 日 志 级 别 是 error LOGGER.info(“保存出错!” + user);<br>1<br>3.15、对资源的close()建议分开操作<br>try{<br>XXX.close();<br>YYY.close();<br>}<br>catch (Exception e){<br>…<br>}<br>// 建议改为<br>try{<br>XXX.close();<br>}<br>catch (Exception e){<br>…<br>}<br>try{<br>YYY.close();<br>}<br>catch (Exception e){<br>…<br>}</p></connector></executor></connector>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>Tomcat8的优化</li>
<li>看懂Java底层字节码</li>
<li>编码的优化建议
</summary>
<category term="JVM" scheme="http://enfangzhong.github.io/categories/JVM/"/>
<category term="JVM" scheme="http://enfangzhong.github.io/tags/JVM/"/>
</entry>
<entry>
<title>架构与优化之JVM优化第02篇垃圾回收</title>
<link href="http://enfangzhong.github.io/2019/12/20/%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BC%98%E5%8C%96%E4%B9%8BJVM%E4%BC%98%E5%8C%96%E7%AC%AC02%E7%AF%87%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
<id>http://enfangzhong.github.io/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/</id>
<published>2019-12-19T16:03:11.000Z</published>
<updated>2019-12-27T06:07:48.256Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>了解什么是垃圾回收</li><li>掌握垃圾会回收的常见算法</li><li>学习串行、并行、并发、G1垃圾收集器</li><li>学习GC日志的可视化查看</li></ul><h1 id="1、什么是垃圾回收?"><a href="#1、什么是垃圾回收?" class="headerlink" title="1、什么是垃圾回收?"></a>1、什么是垃圾回收?</h1><p>程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存 资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了。</p><p><strong>通俗的理解java对象的这一辈子</strong></p><p> 我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。</p><p><img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\image-20191227111522922.png" alt="image-20191227111522922"></p><p>GC策略解决了哪些问题?</p><p>既然是要进行自动GC,那必然会有相应的策略,而这些策略解决了哪些问题呢,粗略的来说,主要有以下几点。</p><p> <strong>1、哪些对象可以被回收。( 根搜索算法解决)</strong></p><p> <strong>2、何时回收这些对象。</strong></p><p> <strong>3、采用什么样的方式回收。</strong></p><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\18021456-60fcf6f4303e4facbb2a6b57d99b3a6d.jpg" alt="img"> </p><p>根搜索算法</p><p> 由于引用计数算法的缺陷,所以JVM一般会采用一种新的算法,叫做<strong>根搜索算法</strong>。它的处理方式就是,<strong>设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的</strong>。</p><p> 就拿上图来说,ObjectD和ObjectE是互相关联的,但是由于GC roots到这两个对象不可达,所以最终D和E还是会被当做GC的对象,上图若是采用引用计数法,则A-E五个对象都不会被回收。</p><p> 说到GC roots(GC根),在JAVA语言中,可以当做GC roots的对象有以下几种:</p><p> <strong>1、虚拟机栈中的引用的对象。</strong></p><p> <strong>2、方法区中的类静态属性引用的对象。</strong></p><p> <strong>3、方法区中的常量引用的对象。</strong></p><p> <strong>4、本地方法栈中JNI的引用的对象。</strong></p><p> 第一和第四种都是指的方法的本地变量表,第二种表达的意思比较清晰,第三种主要指的是声明为final的常量值。</p><p> HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。</p><p> 因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。</p><p> 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。</p><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\young_gc.png" alt="young_gc"> </p><h2 id="1-1、C-C-语言的垃圾回收"><a href="#1-1、C-C-语言的垃圾回收" class="headerlink" title="1.1、C/C++语言的垃圾回收"></a>1.1、C/C++语言的垃圾回收</h2><p>在C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete 关键字释放内存资源。</p><p>如果,程序员在某些位置没有写delete进行释放,那么申请的对象将一直占用内存资源, 最终可能会导致内存溢出。</p><h2 id="1-2、Java语言的垃圾回收"><a href="#1-2、Java语言的垃圾回收" class="headerlink" title="1.2、Java语言的垃圾回收"></a>1.2、Java语言的垃圾回收</h2><p>为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC。</p><p>有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别 完成。<br>换句话说,自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致 内存资源一直没有释放,同样也可能会导致内存溢出的。</p><p>当然,除了Java语言,C#、Python等语言也都有自动的垃圾回收机制。</p><h1 id="2、垃圾回收的常见算法"><a href="#2、垃圾回收的常见算法" class="headerlink" title="2、垃圾回收的常见算法"></a>2、垃圾回收的常见算法</h1><p>自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对 象,哪些是无效的对象,对于无效的对象就要进行回收处理。</p><p>常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法 等。</p><h2 id="2-1、引用计数法"><a href="#2-1、引用计数法" class="headerlink" title="2.1、引用计数法"></a>2.1、引用计数法</h2><p>引用计数是历史最悠久的一种算法,最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用。</p><h3 id="2-1-1、原理"><a href="#2-1-1、原理" class="headerlink" title="2.1.1、原理"></a>2.1.1、原理</h3><p>假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败 时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了, 可以被回收。</p><h3 id="2-1-2、优缺点"><a href="#2-1-2、优缺点" class="headerlink" title="2.1.2、优缺点"></a>2.1.2、优缺点</h3><p>优点:</p><ul><li>实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。</li><li>在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报<br>outofmember 错误。</li><li>区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。</li></ul><p>缺点:</p><ul><li>每次对象被引用时,都需要去更新计数器,有一点时间开销。</li><li>浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。</li><li>无法解决循环引用问题。(最大的缺点)</li></ul><p>什么是循环引用?</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">class TestA {</span><br><span class="line"> public TestB b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class TestB {</span><br><span class="line"> public TestA a;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class Main {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> A a = new A();</span><br><span class="line"> B b = new B();</span><br><span class="line"> a.b = b;</span><br><span class="line"> b.a = a;</span><br><span class="line"> a = null;</span><br><span class="line"> b = null;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">public class Object {</span><br><span class="line"></span><br><span class="line"> Object field = null;</span><br><span class="line"> </span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Thread thread = new Thread(new Runnable() {</span><br><span class="line"> public void run() {</span><br><span class="line"> Object objectA = new Object();</span><br><span class="line"> Object objectB = new Object();//1</span><br><span class="line"> objectA.field = objectB;</span><br><span class="line"> objectB.field = objectA;//2</span><br><span class="line"> //to do something</span><br><span class="line"> objectA = null;</span><br><span class="line"> objectB = null;//3</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> while (true);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p> 这段代码看起来有点刻意为之,但其实在实际编程过程当中,是经常出现的,比如两个一对一关系的数据库对象,各自保持着对方的引用。最后一个无限循环只是为了保持JVM不退出,没什么实际意义。</p><p> 对于我们现在使用的GC来说,当thread线程运行结束后,会将objectA和objectB全部作为待回收的对象。而如果我们的GC采用上面所说的引用计数算法,则这两个对象永远不会被回收,即便我们在使用后显示的将对象归为空值也毫无作用。</p><p> 这里LZ大致解释一下,在代码中LZ标注了1、2、3三个数字,当第1个地方的语句执行完以后,两个对象的引用计数全部为1。当第2个地方的语句执行完以后,两个对象的引用计数就全部变成了2。当第3个地方的语句执行完以后,也就是将二者全部归为空值以后,二者的引用计数仍然为1。根据引用计数算法的回收规则,引用计数没有归0的时候是不会被回收的。</p><h2 id="2-2、标记清除法(五分钟让你彻底明白标记-清除算法)"><a href="#2-2、标记清除法(五分钟让你彻底明白标记-清除算法)" class="headerlink" title="2.2、标记清除法(五分钟让你彻底明白标记/清除算法)"></a>2.2、标记清除法(五分钟让你彻底明白标记/清除算法)</h2><p> 首先,我们通过根搜索算法知道,它可以解决我们应该回收哪些对象的问题,但是它显然还不能承担垃圾搜集的重任,<strong>因为我们在程序(程序也就是指我们运行在JVM上的JAVA程序)运行期间如果想进行垃圾回收,就必须让GC线程与程序当中的线程互相配合,才能在不影响程序运行的前提下,顺利的将垃圾进行回收</strong>。</p><p> 为了达到这个目的,标记/清除算法就应运而生了。<strong>它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除</strong>。</p><p>标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。</p><ul><li>标记:从根节点开始标记引用的对象。( <strong>标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。</strong> )</li><li>清除:未被标记引用的对象就是垃圾对象,可以被清理。( <strong>清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。</strong> )</li></ul><p><strong>通俗的话解释一下标记/清除算法:</strong></p><p> 就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行。 </p><h3 id=""><a href="#" class="headerlink" title=" "></a> </h3><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\19222114-6435e25e050f4a2ea3230879392bdfb3.jpg" alt="img"> </p><p>这张图代表的是程序运行期间所有对象的状态,它们的标志位全部是0(也就是未标记, 以下默认0就是未标记,1为已标记),假设这会儿有效内存空间耗尽了,JVM将会停止应用程序的运行并开启GC线程,然后开始进行标记工作,按照根(root)搜索算法,标记完以后, 对象的状态如下图。</p><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\19222543-1da7fb7bc5d24fd18872a267b65e939e.jpg" alt="img"> </p><p>可以看到,按照根(root)搜索算法,所有从root对象可达的对象就被标记为了存活的对象,此时已经完成了第一阶段标记。接下来,就要执行第二阶段清除了,那么清除完以后,剩下的对象以及对象的状态如下图所示。</p><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\19222820-8eca1c1518754d8fac6da532b13f746b.jpg" alt="img"> </p><p>可以看到,没有被标记的对象将会回收清除掉,而被标记的对象将会留下,并且会将标记位重新归0。接下来就不用说了,唤醒停止的程序线程,让程序继续运行即可。</p><p>为什么非要停止程序的运行呢?</p><p> 这个其实也不难理解,LZ举个最简单的例子,假设我们的程序与GC线程是一起运行的,各位试想这样一种场景。</p><p> 假设我们刚标记完图中最右边的那个对象,暂且记为A,结果此时在程序当中又new了一个新对象B,且A对象可以到达B对象。但是由于此时A对象已经标记结束,B对象此时的标记位依然是0,因为它错过了标记阶段。因此当接下来轮到清除阶段的时候,新对象B将会被苦逼的清除掉。如此一来,不难想象结果,GC线程将会导致程序无法正常工作。</p><p> 上面的结果当然令人无法接受,我们刚new了一个对象,结果经过一次GC,忽然变成null了,这还怎么玩?</p><h3 id="2-2-2、优缺点"><a href="#2-2-2、优缺点" class="headerlink" title="2.2.2、优缺点"></a>2.2.2、优缺点</h3><p>可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引 用的对象都会被回收。</p><p>同样,标记清除算法也是有缺点的:<br>1、效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。</p><p> <strong>(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲</strong>,尤其对于交互式的应用程序来说简直是无法接受。试想一下,如果你玩一个网站,这个网站一个小时就挂五分钟,你还玩吗? </p><p>2、 第二点主要的缺点,则是<strong>这种方式清理出来的空闲内存是不连续的</strong>通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。</p><p> 第二点主要的缺点,则是<strong>这种方式清理出来的空闲内存是不连续的</strong>,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。 </p><h2 id="2-3、标记压缩算法"><a href="#2-3、标记压缩算法" class="headerlink" title="2.3、标记压缩算法"></a>2.3、标记压缩算法</h2><p>标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一 样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标 记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决 了碎片化的问题。</p><p>2.3.1、原理</p><h3 id="2-3-2、优缺点"><a href="#2-3-2、优缺点" class="headerlink" title="2.3.2、优缺点"></a>2.3.2、优缺点</h3><p>优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。</p><h2 id="2-4、复制算法"><a href="#2-4、复制算法" class="headerlink" title="2.4、复制算法"></a>2.4、复制算法</h2><p>复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。<br>如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。</p><p><img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\image-20191227105028340.png" alt="image-20191227105028340"></p><h3 id="2-4-1、JVM中年轻代内存空间"><a href="#2-4-1、JVM中年轻代内存空间" class="headerlink" title="2.4.1、JVM中年轻代内存空间"></a>2.4.1、JVM中年轻代内存空间</h3><p> <img src="/2019/12/20/架构与优化之JVM优化第02篇垃圾回收/blog\jygBlog\source\_posts\架构与优化之JVM优化第02篇垃圾回收\young_gc.png" alt="young_gc"> </p><p>1.在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor 区“To”是空的。</p><p>2.紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍 存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过- XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对 象会被复制到“To”区域。</p><p>3.经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他 们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。</p><p>4.GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。</p><p>2.4.2、优缺点<br>优点:</p><p>在垃圾对象多的情况下,效率较高清理后,内存无碎片<br>缺点:</p><p>在垃圾对象少的情况下,不适用,如:老年代内存<br>分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低</p><p>2.5、分代算法<br>前面介绍了多种回收算法,每一种算法都有自己的优点也有缺点,谁都不能替代谁,所 以根据垃圾回收对象的特点进行选择,才是明智的选择。<br>分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复 制算法,老年代适合使用标记清除或标记压缩算法。</p><p>3、垃圾收集器以及内存分配<br>前面我们讲了垃圾回收的算法,还需要有具体的实现,在jvm中,实现了多种垃圾收集 器,包括:串行垃圾收集器、并行垃圾收集器、CMS(并发)垃圾收集器、G1垃圾收集器,接下来,我们一个个的了解学习。</p><p>3.1、串行垃圾收集器<br>串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作, 并且java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)。<br>对于交互性较强的应用而言,这种垃圾收集器是不能够接受的。一般在Javaweb应用中是不会采用该收集器的。<br>3.1.1、编写测试代码</p><p>package cn.itcast.jvm;</p><p>import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Random;</p><p>public class TestGC {</p><p>public static void main(String[] args) throws Exception { List<object> list = new ArrayList<object>();<br>while (true){<br>int sleep = new Random().nextInt(100); if(System.currentTimeMillis() % 2 ==0){<br>list.clear();<br>}else{<br>for (int i = 0; i < 10000; i++) {<br>Properties properties = new Properties(); properties.put(“key_”+i, “value_” +<br>System.currentTimeMillis() + i);<br>list.add(properties);<br>}<br>}</object></object></p><p>// System.out.println(“list大小为:” + list.size());</p><p>Thread.sleep(sleep);<br>}<br>}<br>}</p><p>3.1.2、设置垃圾回收为串行收集器<br>在程序运行参数中添加2个参数,如下:<br>-XX:+UseSerialGC<br>指定年轻代和老年代都使用串行垃圾收集器<br>-XX:+PrintGCDetails<br>打印垃圾回收的详细信息</p><p>为了测试GC,将堆的初始和最大内存都设置为16M<br>‐XX:+UseSerialGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m</p><p>启动程序,可以看到下面信息:</p><p>[GC (Allocation Failure) [DefNew: 4416K‐>512K(4928K), 0.0046102 secs]<br>4416K‐>1973K(15872K), 0.0046533 secs] [Times: user=0.00 sys=0.00,<br>real=0.00 secs]</p><p>[Full GC (Allocation Failure) [Tenured: 10944K‐>3107K(10944K), 0.0085637<br>secs] 15871K‐>3107K(15872K), [Metaspace: 3496K‐>3496K(1056768K)],<br>0.0085974 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]</p><p>GC日志信息解读:<br>年轻代的内存GC前后的大小:<br>DefNew<br>表示使用的是串行垃圾收集器。<br>4416K->512K(4928K)<br>表示,年轻代GC前,占有4416K内存,GC后,占有512K内存,总大小4928K 0.0046102 secs<br>表示,GC所用的时间,单位为毫秒。<br>4416K->1973K(15872K)<br>表示,GC前,堆内存占有4416K,GC后,占有1973K,总大小为15872K Full GC<br>表示,内存空间全部进行GC</p><p>3.2、并行垃圾收集器<br>并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃 圾回收,这样可以缩短垃圾回收的时间。(这里是指,并行能力较强的机器)<br>当然了,并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是 一样的,只是并行执行,速度更快些,暂停的时间更短一些。</p><p>3.2.1、ParNew垃圾收集器<br>ParNew垃圾收集器是工作在年轻代上的,只是将串行的垃圾收集器改为了并行。<br>通过-XX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器。</p><p>测试:</p><h1 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h1><p>‐XX:+UseParNewGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m</p><h1 id="打印出的信息"><a href="#打印出的信息" class="headerlink" title="打印出的信息"></a>打印出的信息</h1><p>[GC (Allocation Failure) [ParNew: 4416K‐>512K(4928K), 0.0032106 secs] 4416K‐>1988K(15872K), 0.0032697 secs] [Times: user=0.00 sys=0.00,<br>real=0.00 secs]</p><p>由以上信息可以看出, 致。</p><p>使用的是ParNew收集器。其他信息和串行收集器一</p><p>3.2.2、ParallelGC垃圾收集器<br>ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和 系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。</p><p>相关参数如下:</p><p>-XX:+UseParallelGC<br>年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。<br>-XX:+UseParallelOldGC<br>年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。<br>-XX:MaxGCPauseMillis<br>设置最大的垃圾收集时的停顿时间,单位为毫秒<br>需要注意的时,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他 的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会 影响到性能。<br>该参数使用需谨慎。<br>-XX:GCTimeRatio<br>设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。<br>它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%<br>-XX:UseAdaptiveSizePolicy<br>自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、堆大小、停顿时间之间的平衡。<br>一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。<br>测试:</p><h1 id="参数-1"><a href="#参数-1" class="headerlink" title="参数"></a>参数</h1><p>‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐XX:MaxGCPauseMillis=100 ‐ XX:+PrintGCDetails ‐Xms16m ‐Xmx16m</p><h1 id="打印的信息"><a href="#打印的信息" class="headerlink" title="打印的信息"></a>打印的信息</h1><p>[GC (Allocation Failure) [PSYoungGen: 4096K‐>480K(4608K)] 4096K‐</p><blockquote><p>1840K(15872K), 0.0034307 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p></blockquote><p>[Full GC (Ergonomics) [PSYoungGen: 505K‐>0K(4608K)] [ParOldGen: 10332K‐</p><blockquote><p>10751K(11264K)] 10837K‐>10751K(15872K), [Metaspace: 3491K‐<br>3491K(1056768K)], 0.0793622 secs] [Times: user=0.13 sys=0.00, real=0.08<br>secs]</p><p>有以上信息可以看出,年轻代和老年代都使用了ParallelGC垃圾回收器。</p></blockquote><p>3.3、CMS垃圾收集器<br>CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器, 该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。<br>CMS垃圾回收器的执行过程如下:初始化标记(CMS-initial-mark) ,标记root,会导致stw;<br>并发标记(CMS-concurrent-mark),与用户线程同时运行;<br>预清理(CMS-concurrent-preclean),与用户线程同时运行; 重新标记(CMS-remark) ,会导致stw;<br>并发清除(CMS-concurrent-sweep),与用户线程同时运行;<br>调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;<br>并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;<br>3.3.1、测试</p><h1 id="设置启动参数"><a href="#设置启动参数" class="headerlink" title="设置启动参数"></a>设置启动参数</h1><p>‐XX:+UseConcMarkSweepGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m</p><h1 id="运行日志"><a href="#运行日志" class="headerlink" title="运行日志"></a>运行日志</h1><p>[GC (Allocation Failure) [ParNew: 4926K‐>512K(4928K), 0.0041843 secs] 9424K‐>6736K(15872K), 0.0042168 secs] [Times: user=0.00 sys=0.00,<br>real=0.00 secs]</p><h1 id="第一步,初始标记"><a href="#第一步,初始标记" class="headerlink" title="第一步,初始标记"></a>第一步,初始标记</h1><p>[GC (CMS Initial Mark) [1 CMS‐initial‐mark: 6224K(10944K)] 6824K(15872K), 0.0004209 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p><h1 id="第二步,并发标记"><a href="#第二步,并发标记" class="headerlink" title="第二步,并发标记"></a>第二步,并发标记</h1><p>[CMS‐concurrent‐mark‐start]<br>[CMS‐concurrent‐mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p><h1 id="第三步,预处理"><a href="#第三步,预处理" class="headerlink" title="第三步,预处理"></a>第三步,预处理</h1><p>[CMS‐concurrent‐preclean‐start]<br>[CMS‐concurrent‐preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p><h1 id="第四步,重新标记"><a href="#第四步,重新标记" class="headerlink" title="第四步,重新标记"></a>第四步,重新标记</h1><p>[GC (CMS Final Remark) [YG occupancy: 1657 K (4928 K)][Rescan (parallel) , 0.0005811 secs][weak refs processing, 0.0000136 secs][class unloading, 0.0003671 secs][scrub symbol table, 0.0006813 secs][scrub string table, 0.0001216 secs][1 CMS‐remark: 6224K(10944K)] 7881K(15872K), 0.0018324<br>secs] [Times: user=0.00 sys=0.00, real=0.00 secs] #第五步,并发清理<br>[CMS‐concurrent‐sweep‐start]<br>[CMS‐concurrent‐sweep: 0.004/0.004 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p><h1 id="第六步,重置"><a href="#第六步,重置" class="headerlink" title="第六步,重置"></a>第六步,重置</h1><p>[CMS‐concurrent‐reset‐start]<br>[CMS‐concurrent‐reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</p><p>由以上日志信息,可以看出CMS执行的过程。</p><p>3.4、G1垃圾收集器(重点)<br>G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将 G1变成默认的垃圾收集器,以替代CMS。<br>G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:<br>1.第一步,开启G1垃圾收集器<br>2.第二步,设置堆的最大内存<br>3.第三步,设置最大的停顿时间<br>G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。</p><p>3.4.1、原理<br>G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理 划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的 年轻代、老年代区域。</p><p>这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内 存是否足够。<br>在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区 域,完成了清理工作。</p><p>这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。<br>在G1中,有一种特殊的区域,叫Humongous区域。<br>如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。<br>这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。<br>为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果<br>一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。<br>3.4.2、Young GC<br>Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。<br>Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分 数据会直接晋升到年老代空间。<br>Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。 最终Eden空间的数据为空,GC停止工作,应用线程继续执行。</p><p>3.4.2.1、Remembered Set(已记忆集合)<br>在GC年轻代的对象时,我们如何找到年轻代中对象的根对象呢?<br>根对象可能是在年轻代中,也可以在老年代中,那么老年代中的所有对象都是根么? 如果全量扫描老年代,那么这样扫描下来会耗费大量的时间。<br>于是,G1引进了RSet的概念。它的全称是Remembered Set,其作用是跟踪指向某个堆<br>内的对象引用。每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录 的东西应该是 xx Region的 xx Card。<br>3.4.3、Mixed GC<br>当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。<br>MixedGC什么时候触发? 由参数 -XX:InitiatingHeapOccupancyPercent=n 决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。<br>它的GC步骤分2步:<br>1.全局并发标记(global concurrent marking)<br>2.拷贝存活对象(evacuation)<br>3.4.3.1、全局并发标记<br>全局并发标记,执行过程分为五个步骤:<br>初始标记(initial mark,STW)<br>标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。<br>根区域扫描(root region scan)<br>G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。<br>该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。<br>并发标记(Concurrent Marking)<br>G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行, 可以被 STW 年轻代垃圾回收中断。<br>重新标记(Remark,STW)<br>该阶段是 STW 回收,因为程序在运行,针对上一次的标记进行修正。清除垃圾(Cleanup,STW)<br>清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集, 等待evacuation阶段来回收。<br>3.4.3.2、拷贝存活对象<br>Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region<br>中,从而实现垃圾的回收清理。</p><p>3.4.4、G1收集器相关参数<br>-XX:+UseG1GC<br>使用 G1 垃圾收集器<br>-XX:MaxGCPauseMillis<br>设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是 200 毫秒。<br>-XX:G1HeapRegionSize=n<br>设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。<br>默认是堆内存的1/2000。<br>-XX:ParallelGCThreads=n<br>设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。<br>-XX:ConcGCThreads=n<br>设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads)<br>的 1/4 左右。<br>-XX:InitiatingHeapOccupancyPercent=n<br>设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。<br>3.4.5、测试</p><p>‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐XX:+PrintGCDetails ‐Xmx256m</p><h1 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h1><p>[GC pause (G1 Evacuation Pause) (young), 0.0044882 secs] [Parallel Time: 3.7 ms, GC Workers: 3]<br>[GC Worker Start (ms): Min: 14763.7, Avg: 14763.8, Max: 14763.8, Diff: 0.1]</p><h1 id="扫描根节点"><a href="#扫描根节点" class="headerlink" title="扫描根节点"></a>扫描根节点</h1><p>[Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.3, Diff: 0.1,<br>Sum: 0.8]</p><h1 id="更新RS区域所消耗的时间"><a href="#更新RS区域所消耗的时间" class="headerlink" title="更新RS区域所消耗的时间"></a>更新RS区域所消耗的时间</h1><p>[Update RS (ms): Min: 1.8, Avg: 1.9, Max: 1.9, Diff: 0.2, Sum: 5.6]<br>[Processed Buffers: Min: 1, Avg: 1.7, Max: 3, Diff: 2, Sum: 5]<br>[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]<br>[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0,<br>Sum: 0.0]</p><h1 id="对象拷贝"><a href="#对象拷贝" class="headerlink" title="对象拷贝"></a>对象拷贝</h1><p>[Object Copy (ms): Min: 1.1, Avg: 1.2, Max: 1.3, Diff: 0.2, Sum:</p><p>3.6]</p><p>0.2]</p><p>3]</p><p>[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum:</p><p>[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum:</p><p>[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0,</p><p>Sum: 0.0]<br>[GC Worker Total (ms): Min: 3.4, Avg: 3.4, Max: 3.5, Diff: 0.1,<br>Sum: 10.3]<br>[GC Worker End (ms): Min: 14767.2, Avg: 14767.2, Max: 14767.3,<br>Diff: 0.1]<br>[Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms]<br>[Clear CT: 0.0 ms] #清空CardTable<br>[Other: 0.7 ms]<br>[Choose CSet: 0.0 ms] #选取CSet<br>[Ref Proc: 0.5 ms] #弱引用、软引用的处理耗时[Ref Enq: 0.0 ms] #弱引用、软引用的入队耗时[Redirty Cards: 0.0 ms]<br>[Humongous Register: 0.0 ms] #大对象区域注册耗时[Humongous Reclaim: 0.0 ms] #大对象区域回收耗时</p><p>[Free CSet: 0.0 ms]<br>[Eden: 7168.0K(7168.0K)‐>0.0B(13.0M) Survivors: 2048.0K‐>2048.0K Heap: 55.5M(192.0M)‐>48.5M(192.0M)] #年轻代的大小统计<br>[Times: user=0.00 sys=0.00, real=0.00 secs]</p><p>3.4.6、对于G1垃圾收集器优化建议</p><p>年轻代大小<br>避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。</p><p>暂停时间目标不要太过严苛<br>G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。<br>评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意<br>承受更多的垃圾回收开销,而这会直接影响到吞吐量。</p><p>4、可视化GC日志分析工具<br>4.1、GC日志输出参数<br>前面通过-XX:+PrintGCDetails可以对GC日志进行打印,我们就可以在控制台查看,这样 虽然可以查看GC的信息,但是并不直观,可以借助于第三方的GC日志分析工具进行查 看。</p><p>在日志打印输出涉及到的参数如下:</p><p>‐XX:+PrintGC 输出GC日志<br>‐XX:+PrintGCDetails 输出GC的详细日志<br>‐XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)<br>‐XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013‐05‐ 04T21:53:59.234+0800)<br>‐XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息<br>‐Xloggc:../logs/gc.log 日志文件的输出路径</p><p>测试:</p><p>‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xmx256m ‐XX:+PrintGCDetails ‐ XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐ Xloggc:F://test//gc.log<br>1<br>运行后就可以在E盘下生成gc.log文件。如下:</p><p>Java HotSpot(TM) 64‐Bit Server VM (25.144‐b01) for windows‐amd64 JRE (1.8.0_144‐b01), built on Jul 21 2017 21:57:33 by “java_re” with MS VC++ 10.0 (VS2010)<br>Memory: 4k page, physical 12582392k(1939600k free), swap 17300984k(5567740k free)<br>CommandLine flags: ‐XX:InitialHeapSize=201318272 ‐XX:MaxGCPauseMillis=100<br>‐XX:MaxHeapSize=268435456 ‐XX:+PrintGC ‐XX:+PrintGCDateStamps ‐ XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintHeapAtGC ‐ XX:+UseCompressedClassPointers ‐XX:+UseCompressedOops ‐XX:+UseG1GC ‐XX:‐ UseLargePagesIndividualAllocation<br>{Heap before GC invocations=0 (full 0):<br>garbage‐first heap total 196608K, used 9216K [0x00000000f0000000, 0x00000000f0100600, 0x0000000100000000)<br>region size 1024K, 9 young (9216K), 0 survivors (0K)<br>Metaspace used 3491K, capacity 4500K, committed 4864K, reserved 1056768K<br>class space used 381K, capacity 388K, committed 512K, reserved 1048576K<br>2018‐09‐24T23:06:02.230+0800: 0.379: [GC pause (G1 Evacuation Pause) (young), 0.0031038 secs]<br>[Parallel Time: 2.8 ms, GC Workers: 3]<br>[GC Worker Start (ms): Min: 378.6, Avg: 378.8, Max: 379.0, Diff:<br>0.3]<br>[Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 0.8, Diff: 0.8,<br>Sum: 1.3]<br>[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]<br>[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]<br>[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]<br>[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1,<br>Sum: 0.1]<br>[Object Copy (ms): Min: 1.8, Avg: 1.9, Max: 1.9, Diff: 0.1, Sum:</p><p>5.6]</p><p>0.0]</p><p>3]</p><p>[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum:</p><p>[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum:</p><p>[GC Worker Other (ms): Min: 0.0, Avg: 0.2, Max: 0.6, Diff: 0.6,</p><p>Sum: 0.6]</p><p>[GC Worker Total (ms): Min: 2.4, Avg: 2.5, Max: 2.7, Diff: 0.3,<br>Sum: 7.6]<br>[GC Worker End (ms): Min: 381.4, Avg: 381.4, Max: 381.4, Diff: 0.0] [Code Root Fixup: 0.0 ms]<br>[Code Root Purge: 0.0 ms] [Clear CT: 0.0 ms] [Other: 0.2 ms]<br>[Choose CSet: 0.0 ms] [Ref Proc: 0.1 ms] [Ref Enq: 0.0 ms]<br>[Redirty Cards: 0.0 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.0 ms]<br>[Eden: 9216.0K(9216.0K)‐>0.0B(7168.0K) Survivors: 0.0B‐>2048.0K Heap: 9216.0K(192.0M)‐>1888.0K(192.0M)]<br>Heap after GC invocations=1 (full 0):<br>garbage‐first heap total 196608K, used 1888K [0x00000000f0000000, 0x00000000f0100600, 0x0000000100000000)<br>region size 1024K, 2 young (2048K), 2 survivors (2048K)<br>Metaspace used 3491K, capacity 4500K, committed 4864K, reserved 1056768K<br>class space used 381K, capacity 388K, committed 512K, reserved 1048576K<br>}<br>[Times: user=0.00 sys=0.00, real=0.00 secs]<br>{Heap before GC invocations=1 (full 0):<br>garbage‐first heap total 196608K, used 9056K [0x00000000f0000000, 0x00000000f0100600, 0x0000000100000000)<br>region size 1024K, 9 young (9216K), 2 survivors (2048K)<br>Metaspace used 3492K, capacity 4500K, committed 4864K, reserved 1056768K<br>class space used 381K, capacity 388K, committed 512K, reserved 1048576K<br>2018‐09‐24T23:06:02.310+0800: 0.458: [GC pause (G1 Evacuation Pause) (young), 0.0070126 secs]<br>。。。。。。。。。。。。。。。。。。。</p><p>4.2、GC Easy 可视化工具<br>GC Easy是一款在线的可视化工具,易用、功能强大,网站:</p><p><a href="http://gceasy.io/上传后,点击“Analyze”按钮,即可查看报告" target="_blank" rel="noopener">http://gceasy.io/上传后,点击“Analyze”按钮,即可查看报告</a></p>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>了解什么是垃圾回收</li>
<li>掌握垃圾会回收的常见算法</li>
<li>学习串行、并行
</summary>
<category term="JVM" scheme="http://enfangzhong.github.io/categories/JVM/"/>
<category term="JVM" scheme="http://enfangzhong.github.io/tags/JVM/"/>
</entry>
<entry>
<title>架构与优化之JVM优化第01篇JVM基础</title>
<link href="http://enfangzhong.github.io/2019/12/19/%E6%9E%B6%E6%9E%84%E4%B8%8E%E4%BC%98%E5%8C%96%E4%B9%8BJVM%E4%BC%98%E5%8C%96%E7%AC%AC01%E7%AF%87JVM%E5%9F%BA%E7%A1%80/"/>
<id>http://enfangzhong.github.io/2019/12/19/架构与优化之JVM优化第01篇JVM基础/</id>
<published>2019-12-18T16:03:11.000Z</published>
<updated>2019-12-22T15:50:39.891Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li><p>了解下我们为什么要学习JVM优化 </p></li><li><p>掌握jvm的运行参数以及参数的设置 </p></li><li><p>掌握jvm的内存模型(堆内存) </p></li><li><p>掌握jamp命令的使用以及通过MAT工具进行分析 </p></li><li><p>掌握定位分析内存溢出的方法 </p></li><li><p>掌握jstack命令的使用 </p></li><li><p>掌握VisualJVM工具的使用 </p></li></ul><h1 id="1、我们为什么要对jvm做优化?"><a href="#1、我们为什么要对jvm做优化?" class="headerlink" title="1、我们为什么要对jvm做优化?"></a>1、我们为什么要对jvm做优化?</h1><p>在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们 可能将有下面的需求: </p><ul><li><p>运行的应用“卡住了”,日志不输出,程序没有反应 </p></li><li><p>服务器的CPU负载突然升高 在多线程应用下,如何分配线程的数量?</p></li><li><p>…… </p></li></ul><p>我们将对jvm有更深入的学习,我们不仅要让程序能跑起来,而且是可以 跑的更快!可以分析解决在生产环境中所遇到的各种“棘手”的问题。 </p><h1 id="2、jvm的运行参数"><a href="#2、jvm的运行参数" class="headerlink" title="2、jvm的运行参数"></a>2、jvm的运行参数</h1><p>在jvm中有很多的参数可以进行设置,这样可以让jvm在各种环境中都能够高效的运行。 绝大部分的参数保持默认即可。</p><h2 id="2-1、三种参数类型"><a href="#2-1、三种参数类型" class="headerlink" title="2.1、三种参数类型"></a>2.1、三种参数类型</h2><p>jvm的参数类型分为三类,分别是:</p><blockquote><p><strong>标准参数</strong></p></blockquote><ul><li>-help </li><li>-version</li></ul><blockquote><p><strong> -X参数 (非标准参数)</strong></p></blockquote><ul><li>-Xint </li><li>-Xcomp </li></ul><blockquote><p><strong>-XX参数(使用率较高)</strong> </p></blockquote><ul><li>-XX:newSize </li><li>-XX:+UseSerialGC</li></ul><h2 id="2-2、标准参数"><a href="#2-2、标准参数" class="headerlink" title="2.2、标准参数"></a><strong>2.2</strong>、标准参数</h2><p> jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java -help检索出所有的标准参数。 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# java ‐help</span><br><span class="line">用法: java [‐options] class [args...]</span><br><span class="line">(执行类)</span><br><span class="line">或java [‐options] ‐jar jarfile [args...] (执行 jar 文件)</span><br><span class="line">其中选项包括:</span><br><span class="line">‐d32使用 32 位数据模型 (如果可用)</span><br><span class="line">‐d64使用 64 位数据模型 (如果可用)</span><br><span class="line">‐server选择 "server" VM</span><br><span class="line">默认 VM 是 server,</span><br><span class="line">因为您是在服务器类计算机上运行。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">‐cp <目录和 zip/jar 文件的类搜索路径></span><br><span class="line">‐classpath <目录和 zip/jar 文件的类搜索路径></span><br><span class="line">用 : 分隔的目录, JAR 档案</span><br><span class="line">和 ZIP 档案列表, 用于搜索类文件。</span><br><span class="line"></span><br><span class="line">‐D<名称>=<值></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">设置系统属性</span><br><span class="line"></span><br><span class="line">‐verbose:[class|gc|jni]</span><br><span class="line">启用详细输出</span><br><span class="line">‐version输出产品版本并退出</span><br><span class="line">‐version:<值></span><br><span class="line">警告: 此功能已过时, 将在未来发行版中删除。</span><br><span class="line">需要指定的版本才能运行</span><br><span class="line">‐showversion输出产品版本并继续</span><br><span class="line">‐jre‐restrict‐search | ‐no‐jre‐restrict‐search</span><br><span class="line">警告: 此功能已过时, 将在未来发行版中删除。</span><br><span class="line">在版本搜索中包括/排除用户专用 JRE</span><br><span class="line">‐? ‐help输出此帮助消息</span><br><span class="line">‐X输出非标准选项的帮助</span><br><span class="line">‐ea[:<packagename>...|:<classname>]</span><br><span class="line">‐enableassertions[:<packagename>...|:<classname>]</span><br><span class="line">按指定的粒度启用断言</span><br><span class="line">‐da[:<packagename>...|:<classname>]</span><br><span class="line">‐disableassertions[:<packagename>...|:<classname>]</span><br><span class="line">禁用具有指定粒度的断言</span><br><span class="line"></span><br><span class="line">‐esa | ‐enablesystemassertions</span><br><span class="line">启用系统断言</span><br><span class="line">‐dsa | ‐disablesystemassertions</span><br><span class="line">禁用系统断言</span><br><span class="line">‐agentlib:<libname>[=<选项>]</span><br><span class="line">加载本机代理库 <libname>, 例如 ‐agentlib:hprof</span><br><span class="line">另请参阅 ‐agentlib:jdwp=help 和 ‐agentlib:hprof=help</span><br><span class="line">‐agentpath:<pathname>[=<选项>]</span><br><span class="line">按完整路径名加载本机代理库</span><br><span class="line">‐javaagent:<jarpath>[=<选项>]</span><br><span class="line">加载 Java 编程语言代理, 请参阅 java.lang.instrument</span><br><span class="line">‐splash:<imagepath></span><br><span class="line">使用指定的图像显示启动屏幕</span><br></pre></td></tr></table></figure></div><h3 id="2-2-1、实战"><a href="#2-2-1、实战" class="headerlink" title="2.2.1、实战"></a>2.2.1、实战</h3><blockquote><p>实战1:查看jvm版本</p></blockquote><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# java ‐version</span><br><span class="line">java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)</span><br><span class="line"></span><br><span class="line"># ‐showversion参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用,后面会使用到。</span><br></pre></td></tr></table></figure></div><blockquote><p>实战2:通过-D设置系统属性参数</p></blockquote><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public class TestJVM {</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> String str = System.getProperty("str");</span><br><span class="line"> if(str == null){</span><br><span class="line"> System.out.println("itcast");</span><br><span class="line"> }else{</span><br><span class="line"> System.out.println(str);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.gc();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>进行编译、测试:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">#编译</span><br><span class="line">[root@node01 test]# javac TestJVM.java</span><br><span class="line"></span><br><span class="line">#测试</span><br><span class="line">[root@node01 test]# java TestJVM </span><br><span class="line">itcast</span><br><span class="line">[root@node01 test]# java ‐Dstr=123 TestJVM</span><br><span class="line">123</span><br></pre></td></tr></table></figure></div><h3 id="2-2-2、-server与-client参数"><a href="#2-2-2、-server与-client参数" class="headerlink" title="2.2.2、-server与-client参数"></a>2.2.2、-server与-client参数</h3><p>可以通过-server或-client设置jvm的运行参数。</p><ul><li>它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。</li><li>Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了让JVM的启动速度更快,但运行速度会比Serverm模式慢些。</li><li>JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的 JVM。</li><li>32位操作系统<ul><li>如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。</li><li>如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server模式,否则使用client模式。</li></ul></li><li>64位操作系统<ul><li>只有server类型,不支持client类型。</li></ul></li></ul><p>测试:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# java ‐client ‐showversion TestJVM</span><br><span class="line">java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)</span><br><span class="line">itcast</span><br><span class="line">[root@node01 test]# java ‐server ‐showversion TestJVM</span><br><span class="line">java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)</span><br><span class="line">itcast</span><br><span class="line">#由于机器是64位系统,所以不支持client模式</span><br></pre></td></tr></table></figure></div><h2 id="2-3、-X参数"><a href="#2-3、-X参数" class="headerlink" title="2.3、-X参数"></a>2.3、-X参数</h2><p>jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java - X查看非标准参数。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# java ‐X</span><br><span class="line">‐Xmixed混合模式执行 (默认)</span><br><span class="line">‐Xint仅解释模式执行</span><br><span class="line">‐Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件></span><br><span class="line">设置搜索路径以引导类和资源</span><br><span class="line">‐Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件></span><br><span class="line">附加在引导类路径末尾</span><br><span class="line">‐Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件></span><br><span class="line">置于引导类路径之前</span><br><span class="line">‐Xdiag显示附加诊断消息</span><br><span class="line">‐Xnoclassgc禁用类垃圾收集</span><br><span class="line">‐Xincgc启用增量垃圾收集</span><br><span class="line">‐Xloggc:<file>将 GC 状态记录在文件中 (带时间戳)</span><br><span class="line">‐Xbatch禁用后台编译</span><br><span class="line">‐Xms<size>设置初始 Java 堆大小</span><br><span class="line">‐Xmx<size>设置最大 Java 堆大小</span><br><span class="line">‐Xss<size>设置 Java 线程堆栈大小</span><br><span class="line">‐Xprof输出 cpu 配置文件数据</span><br><span class="line">‐Xfuture启用最严格的检查, 预期将来的默认值</span><br><span class="line">‐Xrs减少 Java/VM 对操作系统信号的使用 (请参阅文档)</span><br><span class="line">‐Xcheck:jni对 JNI 函数执行其他检查</span><br><span class="line">‐Xshare:off不尝试使用共享类数据</span><br><span class="line">‐Xshare:auto在可能的情况下使用共享类数据 (默认)</span><br><span class="line">‐Xshare:on要求使用共享类数据, 否则将失败。</span><br><span class="line">‐XshowSettings显示所有设置并继续</span><br><span class="line">‐XshowSettings:all</span><br><span class="line">显示所有设置并继续</span><br><span class="line">‐XshowSettings:vm 显示所有与 vm 相关的设置并继续</span><br><span class="line">‐XshowSettings:properties</span><br><span class="line">显示所有属性设置并继续</span><br><span class="line">‐XshowSettings:locale</span><br><span class="line">显示所有与区域设置相关的设置并继续</span><br><span class="line"></span><br><span class="line">‐X 选项是非标准选项, 如有更改, 恕不另行通知。</span><br></pre></td></tr></table></figure></div><p>2.3.1、-Xint、-Xcomp、-Xmixed</p><ul><li>在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低10倍或更多。</li><li>-Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。<ul><li>然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是— xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就<br>没有意义了。</li></ul></li><li>-Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模式,也是推荐使用的模式。</li></ul><p>示例:强制设置运行模式</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">#强制设置为解释模式</span><br><span class="line">[root@node01 test]# java‐showversion ‐Xint TestJVM java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, interpreted mode) itcast</span><br><span class="line"></span><br><span class="line">#强制设置为编译模式</span><br><span class="line">[root@node01 test]# java‐showversion ‐Xcomp TestJVM java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, compiled mode)</span><br><span class="line"></span><br><span class="line">itcast</span><br><span class="line">#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#默认的混合模式</span><br><span class="line">[root@node01 test]# java‐showversion TestJVM java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode) itcast</span><br></pre></td></tr></table></figure></div><h2 id="2-4、-XX参数"><a href="#2-4、-XX参数" class="headerlink" title="2.4、-XX参数"></a>2.4、-XX参数</h2><p>-XX参数也是非标准参数,主要用于jvm的调优和debug操作。<br>-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型: boolean类型<br>格式:-XX:[±]<br>如:-XX:+DisableExplicitGC 表示禁用手动调用gc操作,也就是说调用<br>System.gc()无效<br>非boolean类型格式:-XX:<br>如:-XX:NewRatio=1 表示新生代和老年代的比值<br>用法:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# java ‐showversion ‐XX:+DisableExplicitGC TestJVM</span><br><span class="line">java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)</span><br><span class="line"></span><br><span class="line">itcast</span><br></pre></td></tr></table></figure></div><h2 id="2-5、-Xms与-Xmx参数"><a href="#2-5、-Xms与-Xmx参数" class="headerlink" title="2.5、-Xms与-Xmx参数"></a>2.5、-Xms与-Xmx参数</h2><p>Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。<br>-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。<br>-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。示例:[root@node01 test]# java ‐Xms512m ‐Xmx2048m TestJVM itcast</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# java ‐Xms512m ‐Xmx2048m TestJVM </span><br><span class="line"></span><br><span class="line">itcast</span><br></pre></td></tr></table></figure></div><h2 id="2-6、查看jvm的运行参数"><a href="#2-6、查看jvm的运行参数" class="headerlink" title="2.6、查看jvm的运行参数"></a>2.6、查看jvm的运行参数</h2><p>有些时候我们需要查看jvm的运行参数,这个需求可能会存在2种情况:</p><p>第一,运行java命令时打印出运行参数; </p><p>第二,查看正在运行的java进程的参数;</p><h3 id="2-6-1、运行java命令时打印参数"><a href="#2-6-1、运行java命令时打印参数" class="headerlink" title="2.6.1、运行java命令时打印参数"></a>2.6.1、运行java命令时打印参数</h3><p>运行java命令时打印参数,需要添加-XX:+PrintFlagsFinal参数即可。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# java ‐XX:+PrintFlagsFinal ‐version [Global flags]</span><br><span class="line">uintx AdaptiveSizeDecrementScaleFactor= 4</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizeMajorGCDecayTimeScale= 10</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizePausePolicy= 0</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizePolicyCollectionCostMargin= 50</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizePolicyInitializingSteps= 20</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizePolicyOutputInterval= 0</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizePolicyWeight= 10</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveSizeThroughPutPolicy= 0</span><br><span class="line">{product}</span><br><span class="line">uintx AdaptiveTimeWeight= 25</span><br><span class="line">{product}</span><br><span class="line">bool AdjustConcurrency= false</span><br><span class="line">{product}</span><br><span class="line">bool AggressiveOpts= false</span><br><span class="line">{product}</span><br><span class="line">intx AliasLevel= 3</span><br><span class="line">{C2 product}</span><br><span class="line">bool AlignVector= true</span><br><span class="line">{C2 product}</span><br><span class="line">intx AllocateInstancePrefetchLines= 1</span><br><span class="line">{product}</span><br><span class="line">intx AllocatePrefetchDistance= 256</span><br><span class="line">{product}</span><br><span class="line">intx AllocatePrefetchInstr= 0</span><br><span class="line">{product}</span><br><span class="line"></span><br><span class="line">…………………………略…………………………………………</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">bool UseXmmI2D= false</span><br><span class="line">{C1 product}</span><br><span class="line">intx ValueSearchLimit= 1000</span><br><span class="line">{C2 product}</span><br><span class="line">bool VerifyMergedCPBytecodes= true</span><br><span class="line">{product}</span><br><span class="line">bool VerifySharedSpaces= false</span><br><span class="line">{product}</span><br><span class="line">intx WorkAroundNPTLTimedWaitHang= 1</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeIncrement= 20</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeSupplement= 80</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeSupplementDecay= 8</span><br><span class="line">{product}</span><br><span class="line">uintx YoungPLABSize= 4096</span><br><span class="line">{product}</span><br><span class="line">bool ZeroTLAB= false</span><br><span class="line">{product}</span><br><span class="line">intx hashCode= 5</span><br><span class="line">{product} java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)</span><br></pre></td></tr></table></figure></div><p>由上述的信息可以看出,参数有boolean类型和数字类型,值的操作符是=或:=,分别代 表默认值和被修改的值。</p><p>示例:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">java ‐XX:+PrintFlagsFinal ‐XX:+VerifySharedSpaces ‐version</span><br><span class="line"></span><br><span class="line">intx ValueMapInitialSize= 11</span><br><span class="line">{C1 product}</span><br><span class="line">intx ValueMapMaxLoopSize= 8</span><br><span class="line">{C1 product}</span><br><span class="line">intx ValueSearchLimit= 1000</span><br><span class="line">{C2 product}</span><br><span class="line">bool VerifyMergedCPBytecodes= true</span><br><span class="line">{product}</span><br><span class="line">bool VerifySharedSpaces:= true</span><br><span class="line">{product}</span><br><span class="line">intx WorkAroundNPTLTimedWaitHang= 1</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeIncrement= 20</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeSupplement= 80</span><br><span class="line">{product}</span><br><span class="line">uintx YoungGenerationSizeSupplementDecay= 8</span><br><span class="line">{product}</span><br><span class="line">uintx YoungPLABSize= 4096</span><br><span class="line">{product}</span><br><span class="line">bool ZeroTLAB= false</span><br><span class="line">{product}</span><br><span class="line">intx hashCode= 5</span><br><span class="line">{product} java version "1.8.0_141"</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)</span><br><span class="line">Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode) </span><br><span class="line"></span><br><span class="line">#可以看到VerifySharedSpaces这个参数已经被修改了。</span><br></pre></td></tr></table></figure></div><h3 id="2-6-2、查看正在运行的jvm参数"><a href="#2-6-2、查看正在运行的jvm参数" class="headerlink" title="2.6.2、查看正在运行的jvm参数"></a>2.6.2、查看正在运行的jvm参数</h3><p>如果想要查看正在运行的jvm就需要借助于jinfo命令查看。<br>首先,启动一个tomcat用于测试,来观察下运行的jvm参数。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cd /tmp/</span><br><span class="line">rz 上传</span><br><span class="line">tar ‐xvf apache‐tomcat‐7.0.57.tar.gz cd apache‐tomcat‐7.0.57</span><br><span class="line">cd bin/</span><br><span class="line">./startup.sh</span><br><span class="line"></span><br><span class="line">#http://122.51.193.216:8080/ 进行访问</span><br></pre></td></tr></table></figure></div><p>访问成功:</p><p> <img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219155258725.png" alt="image-20191219155258725"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">#查看所有的参数,用法:jinfo ‐flags <进程id></span><br><span class="line"></span><br><span class="line">#通过jps 或者jps ‐l 查看java进程</span><br><span class="line">[root@node01 bin]# jps 6346 Jps</span><br><span class="line">6219 Bootstrap [root@node01 bin]# jps ‐l 6358 sun.tools.jps.Jps</span><br><span class="line">6219 org.apache.catalina.startup.Bootstrap [root@node01 bin]#</span><br><span class="line"></span><br><span class="line">[root@node01 bin]# jinfo ‐flags 6219 Attaching to process ID 6219, please wait... Debugger attached successfully.</span><br><span class="line">Server compiler detected. JVM version is 25.141‐b15</span><br><span class="line">Non‐default VM flags: ‐XX:CICompilerCount=2 ‐XX:InitialHeapSize=31457280</span><br><span class="line">‐XX:MaxHeapSize=488636416 ‐XX:MaxNewSize=162529280 ‐ XX:MinHeapDeltaBytes=524288 ‐XX:NewSize=10485760 ‐XX:OldSize=20971520 ‐ XX:+UseCompressedClassPointers ‐XX:+UseCompressedOops ‐ XX:+UseFastUnorderedTimeStamps ‐XX:+UseParallelGC</span><br><span class="line">Command line:‐Djava.util.logging.config.file=/tmp/apache‐tomcat‐ 7.0.57/conf/logging.properties ‐ Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager ‐ Djava.endorsed.dirs=/tmp/apache‐tomcat‐7.0.57/endorsed ‐ Dcatalina.base=/tmp/apache‐tomcat‐7.0.57 ‐Dcatalina.home=/tmp/apache‐ tomcat‐7.0.57 ‐Djava.io.tmpdir=/tmp/apache‐tomcat‐7.0.57/temp</span><br><span class="line"></span><br><span class="line">#查看某一参数的值,用法:jinfo ‐flag <参数名> <进程id> [root@node01 bin]# jinfo ‐flag MaxHeapSize 6219</span><br><span class="line">‐XX:MaxHeapSize=488636416</span><br></pre></td></tr></table></figure></div><h1 id="3、jvm的内存模型"><a href="#3、jvm的内存模型" class="headerlink" title="3、jvm的内存模型"></a>3、jvm的内存模型</h1><p>jvm的内存模型在1.7和1.8有较大的区别,虽然本套课程是以1.8为例进行讲解,但是我们 也是需要对1.7的内存模型有所了解,所以接下里,我们将先学习1.7再学习1.8的内存模 型。</p><h2 id="3-1、jdk1-7的堆内存模型"><a href="#3-1、jdk1-7的堆内存模型" class="headerlink" title="3.1、jdk1.7的堆内存模型"></a>3.1、jdk1.7的堆内存模型</h2><p> <img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219155224831.png" alt="image-20191219155224831"></p><ul><li>Young 年轻区(代)<br>Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中, Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制 对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。</li><li>Tenured 年老区<br>Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young 复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。</li><li>Perm 永久区<br>Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性 加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造 成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以 解决问题。</li><li>Virtual区:<ul><li>最大内存和初始内存的差值,就是Virtual区。</li></ul></li></ul><p>3.2、jdk1.8的堆内存模型</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219155636643.png" alt="image-20191219155636643"></p><p>由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。</p><p>年轻代:Eden + 2*Survivor<br>年老代:OldGen<br>在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。</p><p>需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存 空间中,这也是与1.7的永久代最大的区别所在。</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219155940784.png" alt="image-20191219155940784"></p><p>其中:CodeCache存放的是一些类和class。CCS代表的是一些压缩指针。</p><p>3.3、为什么要废弃1.7中的永久区?<br>官网给出了解释:<a href="http://openjdk.java.net/jeps/122" target="_blank" rel="noopener">http://openjdk.java.net/jeps/122</a></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">This is part of the JRockit and Hotspot convergence effort. JRockit</span><br><span class="line">customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.</span><br><span class="line"></span><br><span class="line">移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,</span><br><span class="line">不需要配置永久代。</span><br></pre></td></tr></table></figure></div><p>现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异常<br>java.lang.OutOfMemoryError: PermGen。<br>基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。</p><p>3.4、通过jstat命令进行查看堆内存使用情况<br>jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下: jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]<br>3.4.1、查看class加载统计</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# jps</span><br><span class="line">7080 Jps</span><br><span class="line">6219 Bootstrap</span><br><span class="line">[root@node01 ~]# jstat ‐class 6219</span><br><span class="line"></span><br><span class="line">LoadedBytesUnloadedBytesTime</span><br><span class="line">32737122.300.03.98</span><br></pre></td></tr></table></figure></div><p>说明:</p><p>Loaded:加载class的数量<br>Bytes:所占用空间大小<br>Unloaded:未加载数量<br>Bytes:未加载占用空间<br>Time:时间<br><strong>3.4.2、查看编译统计</strong></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# jstat ‐compiler 6219</span><br><span class="line">Compiled Failed InvalidTimeFailedType FailedMethod 2376108.041</span><br><span class="line">org/apache/tomcat/util/IntrospectionUtils setProperty</span><br></pre></td></tr></table></figure></div><p>说明:</p><p>Compiled:编译数量。<br>Failed:失败数量<br>Invalid:不可用数量<br>Time:时间<br>FailedType:失败类型<br>FailedMethod:失败的方法<br><strong>3.4.3、垃圾回收统计</strong></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# jstat ‐gc 6219</span><br><span class="line">S0CS1CS0US1UECEUOCOUMC</span><br><span class="line">MUCCSCCCSUYGCYGCTFGCFGCTGCT</span><br><span class="line">9216.0 8704.00.06127.3 62976.03560.433792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br></pre></td></tr></table></figure></div><p> #也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# jstat ‐gc 6219 1000 5</span><br><span class="line">S0CS1CS0US1UECEUOCOUMC MUCCSCCCSUYGCYGCTFGCFGCTGCT</span><br><span class="line">9216.0 8704.00.06127.3 62976.03917.333792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br><span class="line">9216.0 8704.00.06127.3 62976.03917.333792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br><span class="line">9216.0 8704.00.06127.3 62976.03917.333792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br><span class="line">9216.0 8704.00.06127.3 62976.03917.333792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br><span class="line">9216.0 8704.00.06127.3 62976.03917.333792.020434.9</span><br><span class="line">23808.0 23196.1 2560.0 2361.671.07810.2441.323</span><br></pre></td></tr></table></figure></div><p>说明:</p><p>S0C:第一个Survivor区的大小(KB)<br>S1C:第二个Survivor区的大小(KB)<br>S0U:第一个Survivor区的使用大小(KB)<br>S1U:第二个Survivor区的使用大小(KB) EC:Eden区的大小(KB)<br>EU:Eden区的使用大小(KB)<br>OC:Old 区 大 小 (KB)<br>OU:Old 使 用 大 小 (KB)<br>MC:方法区大小(KB)<br>MU:方法区使用大小(KB)<br>CCSC:压缩类空间大小(KB)<br>CCSU:压缩类空间使用大小(KB)<br>YGC:年轻代垃圾回收次数<br>YGCT:年轻代垃圾回收消耗时间<br>FGC:老年代垃圾回收次数<br>FGCT:老年代垃圾回收消耗时间<br>GCT:垃圾回收消耗总时间</p><h1 id="4、jmap的使用以及内存溢出分析"><a href="#4、jmap的使用以及内存溢出分析" class="headerlink" title="4、jmap的使用以及内存溢出分析"></a>4、jmap的使用以及内存溢出分析</h1><p>前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容, 如:内存使用情况的汇总、对内存溢出的定位与分析。</p><h2 id="4-1、查看内存使用情况"><a href="#4-1、查看内存使用情况" class="headerlink" title="4.1、查看内存使用情况"></a>4.1、查看内存使用情况</h2><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">[root@# jmap ‐heap 6219</span><br><span class="line">Attaching to process ID 6219, please wait... Debugger attached successfully.</span><br><span class="line">Server compiler detected.</span><br><span class="line">JVM version is 25.141‐b15</span><br><span class="line"></span><br><span class="line">using thread‐local object allocation. Parallel GC with 2 thread(s)</span><br><span class="line"></span><br><span class="line">Heap Configuration: #堆内存配置信息MinHeapFreeRatio= 0</span><br><span class="line">MaxHeapFreeRatio= 100</span><br><span class="line">MaxHeapSize= 488636416 (466.0MB)</span><br><span class="line">NewSize= 10485760 (10.0MB)</span><br><span class="line">MaxNewSize= 162529280 (155.0MB)</span><br><span class="line">OldSize= 20971520 (20.0MB)</span><br><span class="line">NewRatio= 2</span><br><span class="line">SurvivorRatio= 8</span><br><span class="line">MetaspaceSize= 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize= 17592186044415 MB</span><br><span class="line">G1HeapRegionSize= 0 (0.0MB)</span><br><span class="line"></span><br><span class="line">Heap Usage: # 堆内存的使用情况PS Young Generation #年轻代Eden Space:</span><br><span class="line">capacity = 123731968 (118.0MB)</span><br><span class="line">used= 1384736 (1.320587158203125MB) free= 122347232 (116.67941284179688MB) 1.1191416594941737% used</span><br><span class="line">From Space:</span><br><span class="line">capacity = 9437184 (9.0MB) used= 0 (0.0MB)</span><br><span class="line">free= 9437184 (9.0MB)</span><br><span class="line">0.0% used To Space:</span><br><span class="line">capacity = 9437184 (9.0MB) used= 0 (0.0MB)</span><br><span class="line">free= 9437184 (9.0MB)</span><br><span class="line"></span><br><span class="line">0.0% used</span><br><span class="line">PS Old Generation #年老代</span><br><span class="line">capacity = 28311552 (27.0MB)</span><br><span class="line">used = 13698672 (13.064071655273438MB) free = 14612880 (13.935928344726562MB) 48.38545057508681% used</span><br><span class="line">13648 interned Strings occupying 1866368 bytes.</span><br></pre></td></tr></table></figure></div><h2 id="4-2、查看内存中对象数量及大小"><a href="#4-2、查看内存中对象数量及大小" class="headerlink" title="4.2、查看内存中对象数量及大小"></a>4.2、查看内存中对象数量及大小</h2><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">#查看所有对象,包括活跃以及非活跃的jmap ‐histo <pid> | more</span><br><span class="line"></span><br><span class="line">#查看活跃对象</span><br><span class="line">jmap ‐histo:live <pid> | more</span><br><span class="line">[root@node01 ~]# jmap ‐histo:live 6219 | more num#instances#bytesclass name</span><br><span class="line">‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1:374377914608[C</span><br><span class="line">2:34916837984java.lang.String</span><br><span class="line">3:884654848[B</span><br><span class="line">4:17188550016java.util.HashMap$Node</span><br><span class="line">5:3674424968java.lang.Class</span><br><span class="line">6:6322395512[Ljava.lang.Object;</span><br><span class="line">7:3738328944java.lang.reflect.Method</span><br><span class="line">8:1028208048[Ljava.util.HashMap$Node;</span><br><span class="line">9:2247144264[I</span><br><span class="line">10:4305137760</span><br><span class="line">java.util.concurrent.ConcurrentHashMap$Node</span><br><span class="line">11:1270109080[Ljava.lang.String;</span><br><span class="line">12:6484128</span><br><span class="line">[Ljava.util.concurrent.ConcurrentHashMap$Node;</span><br><span class="line">13:171482272java.util.HashMap</span><br><span class="line">14:328570072[Ljava.lang.Class;</span><br><span class="line">15:288869312java.util.ArrayList</span><br><span class="line">16:398363728java.lang.Object</span><br><span class="line">17:127161008</span><br><span class="line">org.apache.tomcat.util.digester.CallMethodRule</span><br><span class="line">18:151860720java.util.LinkedHashMap$Entry</span><br><span class="line">19:167153472</span><br><span class="line">com.sun.org.apache.xerces.internal.xni.QName</span><br><span class="line">20:8850880[Ljava.util.WeakHashMap$Entry;</span><br><span class="line">21:61849440java.lang.reflect.Constructor</span><br><span class="line">22:154549440java.util.Hashtable$Entry</span><br><span class="line">23:102741080java.util.TreeMap$Entry</span><br><span class="line">24:84640608</span><br><span class="line">org.apache.tomcat.util.modeler.AttributeInfo 25:14238032[S</span><br><span class="line">26:94637840java.lang.ref.SoftReference</span><br><span class="line">27:22636816[[C</span><br><span class="line">。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。</span><br><span class="line"></span><br><span class="line">#对象说明</span><br><span class="line">Bbyte</span><br><span class="line">Cchar</span><br><span class="line">Ddouble</span><br><span class="line">Ffloat</span><br><span class="line">Iint</span><br><span class="line">Jlong</span><br><span class="line">Zboolean</span><br><span class="line">[ 数组,如[I表示int[]</span><br><span class="line">[L+类名 其他对象</span><br></pre></td></tr></table></figure></div><h2 id="4-3、将内存使用情况dump到文件中"><a href="#4-3、将内存使用情况dump到文件中" class="headerlink" title="4.3、将内存使用情况dump到文件中"></a>4.3、将内存使用情况dump到文件中</h2><p>有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也 是支持dump到文件中的。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#用法:</span><br><span class="line">jmap ‐dump:format=b,file=dumpFileName <pid></span><br><span class="line">#示例</span><br><span class="line">jmap ‐dump:format=b,file=/tmp/dump.dat 6219</span><br></pre></td></tr></table></figure></div><p> <img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/20190619215656739.png" alt="img"> </p><p>可以看到已经在/tmp下生成了dump.dat的文件。</p><h2 id="4-4、通过jhat对dump文件进行分析"><a href="#4-4、通过jhat对dump文件进行分析" class="headerlink" title="4.4、通过jhat对dump文件进行分析"></a>4.4、通过jhat对dump文件进行分析</h2><p>在上一小节中,我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方 便查看,这时我们可以借助于jhat工具进行查看。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#用法:</span><br><span class="line">jhat ‐port <port> <file></span><br><span class="line"></span><br><span class="line">#示例:</span><br><span class="line">[root@node01 tmp]# jhat ‐port 9999 /tmp/dump.dat Reading from /tmp/dump.dat...</span><br><span class="line">Dump file created Mon Sep 10 01:04:21 CST 2018 Snapshot read, resolving...</span><br><span class="line">Resolving 204094 objects...</span><br><span class="line">Chasing references, expect 40 dots........................................</span><br><span class="line">Eliminating duplicate references........................................</span><br><span class="line">Snapshot resolved.</span><br><span class="line">Started HTTP server on port 7000 Server is ready.</span><br></pre></td></tr></table></figure></div><p>打开浏览器进行访问:<a href="http://192.168.40.133:7000/" target="_blank" rel="noopener">http://192.168.40.133:7000/</a> </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219173358911.png" alt="image-20191219173358911"></p><p> 在最后面有OQL查询功能。 </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219173557859.png" alt="image-20191219173557859"></p><p>如:查询字符长度大于100的内容。</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219173901648.png" alt="image-20191219173901648"></p><h2 id="4-5、通过MAT工具对dump文件进行分析"><a href="#4-5、通过MAT工具对dump文件进行分析" class="headerlink" title="4.5、通过MAT工具对dump文件进行分析"></a>4.5、通过MAT工具对dump文件进行分析</h2><p>4.5.1、MAT工具介绍<br>MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止 了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。</p><p> 官网地址:<a href="https://www.eclipse.org/mat/" target="_blank" rel="noopener">https://www.eclipse.org/mat/</a> </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219175033119.png" alt="image-20191219175033119"></p><p> <strong>4.5.2、下载安装</strong><br>下载地址:<a href="https://www.eclipse.org/mat/downloads.php" target="_blank" rel="noopener">https://www.eclipse.org/mat/downloads.php</a> </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219175208607.png" alt="image-20191219175208607"></p><p> 将下载得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip进行解压 </p><p> <img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219175951080.png" alt="image-20191219175951080"></p><p> <strong>4.5.3、使用</strong> </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219180326233.png" alt="image-20191219180326233"></p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219181250853.png" alt="image-20191219181250853"></p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219181317573.png" alt="image-20191219181317573"></p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219181348201.png" alt="image-20191219181348201"></p><p>查看对象以及它的依赖: </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219181413557.png" alt="image-20191219181413557"></p><p> 查看可能存在内存泄露 的分析: </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219181441422.png" alt="image-20191219181441422"></p><h1 id="5、实战:内存溢出的定位与分析"><a href="#5、实战:内存溢出的定位与分析" class="headerlink" title="5、实战:内存溢出的定位与分析"></a>5、实战:内存溢出的定位与分析</h1><p>内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。<br>如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。<br>首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。<br>接下来,我们模拟内存溢出的场景。</p><h2 id="5-1、模拟内存溢出"><a href="#5-1、模拟内存溢出" class="headerlink" title="5.1、模拟内存溢出"></a>5.1、模拟内存溢出</h2><p>编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程 序能够正常执行,最后打印ok。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">package cn.itcast.jvm;</span><br><span class="line"></span><br><span class="line">import java.util.ArrayList;</span><br><span class="line">import java.util.List;</span><br><span class="line">import java.util.UUID;</span><br><span class="line"></span><br><span class="line">public class TestJvmOutOfMemory {</span><br><span class="line"></span><br><span class="line"> // 实现,向集合中添加100万个字符串,每个字符串由1000个UUID组成</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> List<String> list = new ArrayList<>();</span><br><span class="line"> for (int i = 0; i < 1000000; i++) {</span><br><span class="line"> String str = "";</span><br><span class="line"> for (int j = 0; j < 1000; j++) {</span><br><span class="line"> str += UUID.randomUUID().toString();</span><br><span class="line"> }</span><br><span class="line"> list.add(str);</span><br><span class="line"> }</span><br><span class="line"> System.out.println("ok");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p> 为了演示效果,我们将设置执行的参数,这里使用的是Idea编辑器。 </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219221555326.png" alt="image-20191219221555326"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#参数如下:</span><br><span class="line">‐Xms8m ‐Xmx8m ‐XX:+HeapDumpOnOutOfMemoryError</span><br></pre></td></tr></table></figure></div><h2 id="5-2、运行测试"><a href="#5-2、运行测试" class="headerlink" title="5.2、运行测试"></a>5.2、运行测试</h2><p>测试结果如下:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid5348.hprof ...</span><br><span class="line">Heap dump file created [8137186 bytes in 0.032 secs]</span><br><span class="line">Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332)</span><br><span class="line">at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuil der.java:124)</span><br><span class="line">at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)</span><br><span class="line">at java.lang.StringBuilder.append(StringBuilder.java:136)</span><br><span class="line">at cn.itcast.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14) Process finished with exit code 1</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219221823077.png" alt="image-20191219221823077"></p><p> 可以看到,当发生内存溢出时,会dump文件到java_pid5348.hprof。 </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219221736582.png" alt="image-20191219221736582"></p><h2 id="5-3、导入到MAT工具中进行分析"><a href="#5-3、导入到MAT工具中进行分析" class="headerlink" title="5.3、导入到MAT工具中进行分析"></a>5.3、导入到MAT工具中进行分析</h2><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219221936015.png" alt="image-20191219221936015"></p><p> 可以看到,有91.03%的内存由Object[]数组占有,所以比较可疑。<br>分析:这个可疑是正确的,因为已经有超过90%的内存都被它占有,这是非常有可能出现内存溢出的。 </p><p> 查看详情:</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219222053874.png" alt="image-20191219222053874"></p><p>可以看到集合中存储了大量的uuid字符串。 </p><h1 id="6、jstack的使用"><a href="#6、jstack的使用" class="headerlink" title="6、jstack的使用"></a>6、jstack的使用</h1><p>有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?</p><p>由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要 看下jvm的内部线程的执行情况,然后再进行分析查找出原因。<br>这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进 行快照,并且打印出来:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br></pre></td><td class="code"><pre><span class="line">#用法:jstack <pid></span><br><span class="line"></span><br><span class="line">[root@node01 bin]# jstack 2203</span><br><span class="line">Full thread dump Java HotSpot(TM) 64‐Bit Server VM (25.141‐b15 mixed mode):</span><br><span class="line"></span><br><span class="line">"Attach Listener" #24 daemon prio=9 os_prio=0 tid=0x00007fabb4001000 nid=0x906 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐exec‐5" #23 daemon prio=5 os_prio=0 tid=0x00007fabb057c000 nid=0x8e1 waiting on condition [0x00007fabd05b8000]</span><br><span class="line">java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">‐ parking to wait for<0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at</span><br><span class="line">java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa it(AbstractQueuedSynchronizer.java:2039)</span><br><span class="line">at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:44 2)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1 074)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java</span><br><span class="line">:1134)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.jav a:624)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread</span><br><span class="line">.java:61)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐exec‐4" #22 daemon prio=5 os_prio=0 tid=0x00007fab9c113800</span><br><span class="line">nid=0x8e0 waiting on condition [0x00007fabd06b9000] java.lang.Thread.State: WAITING (parking)</span><br><span class="line">at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">‐ parking to wait for<0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at</span><br><span class="line">java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa it(AbstractQueuedSynchronizer.java:2039)</span><br><span class="line">at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:44 2)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1 074)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java</span><br><span class="line">:1134)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.jav a:624)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread</span><br><span class="line">.java:61)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐exec‐3" #21 daemon prio=5 os_prio=0 tid=0x0000000001aeb800 nid=0x8df waiting on condition [0x00007fabd09ba000]</span><br><span class="line">java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">‐ parking to wait for<0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at</span><br><span class="line">java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa it(AbstractQueuedSynchronizer.java:2039)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">at</span><br><span class="line">java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:44 2)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1 074)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java</span><br><span class="line">:1134)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.jav a:624)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread</span><br><span class="line">.java:61)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐exec‐2" #20 daemon prio=5 os_prio=0 tid=0x0000000001aea000 nid=0x8de waiting on condition [0x00007fabd0abb000]</span><br><span class="line">java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">‐ parking to wait for<0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at</span><br><span class="line">java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa it(AbstractQueuedSynchronizer.java:2039)</span><br><span class="line">at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:44 2)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1 074)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java</span><br><span class="line">:1134)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.jav a:624)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread</span><br><span class="line">.java:61)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐exec‐1" #19 daemon prio=5 os_prio=0 tid=0x0000000001ae8800 nid=0x8dd waiting on condition [0x00007fabd0bbc000]</span><br><span class="line">java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">‐ parking to wait for<0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at</span><br><span class="line">java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa it(AbstractQueuedSynchronizer.java:2039)</span><br><span class="line">at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:44 2)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at</span><br><span class="line">java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1 074)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java</span><br><span class="line">:1134)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.jav a:624)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread</span><br><span class="line">.java:61)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"ajp‐bio‐8009‐AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007fabe8128000 nid=0x8d0 waiting on condition [0x00007fabd0ece000]</span><br><span class="line">java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)</span><br><span class="line">at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java: 152)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"ajp‐bio‐8009‐Acceptor‐0" #16 daemon prio=5 os_prio=0 tid=0x00007fabe82d4000 nid=0x8cf runnable [0x00007fabd0fcf000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">at java.net.PlainSocketImpl.socketAccept(Native Method) at</span><br><span class="line">java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) at java.net.ServerSocket.implAccept(ServerSocket.java:545)</span><br><span class="line">at java.net.ServerSocket.accept(ServerSocket.java:513) at</span><br><span class="line">org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(Defaul tServerSocketFactory.java:60)</span><br><span class="line">at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐AsyncTimeout" #15 daemon prio=5 os_prio=0 tid=0x00007fabe82d1800 nid=0x8ce waiting on condition [0x00007fabd10d0000]</span><br><span class="line">java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)</span><br><span class="line">at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java: 152)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"http‐bio‐8080‐Acceptor‐0" #14 daemon prio=5 os_prio=0 tid=0x00007fabe82d0000 nid=0x8cd runnable [0x00007fabd11d1000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">at java.net.PlainSocketImpl.socketAccept(Native Method) at</span><br><span class="line">java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) at java.net.ServerSocket.implAccept(ServerSocket.java:545)</span><br><span class="line">at java.net.ServerSocket.accept(ServerSocket.java:513)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">at</span><br><span class="line">org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(Defaul tServerSocketFactory.java:60)</span><br><span class="line">at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"ContainerBackgroundProcessor[StandardEngine[Catalina]]" #13 daemon prio=5 os_prio=0 tid=0x00007fabe82ce000 nid=0x8cc waiting on condition [0x00007fabd12d2000]</span><br><span class="line">java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)</span><br><span class="line">at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(C ontainerBase.java:1513)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"GC Daemon" #10 daemon prio=2 os_prio=0 tid=0x00007fabe83b4000 nid=0x8b3 in Object.wait() [0x00007fabd1c2f000]</span><br><span class="line">java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method)</span><br><span class="line">‐waiting on <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock) at sun.misc.GC$Daemon.run(GC.java:117)</span><br><span class="line">‐locked <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)</span><br><span class="line"></span><br><span class="line">"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fabe80c3800 nid=0x8a5 runnable [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fabe80b6800 nid=0x8a4 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fabe80b3800 nid=0x8a3 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fabe80b2000 nid=0x8a2 runnable [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fabe807f000 nid=0x8a1</span><br><span class="line">in Object.wait() [0x00007fabd2a67000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)</span><br><span class="line">‐ waiting on <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class="line">at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)</span><br><span class="line">‐ locked <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)</span><br><span class="line">at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)</span><br><span class="line"></span><br><span class="line">"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fabe807a800 nid=0x8a0 in Object.wait() [0x00007fabd2b68000]</span><br><span class="line">java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)</span><br><span class="line">‐waiting on <0x00000000e3162958> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502)</span><br><span class="line">at java.lang.ref.Reference.tryHandlePending(Reference.java:191)</span><br><span class="line">‐locked <0x00000000e3162958> (a java.lang.ref.Reference$Lock)</span><br><span class="line">at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)</span><br><span class="line"></span><br><span class="line">"main" #1 prio=5 os_prio=0 tid=0x00007fabe8009000 nid=0x89c runnable [0x00007fabed210000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">at java.net.PlainSocketImpl.socketAccept(Native Method) at</span><br><span class="line">java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) at java.net.ServerSocket.implAccept(ServerSocket.java:545)</span><br><span class="line">at java.net.ServerSocket.accept(ServerSocket.java:513) at</span><br><span class="line">org.apache.catalina.core.StandardServer.await(StandardServer.java:453) at org.apache.catalina.startup.Catalina.await(Catalina.java:777) at org.apache.catalina.startup.Catalina.start(Catalina.java:723) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)</span><br><span class="line">at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java</span><br><span class="line">:62)</span><br><span class="line">at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorI mpl.java:43)</span><br><span class="line">at java.lang.reflect.Method.invoke(Method.java:498)</span><br><span class="line"></span><br><span class="line">at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:321)</span><br><span class="line">at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:455)</span><br></pre></td></tr></table></figure></div><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">"VM Thread" os_prio=0 tid=0x00007fabe8073000 nid=0x89f runnable</span><br><span class="line"></span><br><span class="line">"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fabe801e000</span><br><span class="line">nid=0x89d runnable</span><br><span class="line"></span><br><span class="line">"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fabe8020000</span><br><span class="line">nid=0x89e runnable</span><br><span class="line">"VM Periodic Task Thread" os_prio=0 tid=0x00007fabe80d6800 nid=0x8a6</span><br><span class="line">waiting on condition</span><br><span class="line">JNI global references: 43</span><br></pre></td></tr></table></figure></div><h2 id="6-1、线程的状态"><a href="#6-1、线程的状态" class="headerlink" title="6.1、线程的状态"></a>6.1、线程的状态</h2><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219222439231.png" alt="image-20191219222439231"></p><p>在Java中线程的状态一共被分成6种:</p><ul><li>初始态(NEW)<br>创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。</li><li><p>运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。</p><ul><li>就绪态<ul><li>该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。</li><li>所有就绪态的线程存放在就绪队列中。</li></ul></li><li>运行态<ul><li>获得CPU执行权,正在执行的线程。</li><li>由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条<br>运行态的线程。</li></ul></li></ul></li><li><p>阻塞态(BLOCKED)</p><ul><li>当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。</li><li>而在Java中,阻塞态专指请求锁失败时进入的状态。</li><li>由一个阻塞队列存放所有阻塞态的线程。</li><li>处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。</li></ul></li><li><p>等待态(WAITING)</p><ul><li>当前线程中调用wait、join、park函数时,当前线程就会进入等待态。 </li><li>也有一个等待队列存放所有等待态的线程。</li><li>线程处于等待态表示它需要等待其他线程的指示才能继续运行。</li><li>进入等待态的线程会释放CPU执行权,并释放资源(如:锁)</li></ul></li><li><p>超时等待态(TIMED_WAITING)</p><ul><li>当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;</li><li>它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;</li><li>进入该状态后释放CPU执行权 和 占有的资源。</li><li>与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。</li></ul></li><li><p>终止态(TERMINATED)</p><pre><code> 线程执行结束后的状态。</code></pre></li></ul><h2 id="6-2、实战:死锁问题"><a href="#6-2、实战:死锁问题" class="headerlink" title="6.2、实战:死锁问题"></a>6.2、实战:死锁问题</h2><p>如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我 们可以借助jstack进行分析,下面我们实战下查找死锁的原因。</p><h3 id="6-2-1、构造死锁"><a href="#6-2-1、构造死锁" class="headerlink" title="6.2.1、构造死锁"></a>6.2.1、构造死锁</h3><p>编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被Thread2锁定,所以发送了死锁。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">package cn.itcast.jvm;</span><br><span class="line"></span><br><span class="line">public class TestDeadLock {</span><br><span class="line"></span><br><span class="line"> private static Object obj1 = new Object();</span><br><span class="line"></span><br><span class="line"> private static Object obj2 = new Object();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> new Thread(new Thread1()).start();</span><br><span class="line"> new Thread(new Thread2()).start();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private static class Thread1 implements Runnable{</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> synchronized (obj1){</span><br><span class="line"> System.out.println("Thread1 拿到了 obj1 的锁!");</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁</span><br><span class="line"> Thread.sleep(2000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> synchronized (obj2){</span><br><span class="line"> System.out.println("Thread1 拿到了 obj2 的锁!");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private static class Thread2 implements Runnable{</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> synchronized (obj2){</span><br><span class="line"> System.out.println("Thread2 拿到了 obj2 的锁!");</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁</span><br><span class="line"> Thread.sleep(2000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> synchronized (obj1){</span><br><span class="line"> System.out.println("Thread2 拿到了 obj1 的锁!");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p> <strong>6.2.2、在linux上运行</strong> </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 test]# javac TestDeadLock.java</span><br><span class="line">[root@node01 test]# ll</span><br><span class="line">总用量 28</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot184 9月1110:39TestDeadLock$1.class</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot843 9月1110:39TestDeadLock.class</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot1567 9月1110:39TestDeadLock.java</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot1078 9月1110:39TestDeadLock$Thread1.class</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot1078 9月1110:39TestDeadLock$Thread2.class</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot573 9月910:21TestJVM.class</span><br><span class="line">‐rw‐r‐‐r‐‐.1rootroot261 9月910:21TestJVM.java</span><br><span class="line"></span><br><span class="line"> [root@node01 test]# java TestDeadLock</span><br><span class="line">Thread1 拿到了 obj1 的锁!</span><br><span class="line">Thread2 拿到了 obj2 的锁!</span><br><span class="line">#这里发生了死锁,程序一直将等待下去</span><br></pre></td></tr></table></figure></div><p><strong>6.2.3、使用jstack进行分析</strong> </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line">[root@node01 ~]# jstack 3256</span><br><span class="line">Full thread dump Java HotSpot(TM) 64‐Bit Server VM (25.141‐b15 mixed mode):</span><br><span class="line"></span><br><span class="line">"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f5bfc001000 nid=0xcff waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f5c2c008800 nid=0xcb9 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"Thread‐1" #9 prio=5 os_prio=0 tid=0x00007f5c2c0e9000 nid=0xcc5 waiting for monitor entry [0x00007f5c1c7f6000]</span><br><span class="line">java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread2.run(TestDeadLock.java:47)</span><br><span class="line">‐waiting to lock <0x00000000f655dc40> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc50> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"Thread‐0" #8 prio=5 os_prio=0 tid=0x00007f5c2c0e7000 nid=0xcc4 waiting for monitor entry [0x00007f5c1c8f7000]</span><br><span class="line">java.lang.Thread.State: BLOCKED (on object monitor) at TestDeadLock$Thread1.run(TestDeadLock.java:27)</span><br><span class="line">‐waiting to lock <0x00000000f655dc50> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc40> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f5c2c0d3000 nid=0xcc2 runnable [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b6000 nid=0xcc1 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b3000 nid=0xcc0 waiting on condition [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b1800 nid=0xcbf runnable [0x0000000000000000]</span><br><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line"></span><br><span class="line">"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f5c2c07e800 nid=0xcbe in Object.wait() [0x00007f5c1cdfc000]</span><br><span class="line">java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)</span><br><span class="line">‐ waiting on <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class="line">at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)</span><br><span class="line">‐ locked <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)</span><br><span class="line">at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)</span><br><span class="line"></span><br><span class="line">"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f5c2c07a000 nid=0xcbd in Object.wait() [0x00007f5c1cefd000]</span><br><span class="line">java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)</span><br><span class="line">‐waiting on <0x00000000f6506b68> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502)</span><br><span class="line">at java.lang.ref.Reference.tryHandlePending(Reference.java:191)</span><br><span class="line">‐locked <0x00000000f6506b68> (a java.lang.ref.Reference$Lock)</span><br><span class="line">at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=0 tid=0x00007f5c2c072800 nid=0xcbc runnable</span><br><span class="line">"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01d800 nid=0xcba runnable</span><br><span class="line"></span><br><span class="line">"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01f800 nid=0xcbb runnable</span><br><span class="line"></span><br><span class="line">"VM Periodic Task Thread" os_prio=0 tid=0x00007f5c2c0d6800 nid=0xcc3 waiting on condition</span><br><span class="line"></span><br><span class="line">JNI global references: 6</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Found one Java‐level deadlock:</span><br><span class="line"></span><br><span class="line">=============================</span><br><span class="line">"Thread‐1":</span><br><span class="line">waiting to lock monitor 0x00007f5c080062c8 (object 0x00000000f655dc40, a java.lang.Object),</span><br><span class="line">which is held by "Thread‐0" "Thread‐0":</span><br><span class="line">waiting to lock monitor 0x00007f5c08004e28 (object 0x00000000f655dc50, a java.lang.Object),</span><br><span class="line">which is held by "Thread‐1"</span><br><span class="line"></span><br><span class="line">Java stack information for the threads listed above:</span><br><span class="line">===================================================</span><br><span class="line">"Thread‐1":</span><br><span class="line">at TestDeadLock$Thread2.run(TestDeadLock.java:47)</span><br><span class="line">‐waiting to lock <0x00000000f655dc40> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc50> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br><span class="line">"Thread‐0":</span><br><span class="line">at TestDeadLock$Thread1.run(TestDeadLock.java:27)</span><br><span class="line">‐waiting to lock <0x00000000f655dc50> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc40> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">Found 1 deadlock.</span><br></pre></td></tr></table></figure></div><p> 在输出的信息中,已经看到,发现了1个死锁,关键看这个: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">"Thread‐1":</span><br><span class="line">at TestDeadLock$Thread2.run(TestDeadLock.java:47)</span><br><span class="line">‐waiting to lock <0x00000000f655dc40> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc50> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br><span class="line">"Thread‐0":</span><br><span class="line">at TestDeadLock$Thread1.run(TestDeadLock.java:27)</span><br><span class="line">‐waiting to lock <0x00000000f655dc50> (a java.lang.Object)</span><br><span class="line">‐locked <0x00000000f655dc40> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)</span><br></pre></td></tr></table></figure></div><p>可以清晰的看到:</p><ul><li>Thread2获取了 <0x00000000f655dc50> 的锁,等待获取 <0x00000000f655dc40><br>这个锁</0x00000000f655dc40></0x00000000f655dc50></li><li>Thread1获取了 <0x00000000f655dc40> 的锁,等待获取 <0x00000000f655dc50><br>这个锁</0x00000000f655dc50></0x00000000f655dc40></li><li>由此可见,发生了死锁</li></ul><h1 id="7、VisualVM工具的使用"><a href="#7、VisualVM工具的使用" class="headerlink" title="7、VisualVM工具的使用"></a>7、VisualVM工具的使用</h1><p>VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。</p><p>VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。</p><ul><li>内存信息线程信息</li><li>Dump堆(本地进程)</li><li>Dump线程(本地进程)</li><li>打开堆Dump。堆Dump可以用jmap来生成。打开线程Dump</li><li>生成应用快照(包含内存信息、线程信息等等)</li><li>性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)</li><li>……</li></ul><p>7.1、启动<br>在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219223821746.png" alt="image-20191219223821746"></p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219224901088.png" alt="image-20191219224901088"></p><h2 id="7-2、查看本地进程"><a href="#7-2、查看本地进程" class="headerlink" title="7.2、查看本地进程"></a>7.2、查看本地进程</h2><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219225126244.png" alt="image-20191219225126244"></p><h2 id="7-3、查看CPU、内存、类、线程运行信息"><a href="#7-3、查看CPU、内存、类、线程运行信息" class="headerlink" title="7.3、查看CPU、内存、类、线程运行信息"></a>7.3、查看CPU、内存、类、线程运行信息</h2><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219225226502.png" alt="image-20191219225226502"></p><h2 id="7-4、查看线程详情"><a href="#7-4、查看线程详情" class="headerlink" title="7.4、查看线程详情"></a>7.4、查看线程详情</h2><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219225513677.png" alt="image-20191219225513677"></p><p> 也可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令。 </p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219225624456.png" alt="image-20191219225624456"></p><p>发现,显示的内容是一样的。</p><h2 id="7-5、抽样器"><a href="#7-5、抽样器" class="headerlink" title="7.5、抽样器"></a>7.5、抽样器</h2><p>抽样器可以对CPU、内存在一段时间内进行抽样,以供分析。</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219225744090.png" alt="image-20191219225744090"></p><h2 id="7-6、监控远程的jvm"><a href="#7-6、监控远程的jvm" class="headerlink" title="7.6、监控远程的jvm"></a>7.6、监控远程的jvm</h2><p>VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。</p><h3 id="7-6-1、什么是JMX?"><a href="#7-6-1、什么是JMX?" class="headerlink" title="7.6.1、什么是JMX?"></a>7.6.1、什么是JMX?</h3><p>JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。</p><h3 id="7-6-2、监控远程的tomcat"><a href="#7-6-2、监控远程的tomcat" class="headerlink" title="7.6.2、监控远程的tomcat"></a>7.6.2、监控远程的tomcat</h3><p>想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#在tomcat的bin目录下,修改catalina.sh,添加如下的参数</span><br><span class="line">JAVA_OPTS="‐Dcom.sun.management.jmxremote ‐</span><br><span class="line">Dcom.sun.management.jmxremote.port=9999 ‐ Dcom.sun.management.jmxremote.authenticate=false ‐ Dcom.sun.management.jmxremote.ssl=false"</span><br><span class="line">#这几个参数的意思是:</span><br><span class="line">#‐Dcom.sun.management.jmxremote :允许使用JMX远程管理#‐Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口#‐Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可以连接</span><br><span class="line">#‐Dcom.sun.management.jmxremote.ssl=false :不使用ssl</span><br></pre></td></tr></table></figure></div><p> 保存退出,重启tomcat。</p><h3 id="7-6-3、使用VisualJVM连接远程tomcat"><a href="#7-6-3、使用VisualJVM连接远程tomcat" class="headerlink" title="7.6.3、使用VisualJVM连接远程tomcat"></a><strong>7.6.3、使用VisualJVM连接远程tomcat</strong></h3><p>添加远程主机:</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219230428372.png" alt="image-20191219230428372"></p><p> 在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的 jvm:</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219230618659.png" alt="image-20191219230618659"></p><p>图片中的9999 是前面在远程tomcat中JMX配置的远程连接端口 。</p><p><img src="/2019/12/19/架构与优化之JVM优化第01篇JVM基础/image-20191219230825758.png" alt="image-20191219230825758"></p><p>连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的 tomcat进程。</p>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li><p>了解下我们为什么要学习JVM优化 </p>
</li>
<li><p>掌握jvm的运行参数
</summary>
<category term="JVM" scheme="http://enfangzhong.github.io/categories/JVM/"/>
<category term="JVM" scheme="http://enfangzhong.github.io/tags/JVM/"/>
</entry>
<entry>
<title>消息中间件之rabbitmq及数据同步</title>
<link href="http://enfangzhong.github.io/2019/12/18/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6%E4%B9%8BRabbitMQ%E5%8F%8A%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5/"/>
<id>http://enfangzhong.github.io/2019/12/18/消息中间件之RabbitMQ及数据同步/</id>
<published>2019-12-18T02:16:00.000Z</published>
<updated>2019-12-18T06:25:24.336Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>了解常见的MQ产品</li><li>了解RabbitMQ的5种消息模型</li><li>会使用Spring AMQP</li><li>利用MQ实现搜索和静态页的数据同步</li></ul><h1 id="1-RabbitMQ"><a href="#1-RabbitMQ" class="headerlink" title="1.RabbitMQ"></a>1.RabbitMQ</h1><h2 id="1-1-搜索与商品服务的问题"><a href="#1-1-搜索与商品服务的问题" class="headerlink" title="1.1.搜索与商品服务的问题"></a>1.1.搜索与商品服务的问题</h2><p>目前我们已经完成了商品详情和搜索系统的开发。我们思考一下,是否存在问题?</p><ul><li>商品的原始数据保存在数据库中,增删改查都在数据库中完成。</li><li>搜索服务数据来源是索引库,如果数据库商品发生变化,索引库数据不能及时更新。</li><li>商品详情做了页面静态化,静态页面数据也不会随着数据库商品发生变化。</li></ul><p>如果我们在后台修改了商品的价格,搜索页面和商品详情页显示的依然是旧的价格,这样显然不对。该如何解决?</p><p>这里有两种解决方案:</p><ul><li>方案1:每当后台对商品做增删改操作,同时要修改索引库数据及静态页面</li><li>方案2:搜索服务和商品页面服务对外提供操作接口,后台在商品增删改后,调用接口</li></ul><p>以上两种方式都有同一个严重问题:就是代码耦合,后台服务中需要嵌入搜索和商品页面服务,违背了微服务的<code>独立</code>原则。</p><p>所以,我们会通过另外一种方式来解决这个问题:消息队列</p><h2 id="1-2-消息队列(MQ)"><a href="#1-2-消息队列(MQ)" class="headerlink" title="1.2.消息队列(MQ)"></a>1.2.消息队列(MQ)</h2><h3 id="1-2-1-什么是消息队列"><a href="#1-2-1-什么是消息队列" class="headerlink" title="1.2.1.什么是消息队列"></a>1.2.1.什么是消息队列</h3><p>消息队列,即MQ,Message Queue。</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527063872737.png" alt="1527063872737"></p><p>消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。</p><p>结合前面所说的问题:</p><ul><li>商品服务对商品增删改以后,无需去操作索引库或静态页面,只是发送一条消息,也不关心消息被谁接收。</li><li>搜索服务和静态页面服务接收消息,分别去处理索引库和静态页面。</li></ul><p>如果以后有其它系统也依赖商品服务的数据,同样监听消息即可,商品服务无需任何代码修改。</p><h3 id="1-2-2-AMQP和JMS"><a href="#1-2-2-AMQP和JMS" class="headerlink" title="1.2.2.AMQP和JMS"></a>1.2.2.AMQP和JMS</h3><p>MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527064480681.png" alt="1527064480681"></p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527064487042.png" alt="1527064487042"></p><p>两者间的区别和联系:</p><ul><li>JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式</li><li>JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。</li><li>JMS规定了两种消息模型;而AMQP的消息模型更加丰富</li></ul><h3 id="1-2-3-常见MQ产品"><a href="#1-2-3-常见MQ产品" class="headerlink" title="1.2.3.常见MQ产品"></a>1.2.3.常见MQ产品</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527064606029.png" alt="1527064606029"></p><ul><li>ActiveMQ:基于JMS</li><li>RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好</li><li>RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会</li><li>Kafka:分布式消息系统,高吞吐量</li></ul><h3 id="1-2-4-RabbitMQ"><a href="#1-2-4-RabbitMQ" class="headerlink" title="1.2.4.RabbitMQ"></a>1.2.4.RabbitMQ</h3><p>RabbitMQ是基于AMQP的一款消息管理系统</p><p>官网: <a href="http://www.rabbitmq.com/" target="_blank" rel="noopener">http://www.rabbitmq.com/</a></p><p>官方教程:<a href="http://www.rabbitmq.com/getstarted.html" target="_blank" rel="noopener">http://www.rabbitmq.com/getstarted.html</a></p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532758972119.png" alt="1532758972119"></p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527064762982.png" alt="1527064762982"></p><h2 id="1-3-下载和安装"><a href="#1-3-下载和安装" class="headerlink" title="1.3.下载和安装"></a>1.3.下载和安装</h2><h3 id="1-3-1-下载"><a href="#1-3-1-下载" class="headerlink" title="1.3.1.下载"></a>1.3.1.下载</h3><p>官网下载地址:<a href="http://www.rabbitmq.com/download.html" target="_blank" rel="noopener">http://www.rabbitmq.com/download.html</a></p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532759070767.png" alt="1532759070767"></p><p>目前最新版本是:3.7.5</p><p>我们的课程中使用的是:3.4.1版本</p><p>课前资料提供了安装包:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532761948333.png" alt="1532761948333"></p><h3 id="1-3-2-安装"><a href="#1-3-2-安装" class="headerlink" title="1.3.2.安装"></a>1.3.2.安装</h3><p>详见课前资料中的:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532761916357.png" alt="1532761916357"></p><h1 id="2-五种消息模型"><a href="#2-五种消息模型" class="headerlink" title="2.五种消息模型"></a>2.五种消息模型</h1><p>RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种。</p><p>但是其实3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527068544487.png" alt="1527068544487"></p><p>我们通过一个demo工程来了解下RabbitMQ的工作方式:</p><p>导入工程:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532762038694.png" alt="1532762038694"></p><p>导入后:</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532762308507.png" alt="1532762308507"></p><p>依赖:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"><span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>cn.itcast.rabbitmq<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>itcast-rabbitmq<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>2.0.2.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"><span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"><span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.apache.commons<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>commons-lang3<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>3.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><p>我们抽取一个建立RabbitMQ连接的工具类,方便其他程序获取连接:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConnectionUtil</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 建立与RabbitMQ的连接</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Connection <span class="title">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//定义连接工厂</span></span><br><span class="line"> ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line"> <span class="comment">//设置服务地址</span></span><br><span class="line"> factory.setHost(<span class="string">"192.168.56.101"</span>);</span><br><span class="line"> <span class="comment">//端口</span></span><br><span class="line"> factory.setPort(<span class="number">5672</span>);</span><br><span class="line"> <span class="comment">//设置账号信息,用户名、密码、vhost</span></span><br><span class="line"> factory.setVirtualHost(<span class="string">"/leyou"</span>);</span><br><span class="line"> factory.setUsername(<span class="string">"leyou"</span>);</span><br><span class="line"> factory.setPassword(<span class="string">"leyou"</span>);</span><br><span class="line"> <span class="comment">// 通过工程获取连接</span></span><br><span class="line"> Connection connection = factory.newConnection();</span><br><span class="line"> <span class="keyword">return</span> connection;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="2-1-基本消息模型"><a href="#2-1-基本消息模型" class="headerlink" title="2.1.基本消息模型"></a>2.1.基本消息模型</h2><p>官方介绍:</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532762961149.png" alt="1532762961149"></p><p>RabbitMQ是一个消息代理:它接受和转发消息。 你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。</p><p>RabbitMQ与邮局的主要区别是它不处理纸张,而是接受,存储和转发数据消息的二进制数据块。</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532762975546.png" alt="1532762975546"></p><p>P(producer/ publisher):生产者,一个发送消息的用户应用程序。</p><p>C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序</p><p>队列(红色区域):rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。</p><p>总之:</p><p>生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。</p><p>我们将用Java编写两个程序;发送单个消息的生产者,以及接收消息并将其打印出来的消费者。我们将详细介绍Java API中的一些细节,这是一个消息传递的“Hello World”。</p><p>我们将调用我们的消息发布者(发送者)Send和我们的消息消费者(接收者)Recv。发布者将连接到RabbitMQ,发送一条消息,然后退出。</p><h3 id="2-1-1-生产者发送消息"><a href="#2-1-1-生产者发送消息" class="headerlink" title="2.1.1.生产者发送消息"></a>2.1.1.生产者发送消息</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Send</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"simple_queue"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接以及mq通道</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 从连接中创建通道,这是完成大部分API的地方。</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 声明(创建)队列,必须声明队列才能够发送消息,我们可以把消息发送到队列中。</span></span><br><span class="line"> <span class="comment">// 声明一个队列是幂等的 - 只有当它不存在时才会被创建</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 消息内容</span></span><br><span class="line"> String message = <span class="string">"Hello World!"</span>;</span><br><span class="line"> channel.basicPublish(<span class="string">""</span>, QUEUE_NAME, <span class="keyword">null</span>, message.getBytes());</span><br><span class="line"> System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//关闭通道和连接</span></span><br><span class="line"> channel.close();</span><br><span class="line"> connection.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>控制台:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532763328424.png" alt="1532763328424"></p><h3 id="2-1-2-管理工具中查看消息"><a href="#2-1-2-管理工具中查看消息" class="headerlink" title="2.1.2.管理工具中查看消息"></a>2.1.2.管理工具中查看消息</h3><p>进入队列页面,可以看到新建了一个队列:simple_queue</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532763817830.png" alt="1532763817830"></p><p>点击队列名称,进入详情页,可以查看消息:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532763489858.png" alt="1532763489858"></p><p>在控制台查看消息并不会将消息消费,所以消息还在。</p><h3 id="2-1-3-消费者获取消息"><a href="#2-1-3-消费者获取消息" class="headerlink" title="2.1.3.消费者获取消息"></a>2.1.3.消费者获取消息</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"simple_queue"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 创建通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [x] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,第二个参数:是否自动进行消息确认。</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>控制台:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532763733443.png" alt="1532763733443"></p><p>这个时候,队列中的消息就没了:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532763773208.png" alt="1532763773208"></p><p>我们发现,消费者已经获取了消息,但是程序没有停止,一直在监听队列中是否有新的消息。一旦有新的消息进入队列,就会立即打印.</p><h3 id="2-1-4-消息确认机制(ACK)"><a href="#2-1-4-消息确认机制(ACK)" class="headerlink" title="2.1.4.消息确认机制(ACK)"></a>2.1.4.消息确认机制(ACK)</h3><p>通过刚才的案例可以看出,消息一旦被消费者接收,队列中的消息就会被删除。</p><p>那么问题来了:RabbitMQ怎么知道消息被接收了呢?</p><p>如果消费者领取消息后,还没执行操作就挂掉了呢?或者抛出了异常?消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了!</p><p>因此,RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:</p><ul><li>自动ACK:消息一旦被接收,消费者自动发送ACK</li><li>手动ACK:消息接收后,不会发送ACK,需要手动调用</li></ul><p>大家觉得哪种更好呢?</p><p>这需要看消息的重要性:</p><ul><li>如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便</li><li>如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。</li></ul><p>我们之前的测试都是自动ACK的,如果要手动ACK,需要改动我们的代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv2</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"simple_queue"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 创建通道</span></span><br><span class="line"> <span class="keyword">final</span> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [x] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> <span class="comment">// 手动进行ACK</span></span><br><span class="line"> channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,第二个参数false,手动进行ACK</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>注意到最后一行代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, consumer);</span><br></pre></td></tr></table></figure></div><p>如果第二个参数为true,则会自动进行ACK;如果为false,则需要手动ACK。方法的声明:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764253019.png" alt="1532764253019"></p><h4 id="2-1-4-1-自动ACK存在的问题"><a href="#2-1-4-1-自动ACK存在的问题" class="headerlink" title="2.1.4.1.自动ACK存在的问题"></a>2.1.4.1.自动ACK存在的问题</h4><p>修改消费者,添加异常,如下:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764600849.png" alt="1532764600849"></p><p>生产者不做任何修改,直接运行,消息发送成功:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764694290.png" alt="1532764694290"></p><p>运行消费者,程序抛出异常。但是消息依然被消费:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764717995.png" alt="1532764717995"></p><p>管理界面:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764734232.png" alt="1532764734232"></p><h4 id="2-1-4-2-演示手动ACK"><a href="#2-1-4-2-演示手动ACK" class="headerlink" title="2.1.4.2.演示手动ACK"></a>2.1.4.2.演示手动ACK</h4><p>修改消费者,把自动改成手动(去掉之前制造的异常)</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764831241.png" alt="1532764831241"></p><p>生产者不变,再次运行:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764895239.png" alt="1532764895239"></p><p>运行消费者</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532764957092.png" alt="1532764957092"></p><p>但是,查看管理界面,发现:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765013834.png" alt="1532765013834"></p><p>停掉消费者的程序,发现:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765038088.png" alt="1532765038088"></p><p>这是因为虽然我们设置了手动ACK,但是代码中并没有进行消息确认!所以消息并未被真正消费掉。</p><p>当我们关掉这个消费者,消息的状态再次称为Ready</p><p>修改代码手动ACK:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765123282.png" alt="1532765123282"></p><p>执行:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765151039.png" alt="1532765151039"></p><p>消息消费成功!</p><h2 id="2-2-work消息模型"><a href="#2-2-work消息模型" class="headerlink" title="2.2.work消息模型"></a>2.2.work消息模型</h2><p>工作队列或者竞争消费者模式</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765197277.png" alt="1532765197277"></p><p>在第一篇教程中,我们编写了一个程序,从一个命名队列中发送并接受消息。在这里,我们将创建一个工作队列,在多个工作者之间分配耗时任务。</p><p>工作队列,又称任务队列。主要思想就是避免执行资源密集型任务时,必须等待它执行完成。相反我们稍后完成任务,我们将任务封装为消息并将其发送到队列。 在后台运行的工作进程将获取任务并最终执行作业。当你运行许多消费者时,任务将在他们之间共享,但是<strong>一个消息只能被一个消费者获取</strong>。</p><p>这个概念在Web应用程序中特别有用,因为在短的HTTP请求窗口中无法处理复杂的任务。</p><p>接下来我们来模拟这个流程:</p><pre><code>P:生产者:任务的发布者C1:消费者,领取任务并且完成任务,假设完成速度较快C2:消费者2:领取任务并完成任务,假设完成速度慢</code></pre><p>面试题:避免消息堆积?</p><p>1)采用workqueue,多个消费者监听同一队列。</p><p>2)接收到消息以后,而是通过线程池,异步消费。</p><h3 id="2-2-1-生产者"><a href="#2-2-1-生产者" class="headerlink" title="2.2.1.生产者"></a>2.2.1.生产者</h3><p>生产者与案例1中的几乎一样:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Send</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"test_work_queue"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 循环发布任务</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">50</span>; i++) {</span><br><span class="line"> <span class="comment">// 消息内容</span></span><br><span class="line"> String message = <span class="string">"task .. "</span> + i;</span><br><span class="line"> channel.basicPublish(<span class="string">""</span>, QUEUE_NAME, <span class="keyword">null</span>, message.getBytes());</span><br><span class="line"> System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line"></span><br><span class="line"> Thread.sleep(i * <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 关闭通道和连接</span></span><br><span class="line"> channel.close();</span><br><span class="line"> connection.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>不过这里我们是循环发送50条消息。</p><h3 id="2-2-2-消费者1"><a href="#2-2-2-消费者1" class="headerlink" title="2.2.2.消费者1"></a>2.2.2.消费者1</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527085386747.png" alt="1527085386747"></p><h3 id="2-2-3-消费者2"><a href="#2-2-3-消费者2" class="headerlink" title="2.2.3.消费者2"></a>2.2.3.消费者2</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527085448377.png" alt="1527085448377"></p><p>与消费者1基本类似,就是没有设置消费耗时时间。</p><p>这里是模拟有些消费者快,有些比较慢。</p><p>接下来,两个消费者一同启动,然后发送50条消息:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527085826462.png" alt="1527085826462"></p><p>可以发现,两个消费者各自消费了25条消息,而且各不相同,这就实现了任务的分发。</p><h3 id="2-2-4-能者多劳"><a href="#2-2-4-能者多劳" class="headerlink" title="2.2.4.能者多劳"></a>2.2.4.能者多劳</h3><p>刚才的实现有问题吗?</p><ul><li>消费者1比消费者2的效率要低,一次任务的耗时较长</li><li>然而两人最终消费的消息数量是一样的</li><li>消费者2大量时间处于空闲状态,消费者1一直忙碌</li></ul><p>现在的状态属于是把任务平均分配,正确的做法应该是消费越快的人,消费的越多。</p><p>怎么实现呢?</p><p>我们可以使用basicQos方法和prefetchCount = 1设置。 这告诉RabbitMQ一次不要向工作人员发送多于一条消息。 或者换句话说,不要向工作人员发送新消息,直到它处理并确认了前一个消息。 相反,它会将其分派给不是仍然忙碌的下一个工作人员。</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532765689904.png" alt="1532765689904"></p><p>再次测试:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527086159534.png" alt="1527086159534"></p><h2 id="2-3-订阅模型分类"><a href="#2-3-订阅模型分类" class="headerlink" title="2.3.订阅模型分类"></a>2.3.订阅模型分类</h2><p>在之前的模式中,我们创建了一个工作队列。 工作队列背后的假设是:每个任务只被传递给一个工作人员。 在这一部分,我们将做一些完全不同的事情 - 我们将会传递一个信息给多个消费者。 这种模式被称为“发布/订阅”。 </p><p>订阅模型示意图:</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527086284940.png" alt="1527086284940"></p><p>解读:</p><p>1、1个生产者,多个消费者</p><p>2、每一个消费者都有自己的一个队列</p><p>3、生产者没有将消息直接发送到队列,而是发送到了交换机</p><p>4、每个队列都要绑定到交换机</p><p>5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的</p><p>X(Exchanges):交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。</p><p>Exchange类型有以下几种:</p><pre><code>Fanout:广播,将消息交给所有绑定到交换机的队列Direct:定向,把消息交给符合指定routing key 的队列 Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列</code></pre><p>我们这里先学习</p><pre><code>Fanout:即广播模式</code></pre><p><strong>Exchange(交换机)只负责转发消息,不具备存储消息的能力</strong>,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!</p><h2 id="2-4-订阅模型-Fanout"><a href="#2-4-订阅模型-Fanout" class="headerlink" title="2.4.订阅模型-Fanout"></a>2.4.订阅模型-Fanout</h2><p>Fanout,也称为广播。</p><p>流程图:</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527086564505.png" alt="1527086564505"></p><p>在广播模式下,消息发送流程是这样的:</p><ul><li>1) 可以有多个消费者</li><li>2) 每个<strong>消费者有自己的queue</strong>(队列)</li><li>3) 每个<strong>队列都要绑定到Exchange</strong>(交换机)</li><li>4) <strong>生产者发送的消息,只能发送到交换机</strong>,交换机来决定要发给哪个队列,生产者无法决定。</li><li>5) 交换机把消息发送给绑定过的所有队列</li><li>6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消费</li></ul><h3 id="2-4-1-生产者"><a href="#2-4-1-生产者" class="headerlink" title="2.4.1.生产者"></a>2.4.1.生产者</h3><p>两个变化:</p><ul><li>1) 声明Exchange,不再声明Queue</li><li>2) 发送消息到Exchange,不再发送到Queue</li></ul><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Send</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"fanout_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 声明exchange,指定类型为fanout</span></span><br><span class="line"> channel.exchangeDeclare(EXCHANGE_NAME, <span class="string">"fanout"</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 消息内容</span></span><br><span class="line"> String message = <span class="string">"Hello everyone"</span>;</span><br><span class="line"> <span class="comment">// 发布消息到Exchange</span></span><br><span class="line"> channel.basicPublish(EXCHANGE_NAME, <span class="string">""</span>, <span class="keyword">null</span>, message.getBytes());</span><br><span class="line"> System.out.println(<span class="string">" [生产者] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line"></span><br><span class="line"> channel.close();</span><br><span class="line"> connection.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-4-2-消费者1"><a href="#2-4-2-消费者1" class="headerlink" title="2.4.2.消费者1"></a>2.4.2.消费者1</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"fanout_exchange_queue_1"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"fanout_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 绑定队列到交换机</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">""</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者1] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,自动返回完成</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>要注意代码中:<strong>队列需要和交换机绑定</strong></p><h3 id="2-4-3-消费者2"><a href="#2-4-3-消费者2" class="headerlink" title="2.4.3.消费者2"></a>2.4.3.消费者2</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv2</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"fanout_exchange_queue_2"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"fanout_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 绑定队列到交换机</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">""</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者2] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,手动返回完成</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-4-4-测试"><a href="#2-4-4-测试" class="headerlink" title="2.4.4.测试"></a>2.4.4.测试</h3><p>我们运行两个消费者,然后发送1条消息:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766264386.png" alt="1532766264386"></p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766291204.png" alt="1532766291204"></p><h2 id="2-5-订阅模型-Direct"><a href="#2-5-订阅模型-Direct" class="headerlink" title="2.5.订阅模型-Direct"></a>2.5.订阅模型-Direct</h2><p>有选择性的接收消息</p><p>在订阅模式中,生产者发布消息,所有消费者都可以获取所有消息。</p><p>在路由模式中,我们将添加一个功能 - 我们将只能订阅一部分消息。 例如,我们只能将重要的错误消息引导到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有日志消息。</p><p>但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。</p><p>在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)</p><p>消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766437787.png" alt="1532766437787"></p><p>P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。</p><p>X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列</p><p>C1:消费者,其所在队列指定了需要routing key 为 error 的消息</p><p>C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息</p><h3 id="2-5-1-生产者"><a href="#2-5-1-生产者" class="headerlink" title="2.5.1.生产者"></a>2.5.1.生产者</h3><p>此处我们模拟商品的增删改,发送消息的RoutingKey分别是:insert、update、delete</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Send</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"direct_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明exchange,指定类型为direct</span></span><br><span class="line"> channel.exchangeDeclare(EXCHANGE_NAME, <span class="string">"direct"</span>);</span><br><span class="line"> <span class="comment">// 消息内容</span></span><br><span class="line"> String message = <span class="string">"商品新增了, id = 1001"</span>;</span><br><span class="line"> <span class="comment">// 发送消息,并且指定routing key 为:insert ,代表新增商品</span></span><br><span class="line"> channel.basicPublish(EXCHANGE_NAME, <span class="string">"insert"</span>, <span class="keyword">null</span>, message.getBytes());</span><br><span class="line"> System.out.println(<span class="string">" [商品服务:] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line"></span><br><span class="line"> channel.close();</span><br><span class="line"> connection.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-5-2-消费者1"><a href="#2-5-2-消费者1" class="headerlink" title="2.5.2.消费者1"></a>2.5.2.消费者1</h3><p>我们此处假设消费者1只接收两种类型的消息:更新商品和删除商品。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"direct_exchange_queue_1"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"direct_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 绑定队列到交换机,同时指定需要订阅的routing key。假设此处需要update和delete消息</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"update"</span>);</span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"delete"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者1] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,自动ACK</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-5-3-消费者2"><a href="#2-5-3-消费者2" class="headerlink" title="2.5.3.消费者2"></a>2.5.3.消费者2</h3><p>我们此处假设消费者2接收所有类型的消息:新增商品,更新商品和删除商品。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv2</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"direct_exchange_queue_2"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"direct_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 绑定队列到交换机,同时指定需要订阅的routing key。订阅 insert、update、delete</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"insert"</span>);</span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"update"</span>);</span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"delete"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者2] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,自动ACK</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-5-4-测试"><a href="#2-5-4-测试" class="headerlink" title="2.5.4.测试"></a>2.5.4.测试</h3><p>我们分别发送增、删、改的RoutingKey,发现结果:</p><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527088296131.png" alt="1527088296131"></p><h2 id="2-6-订阅模型-Topic"><a href="#2-6-订阅模型-Topic" class="headerlink" title="2.6.订阅模型-Topic"></a>2.6.订阅模型-Topic</h2><p><code>Topic</code>类型的<code>Exchange</code>与<code>Direct</code>相比,都是可以根据<code>RoutingKey</code>把消息路由到不同的队列。只不过<code>Topic</code>类型<code>Exchange</code>可以让队列在绑定<code>Routing key</code> 的时候使用通配符!</p><p><code>Routingkey</code> 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: <code>item.insert</code></p><p> 通配符规则:</p><pre><code>`#`:匹配一个或多个词`*`:匹配不多不少恰好1个词</code></pre><p>举例:</p><pre><code>`audit.#`:能够匹配`audit.irs.corporate` 或者 `audit.irs``audit.*`:只能匹配`audit.irs`</code></pre><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766712166.png" alt="1532766712166"></p><p>在这个例子中,我们将发送所有描述动物的消息。消息将使用由三个字(两个点)组成的routing key发送。路由关键字中的第一个单词将描述速度,第二个颜色和第三个种类:“<speed>.<color>.<species>”。</species></color></speed></p><p>我们创建了三个绑定:Q1绑定了绑定键“<em> .orange.</em>”,Q2绑定了“<em>.</em>.rabbit”和“lazy.#”。</p><p>Q1匹配所有的橙色动物。</p><p>Q2匹配关于兔子以及懒惰动物的消息。</p><p>练习,生产者发送如下消息,会进入那个队列:</p><p>quick.orange.rabbit Q1 Q2</p><p>lazy.orange.elephant </p><p>quick.orange.fox </p><p>lazy.pink.rabbit </p><p>quick.brown.fox </p><p>quick.orange.male.rabbit </p><p>orange </p><h3 id="2-6-1-生产者"><a href="#2-6-1-生产者" class="headerlink" title="2.6.1.生产者"></a>2.6.1.生产者</h3><p>使用topic类型的Exchange,发送消息的routing key有3种: <code>item.isnert</code>、<code>item.update</code>、<code>item.delete</code>:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Send</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"topic_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明exchange,指定类型为topic</span></span><br><span class="line"> channel.exchangeDeclare(EXCHANGE_NAME, <span class="string">"topic"</span>);</span><br><span class="line"> <span class="comment">// 消息内容</span></span><br><span class="line"> String message = <span class="string">"新增商品 : id = 1001"</span>;</span><br><span class="line"> <span class="comment">// 发送消息,并且指定routing key 为:insert ,代表新增商品</span></span><br><span class="line"> channel.basicPublish(EXCHANGE_NAME, <span class="string">"item.insert"</span>, <span class="keyword">null</span>, message.getBytes());</span><br><span class="line"> System.out.println(<span class="string">" [商品服务:] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line"></span><br><span class="line"> channel.close();</span><br><span class="line"> connection.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-6-2-消费者1"><a href="#2-6-2-消费者1" class="headerlink" title="2.6.2.消费者1"></a>2.6.2.消费者1</h3><p>我们此处假设消费者1只接收两种类型的消息:更新商品和删除商品</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"topic_exchange_queue_1"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"topic_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 绑定队列到交换机,同时指定需要订阅的routing key。需要 update、delete</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"item.update"</span>);</span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"item.delete"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者1] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,自动ACK</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-6-3-消费者2"><a href="#2-6-3-消费者2" class="headerlink" title="2.6.3.消费者2"></a>2.6.3.消费者2</h3><p>我们此处假设消费者2接收所有类型的消息:新增商品,更新商品和删除商品。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 消费者2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Recv2</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String QUEUE_NAME = <span class="string">"topic_exchange_queue_2"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXCHANGE_NAME = <span class="string">"topic_exchange_test"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取到连接</span></span><br><span class="line"> Connection connection = ConnectionUtil.getConnection();</span><br><span class="line"> <span class="comment">// 获取通道</span></span><br><span class="line"> Channel channel = connection.createChannel();</span><br><span class="line"> <span class="comment">// 声明队列</span></span><br><span class="line"> channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 绑定队列到交换机,同时指定需要订阅的routing key。订阅 insert、update、delete</span></span><br><span class="line"> channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"item.*"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 定义队列的消费者</span></span><br><span class="line"> DefaultConsumer consumer = <span class="keyword">new</span> DefaultConsumer(channel) {</span><br><span class="line"> <span class="comment">// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope, BasicProperties properties,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="comment">// body 即消息体</span></span><br><span class="line"> String msg = <span class="keyword">new</span> String(body);</span><br><span class="line"> System.out.println(<span class="string">" [消费者2] received : "</span> + msg + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 监听队列,自动ACK</span></span><br><span class="line"> channel.basicConsume(QUEUE_NAME, <span class="keyword">true</span>, consumer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="2-7-持久化"><a href="#2-7-持久化" class="headerlink" title="2.7.持久化"></a>2.7.持久化</h2><p>如何避免消息丢失?</p><p>1) 消费者的ACK机制。可以防止消费者丢失消息。</p><p>2) 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。</p><p>是可以将消息进行持久化呢?</p><p>要将消息持久化,前提是:队列、Exchange都持久化</p><h3 id="2-7-1-交换机持久化"><a href="#2-7-1-交换机持久化" class="headerlink" title="2.7.1.交换机持久化"></a>2.7.1.交换机持久化</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766951432.png" alt="1532766951432"></p><h3 id="2-7-2-队列持久化"><a href="#2-7-2-队列持久化" class="headerlink" title="2.7.2.队列持久化"></a>2.7.2.队列持久化</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532766981230.png" alt="1532766981230"></p><h3 id="2-7-3-消息持久化"><a href="#2-7-3-消息持久化" class="headerlink" title="2.7.3.消息持久化"></a>2.7.3.消息持久化</h3><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532767057491.png" alt="1532767057491"></p><h1 id="3-Spring-AMQP"><a href="#3-Spring-AMQP" class="headerlink" title="3.Spring AMQP"></a>3.Spring AMQP</h1><h2 id="3-1-简介"><a href="#3-1-简介" class="headerlink" title="3.1.简介"></a>3.1.简介</h2><p>Sprin有很多不同的项目,其中就有对AMQP的支持:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532767136007.png" alt="1532767136007"></p><p>Spring AMQP的页面:<a href="http://spring.io/projects/spring-amqp" target="_blank" rel="noopener">http://spring.io/projects/spring-amqp</a></p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532767171063.png" alt="1532767171063"></p><p>注意这里一段描述:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532767227821.png" alt="1532767227821"> </p><pre><code> Spring-amqp是对AMQP协议的抽象实现,而spring-rabbit 是对协议的具体实现,也是目前的唯一实现。底层使用的就是RabbitMQ。</code></pre><h2 id="3-2-依赖和配置"><a href="#3-2-依赖和配置" class="headerlink" title="3.2.依赖和配置"></a>3.2.依赖和配置</h2><p>添加AMQP的启动器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>在<code>application.yml</code>中添加RabbitMQ地址:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> rabbitmq:</span></span><br><span class="line"><span class="attr"> host:</span> <span class="number">192.168</span><span class="number">.56</span><span class="number">.101</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> virtual-host:</span> <span class="string">/leyou</span></span><br></pre></td></tr></table></figure></div><h2 id="3-3-监听者"><a href="#3-3-监听者" class="headerlink" title="3.3.监听者"></a>3.3.监听者</h2><p>在SpringAmqp中,对消息的消费者进行了封装和抽象,一个普通的JavaBean中的普通方法,只要通过简单的注解,就可以成为一个消费者。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Listener</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line"> value = <span class="meta">@Queue</span>(value = <span class="string">"spring.test.queue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line"> exchange = <span class="meta">@Exchange</span>(</span><br><span class="line"> value = <span class="string">"spring.test.exchange"</span>,</span><br><span class="line"> ignoreDeclarationExceptions = <span class="string">"true"</span>,</span><br><span class="line"> type = ExchangeTypes.TOPIC</span><br><span class="line"> ),</span><br><span class="line"> key = {<span class="string">"#.#"</span>}))</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">(String msg)</span></span>{</span><br><span class="line"> System.out.println(<span class="string">"接收到消息:"</span> + msg);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li><code>@Componet</code>:类上的注解,注册到Spring容器</li><li><code>@RabbitListener</code>:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:<ul><li><code>bindings</code>:指定绑定关系,可以有多个。值是<code>@QueueBinding</code>的数组。<code>@QueueBinding</code>包含下面属性:<ul><li><code>value</code>:这个消费者关联的队列。值是<code>@Queue</code>,代表一个队列</li><li><code>exchange</code>:队列所绑定的交换机,值是<code>@Exchange</code>类型</li><li><code>key</code>:队列和交换机绑定的<code>RoutingKey</code></li></ul></li></ul></li></ul><p>类似listen这样的方法在一个类中可以写多个,就代表多个消费者。</p><h2 id="3-4-AmqpTemplate"><a href="#3-4-AmqpTemplate" class="headerlink" title="3.4.AmqpTemplate"></a>3.4.AmqpTemplate</h2><p>Spring最擅长的事情就是封装,把他人的框架进行封装和整合。</p><p>Spring为AMQP提供了统一的消息处理模板:AmqpTemplate,非常方便的发送消息,其发送方法:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1527090258083.png" alt="1527090258083"></p><p>红框圈起来的是比较常用的3个方法,分别是:</p><ul><li>指定交换机、RoutingKey和消息体</li><li>指定消息</li><li>指定RoutingKey和消息,会向默认的交换机发送消息</li></ul><h2 id="3-5-测试代码"><a href="#3-5-测试代码" class="headerlink" title="3.5.测试代码"></a>3.5.测试代码</h2><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner.class)</span><br><span class="line"><span class="meta">@SpringBootTest</span>(classes = Application.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MqDemo</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> AmqpTemplate amqpTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> String msg = <span class="string">"hello, Spring boot amqp"</span>;</span><br><span class="line"> <span class="keyword">this</span>.amqpTemplate.convertAndSend(<span class="string">"spring.test.exchange"</span>,<span class="string">"a.b"</span>, msg);</span><br><span class="line"> <span class="comment">// 等待10秒后再结束</span></span><br><span class="line"> Thread.sleep(<span class="number">10000</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>运行后查看日志:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532767726274.png" alt="1532767726274"></p><h1 id="3-项目改造"><a href="#3-项目改造" class="headerlink" title="3.项目改造"></a>3.项目改造</h1><p>接下来,我们就改造项目,实现搜索服务、商品静态页的数据同步。</p><h2 id="3-1-思路分析"><a href="#3-1-思路分析" class="headerlink" title="3.1.思路分析"></a>3.1.思路分析</h2><blockquote><p>发送方:商品微服务</p></blockquote><ul><li><p>什么时候发?</p><p>当商品服务对商品进行写操作:增、删、改的时候,需要发送一条消息,通知其它服务。</p></li><li><p>发送什么内容?</p><p>对商品的增删改时其它服务可能需要新的商品数据,但是如果消息内容中包含全部商品信息,数据量太大,而且并不是每个服务都需要全部的信息。因此我们<strong>只发送商品id</strong>,其它服务可以根据id查询自己需要的信息。</p></li></ul><blockquote><p>接收方:搜索微服务、静态页微服务</p></blockquote><p>接收消息后如何处理?</p><ul><li>搜索微服务:<ul><li>增/改:添加新的数据到索引库 </li><li>删:删除索引库数据</li></ul></li><li>静态页微服务:<ul><li>增/改:创建新的静态页</li><li>删:删除原来的静态页</li></ul></li></ul><h2 id="3-2-商品服务发送消息"><a href="#3-2-商品服务发送消息" class="headerlink" title="3.2.商品服务发送消息"></a>3.2.商品服务发送消息</h2><p>我们先在商品微服务<code>leyou-item-service</code>中实现发送消息。</p><h3 id="3-2-1-引入依赖"><a href="#3-2-1-引入依赖" class="headerlink" title="3.2.1.引入依赖"></a>3.2.1.引入依赖</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="3-2-2-配置文件"><a href="#3-2-2-配置文件" class="headerlink" title="3.2.2.配置文件"></a>3.2.2.配置文件</h3><p>我们在application.yml中添加一些有关RabbitMQ的配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> rabbitmq:</span></span><br><span class="line"><span class="attr"> host:</span> <span class="number">192.168</span><span class="number">.56</span><span class="number">.101</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> virtual-host:</span> <span class="string">/leyou</span></span><br><span class="line"><span class="attr"> template:</span></span><br><span class="line"><span class="attr"> exchange:</span> <span class="string">leyou.item.exchange</span></span><br><span class="line"><span class="attr"> publisher-confirms:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure></div><ul><li>template:有关<code>AmqpTemplate</code>的配置<ul><li>exchange:缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个</li></ul></li><li>publisher-confirms:生产者确认机制,确保消息会正确发送,如果发送失败会有错误回执,从而触发重试</li></ul><h3 id="3-2-3-改造GoodsService"><a href="#3-2-3-改造GoodsService" class="headerlink" title="3.2.3.改造GoodsService"></a>3.2.3.改造GoodsService</h3><p>在GoodsService中封装一个发送消息到mq的方法:<strong>(需要注入AmqpTemplate模板)</strong></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendMessage</span><span class="params">(Long id, String type)</span></span>{</span><br><span class="line"> <span class="comment">// 发送消息</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">this</span>.amqpTemplate.convertAndSend(<span class="string">"item."</span> + type, id);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.error(<span class="string">"{}商品消息发送异常,商品id:{}"</span>, type, id, e);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里没有指定交换机,因此默认发送到了配置中的:<code>leyou.item.exchange</code></p><p><strong>注意:这里要把所有异常都try起来,不能让消息的发送影响到正常的业务逻辑</strong></p><p>然后在新增的时候调用:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532768930797.png" alt="1532768930797"></p><p>修改的时候调用:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532769005960.png" alt="1532769005960"></p><h2 id="3-3-搜索服务接收消息"><a href="#3-3-搜索服务接收消息" class="headerlink" title="3.3.搜索服务接收消息"></a>3.3.搜索服务接收消息</h2><p>搜索服务接收到消息后要做的事情:</p><ul><li>增:添加新的数据到索引库</li><li>删:删除索引库数据</li><li>改:修改索引库数据</li></ul><p>因为索引库的新增和修改方法是合二为一的,因此我们可以将这两类消息一同处理,删除另外处理。</p><h3 id="3-3-1-引入依赖"><a href="#3-3-1-引入依赖" class="headerlink" title="3.3.1.引入依赖"></a>3.3.1.引入依赖</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="3-3-2-添加配置"><a href="#3-3-2-添加配置" class="headerlink" title="3.3.2.添加配置"></a>3.3.2.添加配置</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> rabbitmq:</span></span><br><span class="line"><span class="attr"> host:</span> <span class="number">192.168</span><span class="number">.56</span><span class="number">.101</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> virtual-host:</span> <span class="string">/leyou</span></span><br></pre></td></tr></table></figure></div><p>这里只是接收消息而不发送,所以不用配置template相关内容。</p><h3 id="3-3-3-编写监听器"><a href="#3-3-3-编写监听器" class="headerlink" title="3.3.3.编写监听器"></a>3.3.3.编写监听器</h3><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532769181819.png" alt="1532769181819"></p><p>代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GoodsListener</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> SearchService searchService;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 处理insert和update的消息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> id</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line"> value = <span class="meta">@Queue</span>(value = <span class="string">"leyou.create.index.queue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line"> exchange = <span class="meta">@Exchange</span>(</span><br><span class="line"> value = <span class="string">"leyou.item.exchange"</span>,</span><br><span class="line"> ignoreDeclarationExceptions = <span class="string">"true"</span>,</span><br><span class="line"> type = ExchangeTypes.TOPIC),</span><br><span class="line"> key = {<span class="string">"item.insert"</span>, <span class="string">"item.update"</span>}))</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenCreate</span><span class="params">(Long id)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">if</span> (id == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 创建或更新索引</span></span><br><span class="line"> <span class="keyword">this</span>.searchService.createIndex(id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 处理delete的消息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line"> value = <span class="meta">@Queue</span>(value = <span class="string">"leyou.delete.index.queue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line"> exchange = <span class="meta">@Exchange</span>(</span><br><span class="line"> value = <span class="string">"leyou.item.exchange"</span>,</span><br><span class="line"> ignoreDeclarationExceptions = <span class="string">"true"</span>,</span><br><span class="line"> type = ExchangeTypes.TOPIC),</span><br><span class="line"> key = <span class="string">"item.delete"</span>))</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenDelete</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (id == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 删除索引</span></span><br><span class="line"> <span class="keyword">this</span>.searchService.deleteIndex(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="3-3-4-编写创建和删除索引方法"><a href="#3-3-4-编写创建和删除索引方法" class="headerlink" title="3.3.4.编写创建和删除索引方法"></a>3.3.4.编写创建和删除索引方法</h3><p>这里因为要创建和删除索引,我们需要在SearchService中拓展两个方法,创建和删除索引:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createIndex</span><span class="params">(Long id)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"></span><br><span class="line"> Spu spu = <span class="keyword">this</span>.goodsClient.querySpuById(id);</span><br><span class="line"> <span class="comment">// 构建商品</span></span><br><span class="line"> Goods goods = <span class="keyword">this</span>.buildGoods(spu);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 保存数据到索引库</span></span><br><span class="line"> <span class="keyword">this</span>.goodsRepository.save(goods);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteIndex</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.goodsRepository.deleteById(id);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>创建索引的方法可以从之前导入数据的测试类中拷贝和改造。</p><h2 id="3-4-静态页服务接收消息"><a href="#3-4-静态页服务接收消息" class="headerlink" title="3.4.静态页服务接收消息"></a>3.4.静态页服务接收消息</h2><p>商品静态页服务接收到消息后的处理:</p><ul><li>增:创建新的静态页</li><li>删:删除原来的静态页</li><li>改:创建新的静态页并覆盖原来的</li></ul><p>不过,我们编写的创建静态页的方法也具备覆盖以前页面的功能,因此:增和改的消息可以放在一个方法中处理,删除消息放在另一个方法处理。</p><h3 id="3-4-1-引入依赖"><a href="#3-4-1-引入依赖" class="headerlink" title="3.4.1.引入依赖"></a>3.4.1.引入依赖</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="3-4-2-添加配置"><a href="#3-4-2-添加配置" class="headerlink" title="3.4.2.添加配置"></a>3.4.2.添加配置</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> rabbitmq:</span></span><br><span class="line"><span class="attr"> host:</span> <span class="number">192.168</span><span class="number">.56</span><span class="number">.101</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">leyou</span></span><br><span class="line"><span class="attr"> virtual-host:</span> <span class="string">/leyou</span></span><br></pre></td></tr></table></figure></div><p>这里只是接收消息而不发送,所以不用配置template相关内容。</p><h3 id="3-4-3-编写监听器"><a href="#3-4-3-编写监听器" class="headerlink" title="3.4.3.编写监听器"></a>3.4.3.编写监听器</h3><p> <img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532769581964.png" alt="1532769581964"></p><p>代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GoodsListener</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> GoodsHtmlService goodsHtmlService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line"> value = <span class="meta">@Queue</span>(value = <span class="string">"leyou.create.web.queue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line"> exchange = <span class="meta">@Exchange</span>(</span><br><span class="line"> value = <span class="string">"leyou.item.exchange"</span>,</span><br><span class="line"> ignoreDeclarationExceptions = <span class="string">"true"</span>,</span><br><span class="line"> type = ExchangeTypes.TOPIC),</span><br><span class="line"> key = {<span class="string">"item.insert"</span>, <span class="string">"item.update"</span>}))</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenCreate</span><span class="params">(Long id)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">if</span> (id == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 创建页面</span></span><br><span class="line"> goodsHtmlService.createHtml(id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line"> value = <span class="meta">@Queue</span>(value = <span class="string">"leyou.delete.web.queue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line"> exchange = <span class="meta">@Exchange</span>(</span><br><span class="line"> value = <span class="string">"leyou.item.exchange"</span>,</span><br><span class="line"> ignoreDeclarationExceptions = <span class="string">"true"</span>,</span><br><span class="line"> type = ExchangeTypes.TOPIC),</span><br><span class="line"> key = <span class="string">"item.delete"</span>))</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenDelete</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (id == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 删除页面</span></span><br><span class="line"> goodsHtmlService.deleteHtml(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="3-4-4-添加删除页面方法"><a href="#3-4-4-添加删除页面方法" class="headerlink" title="3.4.4.添加删除页面方法"></a>3.4.4.添加删除页面方法</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteHtml</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> File file = <span class="keyword">new</span> File(<span class="string">"C:\\project\\nginx-1.14.0\\html\\item\\"</span>, id + <span class="string">".html"</span>);</span><br><span class="line"> file.deleteOnExit();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="3-5-测试"><a href="#3-5-测试" class="headerlink" title="3.5.测试"></a>3.5.测试</h2><h3 id="3-5-1-查看RabbitMQ控制台"><a href="#3-5-1-查看RabbitMQ控制台" class="headerlink" title="3.5.1.查看RabbitMQ控制台"></a>3.5.1.查看RabbitMQ控制台</h3><p>重新启动项目,并且登录RabbitMQ管理界面:<a href="http://192.168.56.101:15672" target="_blank" rel="noopener">http://192.168.56.101:15672</a></p><p>可以看到,交换机已经创建出来了:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532772446520.png" alt="1532772446520"></p><p>队列也已经创建完毕:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532772471931.png" alt="1532772471931"></p><p>并且队列都已经绑定到交换机:</p><p><img src="/2019/12/18/消息中间件之RabbitMQ及数据同步/1532772521932.png" alt="1532772521932"></p><h3 id="3-5-2-修改数据试一试"><a href="#3-5-2-修改数据试一试" class="headerlink" title="3.5.2.修改数据试一试"></a>3.5.2.修改数据试一试</h3><p>在后台修改商品数据的价格,分别在搜索及商品详情页查看是否统一。</p>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>了解常见的MQ产品</li>
<li>了解RabbitMQ的5种消息模型</li>
<li>会使
</summary>
<category term="消息中间件" scheme="http://enfangzhong.github.io/categories/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="消息中间件" scheme="http://enfangzhong.github.io/tags/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="rabbitmq" scheme="http://enfangzhong.github.io/tags/rabbitmq/"/>
</entry>
<entry>
<title>深入理解Java虚拟机之破坏双亲委派加载机制</title>
<link href="http://enfangzhong.github.io/2019/12/17/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B9%8B%E7%A0%B4%E5%9D%8F%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"/>
<id>http://enfangzhong.github.io/2019/12/17/深入理解Java虚拟机之破坏双亲委派加载机制/</id>
<published>2019-12-17T03:35:00.000Z</published>
<updated>2019-12-18T06:25:11.746Z</updated>
<content type="html"><![CDATA[<p>双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者推荐给开发者的类加载器实现方式。在 Java 的世界中大部分类加载器都遵循这个原则,但是显然也有例外。</p><p>在《深入理解 JVM 虚拟机》一书中,作者提出双亲委派模型目前出现过 3 次较大规模的“被破坏”情况。</p><h1 id="1-第一次被破坏"><a href="#1-第一次被破坏" class="headerlink" title="1.第一次被破坏"></a>1.第一次被破坏</h1><p>第一次被破坏其实发生在双亲委派模型出现之前,也就是 JDK 1.2 发布之前,由于双亲委派模型在 JDK 1.2 之后才引入,而类加载器和抽象类 java.lang.ClassLoader 在 JDK 1.0 时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java 设计者在引入双亲委派模型时不得不做出一些妥协。为了向前兼容,JDK 1.2 之后的 java.lang.ClassLoader 添加了一个新的 protected 方法 findClass() 方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法 loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的 loadClass()。双亲委派的具体逻辑就在这个方法之中,JDK 1.2 之后已经不再提倡用户再去覆盖 loadClass() 方法,而应当把自己的类加载器逻辑写到 findClass() 方法来完成加载,这样就可以保证写出来的类加载器都是符合双亲委派规则的。</p><h1 id="2-第二次被破坏"><a href="#2-第二次被破坏" class="headerlink" title="2.第二次被破坏"></a>2.第二次被破坏</h1><p>第二次被破坏是由于这个模型自身的问题导致的,双亲委派很好地解决了各个类加载器的基础类统一的问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的 API,但世事往往没有绝对的完美,如果基础类又要调回用户的代码,那该怎么办?</p><p>这不是没有可能的事情,比如 JNDI 服务,JNDI 目前已经是 Java 的标准服务,它的代码由启动类加载器去加载(在 JDK 1.3 时放进去的 rt.jar),但 JNDI 的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的 ClassPath 下的 JNDI 接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码啊,那可怎么办?</p><p>为了解决这个问题,Java 设计团队只好引入另外一个不太优雅的设计:线程上下文加载器(Thread Context ClassLoader)。这个类加载器可以通过 java.lang.Thread 类的 setContextClassLoader()(原文在这里写成 setContextLoaser) 方法进行设置,如果创建线程时还未设置,它将从父线程中继承一个,如果在应用程序的全局范围内没有设置过的话,那么这个类加载器默认就是应用程序类加载器。</p><p>通过这个线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI 服务使用这个线程上下文类加载去加载所需要的 SPI 代码,也就是通过父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打破了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情,Java 中所有涉及 SPI 的加载动作都是采用的这种方式,例如 JNDI、JDBC、JCE、JAXB 和 JBI 等。</p><h1 id="3-第三次被破坏"><a href="#3-第三次被破坏" class="headerlink" title="3.第三次被破坏"></a>3.第三次被破坏</h1><p>第三次被破坏是由于用户对程序动态性追求而导致的,这里所说的“动态性”是指当前一些非常“热门”的名词:代码热替换(HotSwap)、模块热部署(Hot Deployment)等,说白了就是希望应用程序能像我们的计算机外设一样,插上鼠标和 U 盘不用重启机器就能立即使用。鼠标有问题就升级或者换个鼠标,不用停机也不用重启。对于实际生产系统来说,关机重启一次可能就要被列为生产事故。因此这种情况下热部署就非常有吸引力。</p><p>在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。当接收到类加载请求时,OSGI 将按照下面的顺序进行类搜索:</p><p>1.将以 java.* 开头的类委派给父类加载器加载<br>2.否则,将委派列表名单内的类委派给父类加载器进行加载<br>3.否则,将 Import 列表中的类委派给 Export 这个类的 Bundle 的类加载器加载<br>4.否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载<br>5.否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载<br>6.否则,查找 Dynamic Import 列表的 Bundle,委派给对应的 Bundle 的类加载器加载<br>7.否则,类查找失败</p><p>上述的查找顺序只有开头两点仍然符合双亲委派模型,其余的类查找都是在平级的类加载器中进行的。</p><h1 id="4-破坏双亲委派的原理"><a href="#4-破坏双亲委派的原理" class="headerlink" title="4.破坏双亲委派的原理"></a>4.破坏双亲委派的原理</h1><p>针对书中介绍的三种情况,往往使用最多的是线程上下文类加载器(TCCL,ThreadContextClassLoader)来破坏双亲委派机制。</p><div class="note info"><p>Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。</p><p>这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI 接口中的代码经常需要加载具体的实现类。那么问题来了,SPI 的接口是 Java 核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI 的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader 无法委派 AppClassLoader 来加载类。</p><p>而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。</p></div><p>那么我们来以 JDBC 源码来看看是如何破坏双亲委派模型的。 JDBC 也属于 SPI,其 Driver 接口是 Java 核心类库的一部分,但是 Driver 的实现类却是由第三方实现,是需要使用系统类加载器进行加载的,符合上述说的情况。</p><h2 id="JDBC-案例分析"><a href="#JDBC-案例分析" class="headerlink" title="JDBC 案例分析"></a>JDBC 案例分析</h2><p>我们先来看平时是如何使用 MySQL 获取数据库连接的:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类</span><br><span class="line">// Class.forName("com.mysql.jdbc.Driver").newInstance(); </span><br><span class="line">String url = "jdbc:mysql://localhost:3306/testdb"; </span><br><span class="line">// 通过java库获取数据库连接</span><br><span class="line">Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");</span><br></pre></td></tr></table></figure></div><p>以上就是 MySQL 注册驱动及获取 Connection 的过程,各位可以发现经常写的 Class.forName 被注释掉了,但依然可以正常运行,这是为什么呢?这是因为从 JDK 1.6 开始自带的 JDBC 4.0 版本已支持 SPI 服务加载机制,只要 MySQL 的 jar 包在类路径中,就可以注册 MySQL 驱动。</p><p>那到底是在哪一步自动注册了 MySQL Driver 的呢?重点就在 DriverManager.getConnection() 方法中。我们都知道调用一个类的静态方法会自动初始化该类(前提是该类还没有被初始化过),进而执行其静态代码块,DriverManager 中的静态代码块如下:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">static {</span><br><span class="line"> loadInitialDrivers();</span><br><span class="line"> println("JDBC DriverManager initialized");</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>初始化方法 loadInitialDrivers() 的代码如下:</p><p><br> private static void loadInitialDrivers() {<br> String drivers;<br> try {<br> // 先读取系统属性<br> drivers = AccessController.doPrivileged(new PrivilegedAction<string>() {<br> public String run() {<br> return System.getProperty(“jdbc.drivers”);<br> }<br> });<br> } catch (Exception ex) {<br> drivers = null;<br> }<br> // 通过SPI加载驱动类<br> AccessController.doPrivileged(new PrivilegedAction<void>() {<br> public Void run() {<br> ServiceLoader<driver> loadedDrivers = ServiceLoader.load(Driver.class);<br> Iterator<driver> driversIterator = loadedDrivers.iterator();<br> try{<br> while(driversIterator.hasNext()) {<br> driversIterator.next();<br> }<br> } catch(Throwable t) {<br> // Do nothing<br> }<br> return null;<br> }<br> });<br> // 继续加载系统属性中的驱动类<br> if (drivers == null || drivers.equals(“”)) {<br> return;<br> }</driver></driver></void></string></p><p><br> String[] driversList = drivers.split(“:”);<br> println(“number of Drivers:” + driversList.length);<br> for (String aDriver : driversList) {<br> try {<br> println(“DriverManager.Initialize: loading “ + aDriver);<br> // 使用AppClassloader加载<br> Class.forName(aDriver, true,<br> ClassLoader.getSystemClassLoader());<br> } catch (Exception ex) {<br> println(“DriverManager.Initialize: load failed: “ + ex);<br> }<br> }<br>}<br>从上面可以看出 JDBC 中的 DriverManager 的加载 Driver 的步骤顺序依次是:</p><p>通过 SPI 方式,读取 META-INF/services 下文件中的类名,使用 TCCL(线程上下文类加载器) 加载;<br>通过 System.getProperty(“jdbc.drivers”) 获取设置,然后通过系统类加载器加载。</p><p>下面详细分析 SPI 加载的那段代码。</p><h2 id="JDBC-中的-SPI"><a href="#JDBC-中的-SPI" class="headerlink" title="JDBC 中的 SPI"></a>JDBC 中的 SPI</h2><p>上面说了那么多 SPI,可能你还没弄懂什么是 SPI,所以先来看看什么是 SPI 机制,引用一段博文中的介绍:</p><div class="note info"><p><strong>SPI 机制简介</strong><br>SPI 的全名为 Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader 的文档里有比较详细的介绍。简单的总结下 java SPI 机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml 解析模块、JDBC 模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。</p><p><strong>SPI 具体约定</strong><br>Java SPI 的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在 jar 包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。JDK 提供服务实现查找的一个工具类:java.util.ServiceLoader。</p></div><p>知道 SPI 的机制后,我们来看刚才的代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);</span><br><span class="line">Iterator<Driver> driversIterator = loadedDrivers.iterator();</span><br><span class="line"></span><br><span class="line">try{</span><br><span class="line"> while(driversIterator.hasNext()) {</span><br><span class="line"> driversIterator.next();</span><br><span class="line"> }</span><br><span class="line">} catch(Throwable t) {</span><br><span class="line">// Do nothing</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>注意 driversIterator.next() 最终就是调用 Class.forName(DriverName, false, loader) 方法,也就是最开始我们注释掉的 Class.forName 加载驱动的方法。好,那句因 SPI 而省略的代码现在解释清楚了,那我们继续看给这个方法传的 loader 是怎么来的。</p><p>因为这句 Class.forName(DriverName, false, loader) 代码所在的类在 java.util.ServiceLoader 类中,而ServiceLoader.class 又加载在 BootrapLoader 中,因此传给 forName 的 loader 必然不能是 BootrapLoader。这时候只能使用 TCCL 了,也就是说把自己加载不了的类加载到 TCCL 中(通过 Thread.currentThread() 获取,简直作弊啊!)。</p><p>可以再看下 ServiceLoader.load(Class) 的代码,发现的确如此:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public static <S> ServiceLoader<S> load(Class<S> service) {</span><br><span class="line"> ClassLoader cl = Thread.currentThread().getContextClassLoader();</span><br><span class="line"> return ServiceLoader.load(service, cl);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>ContextClassLoader 默认存放了 AppClassLoader 的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader 或是 ExtClassLoader 等),在任何需要的时候都可以用 Thread.currentThread().getContextClassLoader() 取出应用程序类加载器来完成需要的操作。</p><p>到这儿差不多把 SPI 机制解释清楚了。直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在 /META-INF 里),那当我启动时我会去扫描所有 jar 包里符合约定的类名,再调用 forName 加载,但我的 ClassLoader 是没法加载的,那就把它加载到当前执行线程的 TCCL 里,后续你想怎么操作(驱动实现类的 static 代码块)就是你的事了。</p><p>好,刚才说的驱动实现类就是 com.mysql.jdbc.Driver,它的静态代码块里头又写了什么呢?是否又用到了 TCCL 呢?我们继续看下一个例子。</p><h2 id="校验实例的归属"><a href="#校验实例的归属" class="headerlink" title="校验实例的归属"></a>校验实例的归属</h2><p>com.mysql.jdbc.Driver 加载后运行的静态代码块:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">static {</span><br><span class="line">try {</span><br><span class="line">// Driver已经加载到TCCL中了,此时可以直接实例化</span><br><span class="line">java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());</span><br><span class="line">} catch (SQLException E) {</span><br><span class="line">throw new RuntimeException("Can't register driver!");</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>registerDriver 方法将 driver 实例注册到系统的 java.sql.DriverManager 类中,其实就是 add 到它的一个名为 registeredDrivers 的静态成员 CopyOnWriteArrayList 中 。</p><p>到此驱动注册基本完成,接下来我们回到最开始的那段样例代码:java.sql.DriverManager.getConnection()。它最终调用了以下方法:</p><pre><code> private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { /* 传入的caller由Reflection.getCallerClass()得到,该方法 * 可获取到调用本方法的Class类,这儿获取到的是当前应用的类加载器 */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } }</code></pre><p><br> if(url == null) {<br> throw new SQLException(“The url cannot be null”, “08001”);<br> }</p><pre><code> SQLException reason = null; // 遍历注册到registeredDrivers里的Driver类 for(DriverInfo aDriver : registeredDrivers) { // 检查Driver类有效性 if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); // 调用com.mysql.jdbc.Driver.connect方法获取连接 Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } throw new SQLException("No suitable driver found for "+ url, "08001");}private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) { boolean result = false; if(driver != null) { Class<?> aClass = null; try { // 传入的classLoader为调用getConnetction的当前类加载器,从中寻找driver的class对象 aClass = Class.forName(driver.getClass().getName(), true, classLoader); } catch (Exception ex) { result = false; } // 注意,只有同一个类加载器中的Class使用==比较时才会相等,此处就是校验用户注册Driver时该Driver所属的类加载器与调用时的是否同一个 // driver.getClass()拿到就是当初执行Class.forName("com.mysql.jdbc.Driver")时的应用AppClassLoader result = ( aClass == driver.getClass() ) ? true : false; } return result;}</code></pre><p>由于 TCCL 本质就是当前应用类加载器,所以之前的初始化就是加载在当前的类加载器中,这一步就是校验存放的 driver 是否属于调用者的 Classloader。例如在下文中的 Tomcat 里,多个 webapp 都有自己的 Classloader,如果它们都自带 mysql-connect.jar 包,那底层 Classloader 的 DriverManager 里将注册多个不同类加载器的 Driver 实例,想要区分只能靠 TCCL 了。</p><h1 id="Tomcat与Spring的类加载案例"><a href="#Tomcat与Spring的类加载案例" class="headerlink" title="Tomcat与Spring的类加载案例"></a>Tomcat与Spring的类加载案例</h1><h2 id="Tomcat-中的类加载器"><a href="#Tomcat-中的类加载器" class="headerlink" title="Tomcat 中的类加载器"></a>Tomcat 中的类加载器</h2><p>在 Tomcat 目录结构中,有三组目录(/common/<em>,/server/</em>和shared/<em>)可以存放公用 Java 类库,此外还有第四组 Web 应用程序自身的目录 /WEB-INF/</em> ,把 Java 类库放置在这些目录中的含义分别是:</p><p>放置在 common 目录中:类库可被 Tomcat 和所有的 Web 应用程序共同使用。<br>放置在 server 目录中:类库可被 Tomcat 使用,但对所有的 Web 应用程序都不可见。<br>放置在 shared 目录中:类库可被所有的 Web 应用程序共同使用,但对 Tomcat 自己不可见。<br>放置在 /WebApp/WEB-INF 目录中:类库仅仅可以被此 Web 应用程序使用,对 Tomcat 和其他 Web 应用程序都不可见。<br>为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat 自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,如下图所示:</p><p> <img src="/2019/12/17/深入理解Java虚拟机之破坏双亲委派加载机制/1240" alt="image.png"> </p><p>灰色背景的 3 个类加载器是 JDK 默认提供的类加载器,这 3 个加载器的作用前面已经介绍过了。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/<em>、/server/</em>、/shared/<em> 和 /WebApp/WEB-INF/</em> 中的 Java 类库。其中 WebApp 类加载器和 JSP 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 JSP 类加载器。</p><p>从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 JSP 类加载器来实现 JSP 文件的 HotSwap 功能。</p><h2 id="Spring-加载问题"><a href="#Spring-加载问题" class="headerlink" title="Spring 加载问题"></a>Spring 加载问题</h2><p>Tomcat 加载器的实现清晰易懂,并且采用了官方推荐的“正统”的使用类加载器的方式。这时作者提一个问题:如果有 10 个 Web 应用程序都用到了 Spring 的话,可以把 Spring 的 jar 包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个 web 应用程序的 bean, getBean 时自然要能访问到应用程序的类,而用户的程序显然是放在/WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的 Class 呢?</p><p>实际上,Spring 根本不会去管自己被放在哪里,它统统使用 TCCL 来加载类,而 TCCL 默认设置为了 WebAppClassLoader,也就是说哪个 WebApp 应用调用了 Spring,Spring 就去取该应用自己的 WebAppClassLoader 来加载 bean,简直完美~</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>有兴趣的可以接着看看具体实现:</p><p>在 web.xml 中定义的 listener 为 org.springframework.web.context.ContextLoaderListener,它最终调用了 org.springframework.web.context.ContextLoader 类来装载 bean,具体方法如下(删去了部分不相关内容):</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {</span><br><span class="line">try {</span><br><span class="line">// 创建WebApplicationContext</span><br><span class="line">if (this.context == null) {</span><br><span class="line">this.context = createWebApplicationContext(servletContext);</span><br><span class="line">}</span><br><span class="line">// 将其保存到该webapp的servletContext中</span><br><span class="line">servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);</span><br><span class="line">// 获取线程上下文类加载器,默认为WebAppClassLoader</span><br><span class="line">ClassLoader ccl = Thread.currentThread().getContextClassLoader();</span><br><span class="line">// 如果spring的jar包放在每个webapp自己的目录中</span><br><span class="line">// 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是WebAppClassLoader</span><br><span class="line">if (ccl == ContextLoader.class.getClassLoader()) {</span><br><span class="line">currentContext = this.context;</span><br><span class="line">}</span><br><span class="line">else if (ccl != null) {</span><br><span class="line">// 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的WebApplicationContext及对应的WebAppClassLoader存下来</span><br><span class="line">// 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出</span><br><span class="line">currentContextPerThread.put(ccl, this.context);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">return this.context;</span><br><span class="line">}</span><br><span class="line">catch (RuntimeException ex) {</span><br><span class="line">logger.error("Context initialization failed", ex);</span><br><span class="line">throw ex;</span><br><span class="line">}</span><br><span class="line">catch (Error err) {</span><br><span class="line">logger.error("Context initialization failed", err);</span><br><span class="line">throw err;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>具体说明都在注释中,Spring 考虑到了自己可能被放到其他位置,所以直接用 TCCL 来解决所有可能面临的情况。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过上面的两个案例分析,我们可以总结出线程上下文类加载器的适用场景:</p><p>当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的 ClassLoader 找到并加载该类。<br>当使用本类托管类加载,然而加载本类的 ClassLoader 未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。</p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><p>真正理解线程上下文类加载器(多案例分析)<br>JVM双亲委派机制与Tomcat</p>]]></content>
<summary type="html">
<p>双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者推荐给开发者的类加载器实现方式。在 Java 的世界中大部分类加载器都遵循这个原则,但是显然也有例外。</p>
<p>在《深入理解 JVM 虚拟机》一书中,作者提出双亲委派模型目前出现过 3 次较大规模的“被破坏
</summary>
<category term="JVM" scheme="http://enfangzhong.github.io/categories/JVM/"/>
<category term="JVM" scheme="http://enfangzhong.github.io/tags/JVM/"/>
<category term="深入理解Java虚拟机" scheme="http://enfangzhong.github.io/tags/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
<category term="Java虚拟机" scheme="http://enfangzhong.github.io/tags/Java%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
</entry>
<entry>
<title>Spring Cloud快速开发入门第02篇</title>
<link href="http://enfangzhong.github.io/2019/12/12/Spring%20Cloud%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8%E7%AC%AC02%E7%AF%87/"/>
<id>http://enfangzhong.github.io/2019/12/12/Spring Cloud快速开发入门第02篇/</id>
<published>2019-12-11T16:03:11.000Z</published>
<updated>2019-12-28T21:12:22.914Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>会配置Hystix熔断</li><li>会使用Feign进行远程调用</li><li>能独立搭建Zuul网关</li><li>能编写Zuul的过滤器</li></ul><h1 id="1-Hystrix"><a href="#1-Hystrix" class="headerlink" title="1.Hystrix"></a>1.Hystrix</h1><h2 id="1-1-简介"><a href="#1-1-简介" class="headerlink" title="1.1.简介"></a>1.1.简介</h2><p>Hystrix,英文意思是豪猪,全身是刺,看起来就不好惹,是一种保护机制。</p><p>Hystrix也是Netflix公司的一款组件。</p><p>主页:<a href="https://github.com/Netflix/Hystrix/" target="_blank" rel="noopener">https://github.com/Netflix/Hystrix/</a></p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525658740266.png" alt="1525658740266"></p><p>那么Hystix的作用是什么呢?具体要保护什么呢?</p><p>Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。</p><h2 id="1-2-雪崩问题"><a href="#1-2-雪崩问题" class="headerlink" title="1.2.雪崩问题"></a>1.2.雪崩问题</h2><p>微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1533829099748.png" alt="1533829099748"></p><p>如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。</p><p>如果此时,某个服务出现异常:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1533829198240.png" alt="1533829198240"></p><p>例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1533829307389.png" alt="1533829307389"></p><p>服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。</p><p>这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。 </p><p>Hystix解决雪崩问题的手段有两个:</p><ul><li>线程隔离</li><li>服务熔断</li></ul><h2 id="1-3-线程隔离,服务降级"><a href="#1-3-线程隔离,服务降级" class="headerlink" title="1.3.线程隔离,服务降级"></a>1.3.线程隔离,服务降级</h2><h3 id="1-3-1-原理"><a href="#1-3-1-原理" class="headerlink" title="1.3.1.原理"></a>1.3.1.原理</h3><p>线程隔离示意图:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1533829598310.png" alt="1533829598310"></p><p>解读:</p><p>Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。</p><p>用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果<strong>线程池已满</strong>,或者<strong>请求超时</strong>,则会进行降级处理,什么是服务降级?</p><blockquote><p>服务降级:优先保证核心服务,而非核心服务不可用或弱可用。</p></blockquote><p>用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。</p><p>服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。</p><p>触发Hystix服务降级的情况:</p><ul><li>线程池已满</li><li>请求超时</li></ul><h3 id="1-3-2-动手实践"><a href="#1-3-2-动手实践" class="headerlink" title="1.3.2.动手实践"></a>1.3.2.动手实践</h3><h4 id="1-3-2-1-引入依赖"><a href="#1-3-2-1-引入依赖" class="headerlink" title="1.3.2.1.引入依赖"></a>1.3.2.1.引入依赖</h4><p>首先在itcast-service-consumer的pom.xml中引入Hystrix依赖:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-hystrix<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h4 id="1-3-2-2-开启熔断"><a href="#1-3-2-2-开启熔断" class="headerlink" title="1.3.2.2.开启熔断"></a>1.3.2.2.开启熔断</h4><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535341341482.png" alt="1535341341482"></p><p>可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注解:@SpringCloudApplication</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535341390087.png" alt="1535341390087"></p><p>因此,我们可以使用这个组合注解来代替之前的3个注解。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringCloudApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastServiceConsumerApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="meta">@LoadBalanced</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastServiceConsumerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h4 id="1-3-2-3-编写降级逻辑"><a href="#1-3-2-3-编写降级逻辑" class="headerlink" title="1.3.2.3.编写降级逻辑"></a>1.3.2.3.编写降级逻辑</h4><p>我们改造itcast-service-consumer,当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystixCommond来完成:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="meta">@HystrixCommand</span>(fallbackMethod = <span class="string">"queryUserByIdFallBack"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id) </span>{</span><br><span class="line"> String user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://service-provider/user/"</span> + id, String.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">queryUserByIdFallBack</span><span class="params">(Long id)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"请求繁忙,请稍后再试!"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>要注意,因为熔断的降级逻辑方法必须跟正常逻辑方法保证:<strong>相同的参数列表和返回值声明</strong>。失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以我们把queryById的方法改造为返回String,反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。</p><p>说明:</p><ul><li>@HystrixCommand(fallbackMethod = “queryByIdFallBack”):用来声明一个降级逻辑的方法</li></ul><p>测试:</p><p>当itcast-service-provder正常提供服务时,访问与以前一致。但是当我们将itcast-service-provider停机时,会发现页面返回了降级处理信息:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535852634763.png" alt="1535852634763"></p><h4 id="1-3-2-4-默认FallBack"><a href="#1-3-2-4-默认FallBack" class="headerlink" title="1.3.2.4.默认FallBack"></a>1.3.2.4.默认FallBack</h4><p>我们刚才把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以我们可以把Fallback配置加在类上,实现默认fallback:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="meta">@DefaultProperties</span>(defaultFallback = <span class="string">"fallBackMethod"</span>) <span class="comment">// 指定一个类的全局熔断方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="meta">@HystrixCommand</span> <span class="comment">// 标记该方法需要熔断</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id) </span>{</span><br><span class="line"> String user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://service-provider/user/"</span> + id, String.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 熔断方法</span></span><br><span class="line"><span class="comment"> * 返回值要和被熔断的方法的返回值一致</span></span><br><span class="line"><span class="comment"> * 熔断方法不需要参数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">fallBackMethod</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"请求繁忙,请稍后再试!"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li>@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法</li><li>@HystrixCommand:在方法上直接使用该注解,使用默认的剪辑方法。</li><li>defaultFallback:默认降级方法,不用任何参数,以匹配更多方法,但是返回值一定一致</li></ul><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535852634763.png" alt="1535852634763"></p><h4 id="1-3-2-5-设置超时"><a href="#1-3-2-5-设置超时" class="headerlink" title="1.3.2.5.设置超时"></a>1.3.2.5.设置超时</h4><p>在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过配置修改这个值:</p><p>我们可以通过<code>hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds</code>来设置Hystrix超时时间。该配置没有提示。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">hystrix:</span></span><br><span class="line"><span class="attr"> command:</span></span><br><span class="line"><span class="attr"> default:</span></span><br><span class="line"><span class="attr"> execution:</span></span><br><span class="line"><span class="attr"> isolation:</span></span><br><span class="line"><span class="attr"> thread:</span></span><br><span class="line"><span class="attr"> timeoutInMilliseconds:</span> <span class="number">6000</span> <span class="comment"># 设置hystrix的超时时间为6000ms</span></span><br></pre></td></tr></table></figure></div><p><strong>改造服务提供者</strong></p><p>改造服务提供者的UserController接口,随机休眠一段时间,以触发熔断:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"{id}"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">6000</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.userService.queryUserById(id);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="1-4-服务熔断"><a href="#1-4-服务熔断" class="headerlink" title="1.4.服务熔断"></a>1.4.服务熔断</h2><h3 id="1-4-1-熔断原理"><a href="#1-4-1-熔断原理" class="headerlink" title="1.4.1.熔断原理"></a>1.4.1.熔断原理</h3><p>熔断器,也叫断路器,其英文单词为:Circuit Breaker </p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525658640314.png" alt="1525658640314"></p><p>熔断状态机3个状态:</p><ul><li>Closed:关闭状态,所有请求都正常访问。</li><li>Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。</li><li>Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时</li></ul><h3 id="1-4-2-动手实践"><a href="#1-4-2-动手实践" class="headerlink" title="1.4.2.动手实践"></a>1.4.2.动手实践</h3><p>为了能够精确控制请求的成功或失败,我们在consumer的调用业务中加入一段逻辑:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"{id}"</span>)</span><br><span class="line"><span class="meta">@HystrixCommand</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">queryUserById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>{</span><br><span class="line"> <span class="keyword">if</span>(id == <span class="number">1</span>){</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"太忙了"</span>);</span><br><span class="line"> }</span><br><span class="line"> String user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://service-provider/user/"</span> + id, String.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这样如果参数是id为1,一定失败,其它情况都成功。(不要忘了清空service-provider中的休眠逻辑)</p><p>我们准备两个请求窗口:</p><ul><li>一个请求:<a href="http://localhost/consumer/user/1,注定失败" target="_blank" rel="noopener">http://localhost/consumer/user/1,注定失败</a></li><li>一个请求:<a href="http://localhost/consumer/user/2,肯定成功" target="_blank" rel="noopener">http://localhost/consumer/user/2,肯定成功</a></li></ul><p>当我们疯狂访问id为1的请求时(超过20次),就会触发熔断。断路器会断开,一切请求都会被降级处理。</p><p>此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有几毫秒左右:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1543053265477.png" alt="1543053265477"></p><p>不过,默认的熔断触发要求较高,休眠时间窗较短,为了测试方便,我们可以通过配置修改熔断策略:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">circuitBreaker.requestVolumeThreshold=10</span><br><span class="line">circuitBreaker.sleepWindowInMilliseconds=10000</span><br><span class="line">circuitBreaker.errorThresholdPercentage=50</span><br></pre></td></tr></table></figure></div><p>解读:</p><ul><li>requestVolumeThreshold:触发熔断的最小请求次数,默认20</li><li>errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%</li><li>sleepWindowInMilliseconds:休眠时长,默认是5000毫秒</li></ul><h1 id="2-Feign"><a href="#2-Feign" class="headerlink" title="2.Feign"></a>2.Feign</h1><p>在前面的学习中,我们使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://service-provider/user/"</span> + id, String.class);</span><br></pre></td></tr></table></figure></div><p>如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?</p><p>这就是我们接下来要学的Feign的功能了。</p><h2 id="2-1-简介"><a href="#2-1-简介" class="headerlink" title="2.1.简介"></a>2.1.简介</h2><p>有道词典的英文解释:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1528855057359.png" alt="1528855057359"></p><p>为什么叫伪装?</p><p>Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。</p><p>项目主页:<a href="https://github.com/OpenFeign/feign" target="_blank" rel="noopener">https://github.com/OpenFeign/feign</a></p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525652009416.png" alt="1525652009416"></p><h2 id="2-2-快速入门"><a href="#2-2-快速入门" class="headerlink" title="2.2.快速入门"></a>2.2.快速入门</h2><p>改造itcast-service-consumer工程</p><h3 id="2-2-1-导入依赖"><a href="#2-2-1-导入依赖" class="headerlink" title="2.2.1.导入依赖"></a>2.2.1.导入依赖</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-openfeign<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="2-2-2-开启Feign功能"><a href="#2-2-2-开启Feign功能" class="headerlink" title="2.2.2.开启Feign功能"></a>2.2.2.开启Feign功能</h3><p>我们在启动类上,<strong>添加注解</strong>,开启Feign功能</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringCloudApplication</span></span><br><span class="line"><span class="meta">@EnableFeignClients</span> <span class="comment">// 开启feign客户端</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastServiceConsumerApplication</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastServiceConsumerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><strong>删除RestTemplate</strong>:feign已经自动集成了Ribbon负载均衡的RestTemplate。所以,此处不需要再注册RestTemplate。</p><h3 id="2-2-3-Feign的客户端"><a href="#2-2-3-Feign的客户端" class="headerlink" title="2.2.3.Feign的客户端"></a>2.2.3.Feign的客户端</h3><p>在itcast-service-consumer工程中,添加UserClient接口:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1540683659305.png" alt="1540683659305"></p><p>内容:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"service-provider"</span>) <span class="comment">// 标注该类是一个feign接口</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"user/{id}"</span>)</span><br><span class="line"> <span class="function">User <span class="title">queryById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li>首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像</li><li><code>@FeignClient</code>,声明这是一个Feign客户端,类似<code>@Mapper</code>注解。同时通过<code>value</code>属性指定服务名称</li><li>接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果</li></ul><p>改造原来的调用逻辑,调用UserClient接口:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserClient userClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id)</span>{</span><br><span class="line"> User user = <span class="keyword">this</span>.userClient.queryUserById(id);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="2-2-4-启动测试"><a href="#2-2-4-启动测试" class="headerlink" title="2.2.4.启动测试"></a>2.2.4.启动测试</h3><p>访问接口: </p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535346290987.png" alt="1535346290987"></p><p>正常获取到了结果。</p><h2 id="2-3-负载均衡"><a href="#2-3-负载均衡" class="headerlink" title="2.3.负载均衡"></a>2.3.负载均衡</h2><p>Feign中本身已经集成了Ribbon依赖和自动配置:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1528859608579.png" alt="1528859608579"></p><p>因此我们不需要额外引入依赖,也不需要再注册<code>RestTemplate</code>对象。</p><h2 id="2-4-Hystrix支持"><a href="#2-4-Hystrix支持" class="headerlink" title="2.4.Hystrix支持"></a>2.4.Hystrix支持</h2><p>Feign默认也有对Hystrix的集成:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1528861288636.png" alt="1528861288636"></p><p>只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:(在itcast-service-consumer工程添加配置内容)</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">feign:</span></span><br><span class="line"><span class="attr"> hystrix:</span></span><br><span class="line"><span class="attr"> enabled:</span> <span class="literal">true</span> <span class="comment"># 开启Feign的熔断功能</span></span><br></pre></td></tr></table></figure></div><p>但是,Feign中的Fallback配置不像hystrix中那样简单了。</p><p>1)首先,我们要定义一个类UserClientFallback,实现刚才编写的UserClient,作为fallback的处理类</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1540683742479.png" alt="1540683742479"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserClientFallback</span> <span class="keyword">implements</span> <span class="title">UserClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryById</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> user.setUserName(<span class="string">"服务器繁忙,请稍后再试!"</span>);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>2)然后在UserFeignClient中,指定刚才编写的实现类</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"service-provider"</span>, fallback = UserClientFallback.class) <span class="comment">// 标注该类是一个feign接口</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"user/{id}"</span>)</span><br><span class="line"> <span class="function">User <span class="title">queryUserById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>3)重启测试:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1535346896313.png" alt="1535346896313"></p><h2 id="2-5-请求压缩-了解"><a href="#2-5-请求压缩-了解" class="headerlink" title="2.5.请求压缩(了解)"></a>2.5.请求压缩(了解)</h2><p>Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">feign:</span></span><br><span class="line"><span class="attr"> compression:</span></span><br><span class="line"><span class="attr"> request:</span></span><br><span class="line"><span class="attr"> enabled:</span> <span class="literal">true</span> <span class="comment"># 开启请求压缩</span></span><br><span class="line"><span class="attr"> response:</span></span><br><span class="line"><span class="attr"> enabled:</span> <span class="literal">true</span> <span class="comment"># 开启响应压缩</span></span><br></pre></td></tr></table></figure></div><p>同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">feign:</span></span><br><span class="line"><span class="attr"> compression:</span></span><br><span class="line"><span class="attr"> request:</span></span><br><span class="line"><span class="attr"> enabled:</span> <span class="literal">true</span> <span class="comment"># 开启请求压缩</span></span><br><span class="line"><span class="attr"> mime-types:</span> <span class="string">text/html,application/xml,application/json</span> <span class="comment"># 设置压缩的数据类型</span></span><br><span class="line"><span class="attr"> min-request-size:</span> <span class="number">2048</span> <span class="comment"># 设置触发压缩的大小下限</span></span><br></pre></td></tr></table></figure></div><p>注:上面的数据类型、压缩大小下限均为默认值。</p><h2 id="2-6-日志级别-了解"><a href="#2-6-日志级别-了解" class="headerlink" title="2.6.日志级别(了解)"></a>2.6.日志级别(了解)</h2><p>前面讲过,通过<code>logging.level.xx=debug</code>来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为<code>@FeignClient</code>注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。</p><p>1)设置com.leyou包下的日志级别都为debug</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line"><span class="attr"> level:</span></span><br><span class="line"> <span class="string">cn.itcast:</span> <span class="string">debug</span></span><br></pre></td></tr></table></figure></div><p>2)编写配置类,定义日志级别</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529113196740.png" alt="1529113196740"></p><p>内容:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeignLogConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> Logger.<span class="function">Level <span class="title">feignLoggerLevel</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> Logger.Level.FULL;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里指定的Level级别是FULL,Feign支持4种级别:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1528863525224.png" alt="1528863525224"></p><ul><li>NONE:不记录任何日志信息,这是默认值。</li><li>BASIC:仅记录请求的方法,URL以及响应状态码和执行时间</li><li>HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息</li><li>FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。</li></ul><p>3)在FeignClient中指定配置类:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"service-privider"</span>, fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserFeignClient</span> </span>{</span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"/user/{id}"</span>)</span><br><span class="line"> <span class="function">User <span class="title">queryUserById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>4)重启项目,即可看到每次访问的日志:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1528863489923.png" alt="1528863489923"></p><h1 id="3-Zuul网关"><a href="#3-Zuul网关" class="headerlink" title="3.Zuul网关"></a>3.Zuul网关</h1><p>通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525674644660.png" alt="1525674644660"></p><p>我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。</p><p>在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,直接暴露我们的服务地址,这样的实现是否合理,或者是否有更好的实现方式呢?</p><p>先来说说这样架构需要做的一些事儿以及存在的不足:</p><ul><li><p>破坏了服务无状态特点。</p><pre><code>为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。</code></pre><p> 从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。</p></li><li><p>无法直接复用既有接口。</p><pre><code>当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。</code></pre></li></ul><p>面对类似上面的问题,我们要如何解决呢?答案是:服务网关!</p><p>为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器的 服务网关。</p><p>服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备<code>服务路由</code>、<code>均衡负载</code>功能之外,它还具备了<code>权限控制</code>等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。</p><h2 id="3-1-简介"><a href="#3-1-简介" class="headerlink" title="3.1.简介"></a>3.1.简介</h2><p>官网:<a href="https://github.com/Netflix/zuul" target="_blank" rel="noopener">https://github.com/Netflix/zuul</a></p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525675037152.png" alt=""></p><p>Zuul:维基百科</p><p>电影《捉鬼敢死队》中的怪兽,Zuul,在纽约引发了巨大骚乱。</p><p>事实上,在微服务架构中,Zuul就是守门的大Boss!一夫当关,万夫莫开!</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525675168152.png" alt="1525675168152"></p><h2 id="3-2-Zuul加入后的架构"><a href="#3-2-Zuul加入后的架构" class="headerlink" title="3.2.Zuul加入后的架构"></a>3.2.Zuul加入后的架构</h2><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525675648881.png" alt="1525675648881"></p><p>不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。</p><h2 id="3-3-快速入门"><a href="#3-3-快速入门" class="headerlink" title="3.3.快速入门"></a>3.3.快速入门</h2><h3 id="3-3-1-新建工程"><a href="#3-3-1-新建工程" class="headerlink" title="3.3.1.新建工程"></a>3.3.1.新建工程</h3><p>填写基本信息:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529112749084.png" alt="1529112749084"></p><p>添加Zuul依赖:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529112691169.png" alt="1529112691169"></p><h3 id="3-3-2-编写配置"><a href="#3-3-2-编写配置" class="headerlink" title="3.3.2.编写配置"></a>3.3.2.编写配置</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">10010</span> <span class="comment">#服务端口</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">api-gateway</span> <span class="comment">#指定服务名</span></span><br></pre></td></tr></table></figure></div><h3 id="3-3-3-编写引导类"><a href="#3-3-3-编写引导类" class="headerlink" title="3.3.3.编写引导类"></a>3.3.3.编写引导类</h3><p>通过<code>@EnableZuulProxy</code>注解开启Zuul的功能:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableZuulProxy</span> <span class="comment">// 开启网关功能</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastZuulApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastZuulApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="3-3-4-编写路由规则"><a href="#3-3-4-编写路由规则" class="headerlink" title="3.3.4.编写路由规则"></a>3.3.4.编写路由规则</h3><p>我们需要用Zuul来代理service-provider服务,先看一下控制面板中的服务状态:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1542672192226.png" alt="1542672192226"></p><ul><li>ip为:127.0.0.1</li><li>端口为:8081</li></ul><p>映射规则:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">10010</span> <span class="comment">#服务端口</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">api-gateway</span> <span class="comment">#指定服务名</span></span><br><span class="line"><span class="attr">zuul:</span></span><br><span class="line"><span class="attr"> routes:</span></span><br><span class="line"><span class="attr"> service-provider:</span> <span class="comment"># 这里是路由id,随意写</span></span><br><span class="line"><span class="attr"> path:</span> <span class="string">/service-provider/**</span> <span class="comment"># 这里是映射路径</span></span><br><span class="line"><span class="attr"> url:</span> <span class="attr">http://127.0.0.1:8081</span> <span class="comment"># 映射路径对应的实际url地址</span></span><br></pre></td></tr></table></figure></div><p>我们将符合<code>path</code> 规则的一切请求,都代理到 <code>url</code>参数指定的地址</p><p>本例中,我们将 <code>/service-provider/**</code>开头的请求,代理到<a href="http://127.0.0.1:8081" target="_blank" rel="noopener">http://127.0.0.1:8081</a></p><h3 id="3-3-5-启动测试"><a href="#3-3-5-启动测试" class="headerlink" title="3.3.5.启动测试"></a>3.3.5.启动测试</h3><p>访问的路径中需要加上配置规则的映射路径,我们访问:<a href="http://127.0.0.1:10010/service-provider/user/1" target="_blank" rel="noopener">http://127.0.0.1:10010/service-provider/user/1</a></p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1543054030005.png" alt="1543054030005"></p><h2 id="3-4-面向服务的路由"><a href="#3-4-面向服务的路由" class="headerlink" title="3.4.面向服务的路由"></a>3.4.面向服务的路由</h2><p>在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!</p><p>对itcast-zuul工程修改优化:</p><h3 id="3-4-1-添加Eureka客户端依赖"><a href="#3-4-1-添加Eureka客户端依赖" class="headerlink" title="3.4.1.添加Eureka客户端依赖"></a>3.4.1.添加Eureka客户端依赖</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="3-4-2-添加Eureka配置,获取服务信息"><a href="#3-4-2-添加Eureka配置,获取服务信息" class="headerlink" title="3.4.2.添加Eureka配置,获取服务信息"></a>3.4.2.添加Eureka配置,获取服务信息</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> registry-fetch-interval-seconds:</span> <span class="number">5</span> <span class="comment"># 获取服务列表的周期:5s</span></span><br><span class="line"><span class="attr"> service-url:</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10086/eureka</span></span><br></pre></td></tr></table></figure></div><h3 id="3-4-3-开启Eureka客户端发现功能"><a href="#3-4-3-开启Eureka客户端发现功能" class="headerlink" title="3.4.3.开启Eureka客户端发现功能"></a>3.4.3.开启Eureka客户端发现功能</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableZuulProxy</span> <span class="comment">// 开启Zuul的网关功能</span></span><br><span class="line"><span class="meta">@EnableDiscoveryClient</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZuulDemoApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">SpringApplication.run(ZuulDemoApplication.class, args);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="3-4-4-修改映射配置,通过服务名称获取"><a href="#3-4-4-修改映射配置,通过服务名称获取" class="headerlink" title="3.4.4.修改映射配置,通过服务名称获取"></a>3.4.4.修改映射配置,通过服务名称获取</h3><p>因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">zuul:</span></span><br><span class="line"><span class="attr"> routes:</span></span><br><span class="line"><span class="attr"> service-provider:</span> <span class="comment"># 这里是路由id,随意写</span></span><br><span class="line"><span class="attr"> path:</span> <span class="string">/service-provider/**</span> <span class="comment"># 这里是映射路径</span></span><br><span class="line"><span class="attr"> serviceId:</span> <span class="string">service-provider</span> <span class="comment"># 指定服务名称</span></span><br></pre></td></tr></table></figure></div><h3 id="3-4-5-启动测试"><a href="#3-4-5-启动测试" class="headerlink" title="3.4.5.启动测试"></a>3.4.5.启动测试</h3><p>再次启动,这次Zuul进行代理时,会利用Ribbon进行负载均衡访问:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1543054030005.png" alt="1543054030005"></p><h2 id="3-5-简化的路由配置"><a href="#3-5-简化的路由配置" class="headerlink" title="3.5.简化的路由配置"></a>3.5.简化的路由配置</h2><p>在刚才的配置中,我们的规则是这样的:</p><ul><li><code>zuul.routes.<route>.path=/xxx/**</code>: 来指定映射路径。<code><route></code>是自定义的路由名</li><li><code>zuul.routes.<route>.serviceId=service-provider</code>:来指定服务名。</li></ul><p>而大多数情况下,我们的<code><route></code>路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:<code>zuul.routes.<serviceId>=<path></code></p><p>比方说上面我们关于service-provider的配置可以简化为一条:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">zuul:</span></span><br><span class="line"><span class="attr"> routes:</span></span><br><span class="line"><span class="attr"> service-provider:</span> <span class="string">/service-provider/**</span> <span class="comment"># 这里是映射路径</span></span><br></pre></td></tr></table></figure></div><p>省去了对服务名称的配置。</p><h2 id="3-6-默认的路由规则"><a href="#3-6-默认的路由规则" class="headerlink" title="3.6.默认的路由规则"></a>3.6.默认的路由规则</h2><p>在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:</p><ul><li>默认情况下,一切服务的映射路径就是服务名本身。例如服务名为:<code>service-provider</code>,则默认的映射路径就 是:<code>/service-provider/**</code></li></ul><p>也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看。</p><h2 id="3-7-路由前缀"><a href="#3-7-路由前缀" class="headerlink" title="3.7.路由前缀"></a>3.7.路由前缀</h2><p>配置示例:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">zuul:</span></span><br><span class="line"><span class="attr"> routes:</span></span><br><span class="line"><span class="attr"> service-provider:</span> <span class="string">/service-provider/**</span></span><br><span class="line"><span class="attr"> service-consumer:</span> <span class="string">/service-consumer/**</span></span><br><span class="line"><span class="attr"> prefix:</span> <span class="string">/api</span> <span class="comment"># 添加路由前缀</span></span><br></pre></td></tr></table></figure></div><p>我们通过<code>zuul.prefix=/api</code>来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1543054221479.png" alt="1543054221479"></p><h2 id="3-8-过滤器"><a href="#3-8-过滤器" class="headerlink" title="3.8.过滤器"></a>3.8.过滤器</h2><p>Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。</p><h3 id="3-8-1-ZuulFilter"><a href="#3-8-1-ZuulFilter" class="headerlink" title="3.8.1.ZuulFilter"></a>3.8.1.ZuulFilter</h3><p>ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> ZuulFilter implements IZuulFilter{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">abstract</span> <span class="keyword">public</span> String <span class="title">filterType</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">abstract</span> <span class="keyword">public</span> <span class="keyword">int</span> <span class="title">filterOrder</span><span class="params">()</span></span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">shouldFilter</span><span class="params">()</span></span>;<span class="comment">// 来自IZuulFilter</span></span><br><span class="line"></span><br><span class="line"> <span class="function">Object <span class="title">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException</span>;<span class="comment">// IZuulFilter</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li><code>shouldFilter</code>:返回一个<code>Boolean</code>值,判断该过滤器是否需要执行。返回true执行,返回false不执行。</li><li><code>run</code>:过滤器的具体业务逻辑。</li><li><code>filterType</code>:返回字符串,代表过滤器的类型。包含以下4种:<ul><li><code>pre</code>:请求在被路由之前执行</li><li><code>route</code>:在路由请求时调用</li><li><code>post</code>:在route和errror过滤器之后调用</li><li><code>error</code>:处理请求时发生错误调用</li></ul></li><li><code>filterOrder</code>:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。</li></ul><h3 id="3-8-2-过滤器执行生命周期"><a href="#3-8-2-过滤器执行生命周期" class="headerlink" title="3.8.2.过滤器执行生命周期"></a>3.8.2.过滤器执行生命周期</h3><p>这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529152248172.png" alt="1529152248172"></p><p>正常流程:</p><ul><li>请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。</li></ul><p>异常流程:</p><ul><li>整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。</li><li>如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。</li><li>如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。</li></ul><p>所有内置过滤器列表:</p><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1525682427811.png" alt=""></p><h3 id="3-8-3-使用场景"><a href="#3-8-3-使用场景" class="headerlink" title="3.8.3.使用场景"></a>3.8.3.使用场景</h3><p>场景非常多:</p><ul><li>请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了</li><li>异常处理:一般会在error类型和post类型过滤器中结合来处理。</li><li>服务调用时长统计:pre和post结合使用。</li></ul><h2 id="3-9-自定义过滤器"><a href="#3-9-自定义过滤器" class="headerlink" title="3.9.自定义过滤器"></a>3.9.自定义过滤器</h2><p>接下来我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。</p><h3 id="3-9-1-定义过滤器类"><a href="#3-9-1-定义过滤器类" class="headerlink" title="3.9.1.定义过滤器类"></a>3.9.1.定义过滤器类</h3><p> <img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529136926454.png" alt="1529136926454"></p><p>内容:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoginFilter</span> <span class="keyword">extends</span> <span class="title">ZuulFilter</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 过滤器类型,前置过滤器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">filterType</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"pre"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 过滤器的执行顺序</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">filterOrder</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 该过滤器是否生效</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">shouldFilter</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登陆校验逻辑</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> ZuulException</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException </span>{</span><br><span class="line"> <span class="comment">// 获取zuul提供的上下文对象</span></span><br><span class="line"> RequestContext context = RequestContext.getCurrentContext();</span><br><span class="line"> <span class="comment">// 从上下文对象中获取请求对象</span></span><br><span class="line"> HttpServletRequest request = context.getRequest();</span><br><span class="line"> <span class="comment">// 获取token信息</span></span><br><span class="line"> String token = request.getParameter(<span class="string">"access-token"</span>);</span><br><span class="line"> <span class="comment">// 判断</span></span><br><span class="line"> <span class="keyword">if</span> (StringUtils.isBlank(token)) {</span><br><span class="line"> <span class="comment">// 过滤该请求,不对其进行路由</span></span><br><span class="line"> context.setSendZuulResponse(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">// 设置响应状态码,401</span></span><br><span class="line"> context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);</span><br><span class="line"> <span class="comment">// 设置响应信息</span></span><br><span class="line"> context.setResponseBody(<span class="string">"{\"status\":\"401\", \"text\":\"request error!\"}"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 校验通过,把登陆信息放入上下文信息,继续向后执行</span></span><br><span class="line"> context.set(<span class="string">"token"</span>, token);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="3-9-2-测试"><a href="#3-9-2-测试" class="headerlink" title="3.9.2.测试"></a>3.9.2.测试</h3><p>没有token参数时,访问失败:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529161460740.png" alt="1529161460740"></p><p>添加token参数后:</p><p><img src="/2019/12/12/Spring Cloud快速开发入门第02篇/1529161252733.png" alt="1529161252733"></p><h2 id="3-10-负载均衡和熔断"><a href="#3-10-负载均衡和熔断" class="headerlink" title="3.10.负载均衡和熔断"></a>3.10.负载均衡和熔断</h2><p>Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">hystrix:</span></span><br><span class="line"><span class="attr"> command:</span></span><br><span class="line"><span class="attr"> default:</span></span><br><span class="line"><span class="attr"> execution:</span></span><br><span class="line"><span class="attr"> isolation:</span></span><br><span class="line"><span class="attr"> thread:</span></span><br><span class="line"><span class="attr"> timeoutInMilliseconds:</span> <span class="number">2000</span> <span class="comment"># 设置hystrix的超时时间为6000ms</span></span><br></pre></td></tr></table></figure></div>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>会配置Hystix熔断</li>
<li>会使用Feign进行远程调用</li>
<li>能独立
</summary>
<category term="Spring Cloud" scheme="http://enfangzhong.github.io/categories/Spring-Cloud/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/categories/Spring-Cloud/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="http://enfangzhong.github.io/tags/Spring-Cloud/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
</entry>
<entry>
<title>Spring Cloud快速开发入门第01篇</title>
<link href="http://enfangzhong.github.io/2019/12/11/Spring%20Cloud%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8%E7%AC%AC01%E7%AF%87/"/>
<id>http://enfangzhong.github.io/2019/12/11/Spring Cloud快速开发入门第01篇/</id>
<published>2019-12-11T08:03:11.000Z</published>
<updated>2019-12-18T06:18:55.028Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>了解系统架构的演变</li><li>了解RPC与Http的区别</li><li>知道什么是SpringCloud</li><li>独立搭建Eureka注册中心</li><li>独立配置Robbin负载均衡</li></ul><h1 id="1-系统架构演变"><a href="#1-系统架构演变" class="headerlink" title="1.系统架构演变"></a>1.系统架构演变</h1><p>随着互联网的发展,网站应用的规模不断扩大。需求的激增,带来的是技术上的压力。系统架构也因此不断的演进、升级、迭代。从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构,还有在Google带领下来势汹涌的Service Mesh。我们到底是该乘坐微服务的船只驶向远方,还是偏安一隅得过且过?</p><p>其实生活不止眼前的苟且,还有诗和远方。所以我们今天就回顾历史,看一看系统架构演变的历程;把握现在,学习现在最火的技术架构;展望未来,争取成为一名优秀的Java工程师。</p><h2 id="1-1-集中式架构"><a href="#1-1-集中式架构" class="headerlink" title="1.1.集中式架构"></a>1.1.集中式架构</h2><p>当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525529091749.png" alt="1525529091749"></p><p>存在的问题:</p><ul><li>代码耦合,开发维护困难</li><li>无法针对不同模块进行针对性优化</li><li>无法水平扩展</li><li>单点容错率低,并发能力差</li></ul><h2 id="1-2-垂直拆分"><a href="#1-2-垂直拆分" class="headerlink" title="1.2.垂直拆分"></a>1.2.垂直拆分</h2><p>当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525529671801.png" alt="1525529671801"></p><p>优点:</p><ul><li>系统拆分实现了流量分担,解决了并发问题</li><li>可以针对不同模块进行优化</li><li>方便水平扩展,负载均衡,容错率提高</li></ul><p>缺点:</p><ul><li>系统间相互独立,会有很多重复开发工作,影响开发效率</li></ul><h2 id="1-3-分布式服务"><a href="#1-3-分布式服务" class="headerlink" title="1.3.分布式服务"></a>1.3.分布式服务</h2><p>当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525530657919.png" alt="1525530657919"></p><p>优点:</p><ul><li>将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率</li></ul><p>缺点:</p><ul><li>系统间耦合度变高,调用关系错综复杂,难以维护</li></ul><h2 id="1-4-流动计算架构(SOA)"><a href="#1-4-流动计算架构(SOA)" class="headerlink" title="1.4.流动计算架构(SOA)"></a>1.4.流动计算架构(SOA)</h2><p>SOA :面向服务的架构</p><p>当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525530804753.png" alt="1525530804753"></p><p>以前出现了什么问题?</p><ul><li>服务越来越多,需要管理每个服务的地址</li><li>调用关系错综复杂,难以理清依赖关系</li><li>服务过多,服务状态难以管理,无法根据服务情况动态管理</li></ul><p>服务治理要做什么?</p><ul><li>服务注册中心,实现服务自动注册和发现,无需人为记录服务地址</li><li>服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系</li><li>动态监控服务状态监控报告,人为控制服务状态</li></ul><p>缺点:</p><ul><li>服务间会有依赖关系,一旦某个环节出错会影响较大</li><li>服务关系复杂,运维、测试部署困难,不符合DevOps思想</li></ul><h2 id="1-5-微服务"><a href="#1-5-微服务" class="headerlink" title="1.5.微服务"></a>1.5.微服务</h2><p>前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实却有一些差别:</p><p>微服务的特点:</p><ul><li>单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责</li><li>微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。</li><li>面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。</li><li>自治:自治是说服务间互相独立,互不干扰<ul><li>团队独立:每个服务都是一个独立的开发团队,人数不能过多。</li><li>技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉</li><li>前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口</li><li>数据库分离:每个服务都使用自己的数据源</li><li>部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护</li></ul></li></ul><p>微服务结构图:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1526860071166.png" alt="1526860071166"></p><h1 id="2-服务调用方式"><a href="#2-服务调用方式" class="headerlink" title="2.服务调用方式"></a>2.服务调用方式</h1><h2 id="2-1-RPC和HTTP"><a href="#2-1-RPC和HTTP" class="headerlink" title="2.1.RPC和HTTP"></a>2.1.RPC和HTTP</h2><p>无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?</p><p>常见的远程调用方式有以下2种:</p><ul><li><p>RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表</p></li><li><p>Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。</p><p>现在热门的Rest风格,就可以通过http协议来实现。</p></li></ul><p>如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。</p><p>相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。</p><h2 id="2-2-Http客户端工具"><a href="#2-2-Http客户端工具" class="headerlink" title="2.2.Http客户端工具"></a>2.2.Http客户端工具</h2><p>既然微服务选择了Http,那么我们就需要考虑自己来实现对请求和响应的处理。不过开源世界已经有很多的http客户端工具,能够帮助我们做这些事情,例如:</p><ul><li>HttpClient</li><li>OKHttp</li><li>URLConnection</li></ul><p>接下来,不过这些不同的客户端,API各不相同</p><h2 id="2-3-Spring的RestTemplate"><a href="#2-3-Spring的RestTemplate" class="headerlink" title="2.3.Spring的RestTemplate"></a>2.3.Spring的RestTemplate</h2><p>Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:</p><ul><li>HttpClient</li><li>OkHttp</li><li>JDK原生的URLConnection(默认的)</li></ul><p>我们导入课前资料提供的demo工程:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1534812923451.png" alt="1534812923451"></p><p>首先在项目中注册一个<code>RestTemplate</code>对象,可以在启动类位置注册:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpDemoApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">SpringApplication.run(HttpDemoApplication.class, args);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span> </span>{</span><br><span class="line"> </span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在测试类中直接<code>@Autowired</code>注入:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner.class)</span><br><span class="line"><span class="meta">@SpringBootTest</span>(classes = HttpDemoApplication.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpDemoApplicationTests</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">httpGet</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 调用springboot案例中的rest接口</span></span><br><span class="line">User user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://localhost/user/1"</span>, User.class);</span><br><span class="line">System.out.println(user);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li>通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。</li></ul><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525573702492.png" alt="1525573702492"></p><p>学习完了Http客户端工具,接下来就可以正式学习微服务了。</p><h1 id="3-初识SpringCloud"><a href="#3-初识SpringCloud" class="headerlink" title="3.初识SpringCloud"></a>3.初识SpringCloud</h1><p>微服务是一种架构方式,最终肯定需要技术架构去实施。</p><p>微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?</p><ul><li>后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。</li><li>技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了</li><li>群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。</li><li>使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建</li></ul><h2 id="3-1-简介"><a href="#3-1-简介" class="headerlink" title="3.1.简介"></a>3.1.简介</h2><p>SpringCloud是Spring旗下的项目之一,<a href="http://projects.spring.io/spring-cloud/" target="_blank" rel="noopener">官网地址:http://projects.spring.io/spring-cloud/</a></p><p>Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。</p><p>SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:</p><ul><li>Eureka:服务治理组件,包含服务注册中心,服务注册与发现机制的实现。(服务治理,服务注册/发现) </li><li>Zuul:网关组件,提供智能路由,访问过滤功能 </li><li>Ribbon:客户端负载均衡的服务调用组件(客户端负载) </li><li>Feign:服务调用,给予Ribbon和Hystrix的声明式服务调用组件 (声明式服务调用) </li><li>Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。(熔断、断路器,容错) </li></ul><p>架构图:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525575656796.png" alt="1525575656796"></p><p>以上只是其中一部分。</p><h2 id="3-2-版本"><a href="#3-2-版本" class="headerlink" title="3.2.版本"></a>3.2.版本</h2><p>因为Spring Cloud不同其他独立项目,它拥有很多子项目的大项目。所以它的版本是版本名+版本号 (如Angel.SR6)。 </p><p>版本名:是伦敦的地铁名 </p><p>版本号:SR(Service Releases)是固定的 ,大概意思是稳定版本。后面会有一个递增的数字。 </p><p>所以 Edgware.SR3就是Edgware的第3个Release版本。 </p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528263985902.png" alt="1528263985902"></p><p>我们在项目中,会是以Finchley的版本。</p><p>其中包含的组件,也都有各自的版本,如下表:</p><div class="table-container"><table><thead><tr><th>Component</th><th>Edgware.SR3</th><th>Finchley.RC1</th><th>Finchley.BUILD-SNAPSHOT</th></tr></thead><tbody><tr><td>spring-cloud-aws</td><td>1.2.2.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-bus</td><td>1.3.2.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-cli</td><td>1.4.1.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-commons</td><td>1.3.3.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-contract</td><td>1.2.4.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-config</td><td>1.4.3.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-netflix</td><td>1.4.4.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-security</td><td>1.2.2.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-cloudfoundry</td><td>1.1.1.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-consul</td><td>1.3.3.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-sleuth</td><td>1.3.3.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-stream</td><td>Ditmars.SR3</td><td>Elmhurst.RELEASE</td><td>Elmhurst.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-zookeeper</td><td>1.2.1.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-boot</td><td>1.5.10.RELEASE</td><td>2.0.1.RELEASE</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-task</td><td>1.2.2.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.RELEASE</td></tr><tr><td>spring-cloud-vault</td><td>1.1.0.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-gateway</td><td>1.0.1.RELEASE</td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr><tr><td>spring-cloud-openfeign</td><td></td><td>2.0.0.RC1</td><td>2.0.0.BUILD-SNAPSHOT</td></tr></tbody></table></div><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528263942152.png" alt="1528263942152"></p><p>接下来,我们就一一学习SpringCloud中的重要组件。</p><h1 id="4-微服务场景模拟"><a href="#4-微服务场景模拟" class="headerlink" title="4.微服务场景模拟"></a>4.微服务场景模拟</h1><p>首先,我们需要模拟一个服务调用的场景,搭建两个工程:itcast-service-provider(服务提供方)和itcast-service-consumer(服务调用方)。方便后面学习微服务架构</p><p>服务提供方:使用mybatis操作数据库,实现对数据的增删改查;并对外提供rest接口服务。</p><p>服务消费方:使用restTemplate远程调用服务提供方的rest接口服务,获取数据。</p><h2 id="4-1-服务提供者"><a href="#4-1-服务提供者" class="headerlink" title="4.1.服务提供者"></a>4.1.服务提供者</h2><p>我们新建一个项目:itcast-service-provider,对外提供根据id查询用户的服务。</p><h3 id="4-1-1-Spring脚手架创建工程"><a href="#4-1-1-Spring脚手架创建工程" class="headerlink" title="4.1.1.Spring脚手架创建工程"></a>4.1.1.Spring脚手架创建工程</h3><p>借助于Spring提供的快速搭建工具:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528266515225.png" alt="1528266515225"></p><p>next—>填写项目信息:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528266803455.png" alt="1528266803455"></p><p>next —> 添加web依赖:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528266923967.png" alt="1528266923967"></p><p>添加mybatis依赖:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528267588935.png" alt="1528267588935"></p><p>Next —> 填写项目位置:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528267665004.png" alt="1528267665004"></p><p>生成的项目结构,已经包含了引导类(itcastServiceProviderApplication):</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528267830854.png" alt="1528267830854"></p><p>依赖也已经全部自动引入:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.service<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-service-provider<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">packaging</span>></span>jar<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>itcast-service-provider<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.build.sourceEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.build.sourceEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.reporting.outputEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.reporting.outputEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-jdbc<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.mybatis.spring.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>runtime<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="comment"><!-- 需要手动引入通用mapper的启动器,spring没有收录该依赖 --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>tk.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mapper-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><p>当然,因为要使用通用mapper,所以我们<strong>需要手动加一条依赖</strong>:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>tk.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mapper-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>非常快捷啊!</p><h3 id="4-1-2-编写代码"><a href="#4-1-2-编写代码" class="headerlink" title="4.1.2.编写代码"></a>4.1.2.编写代码</h3><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1535671376231.png" alt="1535671376231"></p><h4 id="4-1-2-1-配置"><a href="#4-1-2-1-配置" class="headerlink" title="4.1.2.1.配置"></a>4.1.2.1.配置</h4><p>属性文件,这里我们采用了yaml语法,而不是properties:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">8081</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> datasource:</span></span><br><span class="line"><span class="attr"> url:</span> <span class="attr">jdbc:mysql://localhost:3306/mybatis</span> <span class="comment">#你学习mybatis时,使用的数据库地址</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">root</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">root</span></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line"><span class="attr"> type-aliases-package:</span> <span class="string">cn.itcast.service.pojo</span></span><br></pre></td></tr></table></figure></div><h4 id="4-1-2-2-实体类"><a href="#4-1-2-2-实体类" class="headerlink" title="4.1.2.2.实体类"></a>4.1.2.2.实体类</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Table</span>(name = <span class="string">"tb_user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Id</span></span><br><span class="line"> <span class="meta">@GeneratedValue</span>(strategy = GenerationType.IDENTITY)</span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 用户名</span></span><br><span class="line"> <span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 密码</span></span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 姓名</span></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 年龄</span></span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 性别,1男性,2女性</span></span><br><span class="line"> <span class="keyword">private</span> Integer sex;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出生日期</span></span><br><span class="line"> <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建时间</span></span><br><span class="line"> <span class="keyword">private</span> Date created;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 更新时间</span></span><br><span class="line"> <span class="keyword">private</span> Date updated;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Long <span class="title">getId</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> id;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setId</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.id = id;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getUserName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> userName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUserName</span><span class="params">(String userName)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.userName = userName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getPassword</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> password;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setPassword</span><span class="params">(String password)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.password = password;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Integer <span class="title">getAge</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAge</span><span class="params">(Integer age)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.age = age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Integer <span class="title">getSex</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> sex;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSex</span><span class="params">(Integer sex)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.sex = sex;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getBirthday</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> birthday;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBirthday</span><span class="params">(Date birthday)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.birthday = birthday;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getCreated</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> created;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCreated</span><span class="params">(Date created)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.created = created;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getUpdated</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> updated;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUpdated</span><span class="params">(Date updated)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.updated = updated;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h4 id="4-1-2-3-UserMapper"><a href="#4-1-2-3-UserMapper" class="headerlink" title="4.1.2.3.UserMapper"></a>4.1.2.3.UserMapper</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> <span class="keyword">extends</span> <span class="title">tk</span>.<span class="title">mybatis</span>.<span class="title">mapper</span>.<span class="title">common</span>.<span class="title">Mapper</span><<span class="title">User</span>></span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h4 id="4-1-2-4-UserService"><a href="#4-1-2-4-UserService" class="headerlink" title="4.1.2.4.UserService"></a>4.1.2.4.UserService</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryById</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.userMapper.selectByPrimaryKey(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h4 id="4-1-2-5-UserController"><a href="#4-1-2-5-UserController" class="headerlink" title="4.1.2.5.UserController"></a>4.1.2.5.UserController</h4><p>添加一个对外查询的接口:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"{id}"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.userService.queryById(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="4-1-3-启动并测试"><a href="#4-1-3-启动并测试" class="headerlink" title="4.1.3.启动并测试"></a>4.1.3.启动并测试</h3><p>启动项目,访问接口:<a href="http://localhost:8081/user/1" target="_blank" rel="noopener">http://localhost:8081/user/1</a></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528272537927.png" alt="1528272537927"></p><h2 id="4-2-服务调用者"><a href="#4-2-服务调用者" class="headerlink" title="4.2.服务调用者"></a>4.2.服务调用者</h2><p>搭建itcast-service-consumer服务消费方工程。</p><h3 id="4-2-1-创建工程"><a href="#4-2-1-创建工程" class="headerlink" title="4.2.1.创建工程"></a>4.2.1.创建工程</h3><p>与上面类似,这里不再赘述,需要注意的是,我们调用itcast-service-provider的解耦获取数据,因此不需要mybatis相关依赖了。</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528272829384.png" alt="1528272829384"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528272875502.png" alt="1528272875502"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528273029361.png" alt="1528273029361"></p><p>pom:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.service<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-service-consumer<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">packaging</span>></span>jar<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>itcast-service-consumer<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.4.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.build.sourceEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.build.sourceEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.reporting.outputEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.reporting.outputEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="4-2-2-编写代码"><a href="#4-2-2-编写代码" class="headerlink" title="4.2.2.编写代码"></a>4.2.2.编写代码</h3><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1540643087305.png" alt="1540643087305"></p><p>首先在引导类中注册<code>RestTemplate</code>:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastServiceConsumerApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastServiceConsumerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>编写配置(application.yml):</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line"> port: 80</span><br></pre></td></tr></table></figure></div><p>编写UserController:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id)</span>{</span><br><span class="line"> User user = <span class="keyword">this</span>.restTemplate.getForObject(<span class="string">"http://localhost:8081/user/"</span> + id, User.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>pojo对象(User):</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 用户名</span></span><br><span class="line"> <span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 密码</span></span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 姓名</span></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 年龄</span></span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 性别,1男性,2女性</span></span><br><span class="line"> <span class="keyword">private</span> Integer sex;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 出生日期</span></span><br><span class="line"> <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建时间</span></span><br><span class="line"> <span class="keyword">private</span> Date created;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 更新时间</span></span><br><span class="line"> <span class="keyword">private</span> Date updated;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Long <span class="title">getId</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> id;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setId</span><span class="params">(Long id)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.id = id;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getUserName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> userName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUserName</span><span class="params">(String userName)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.userName = userName;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getPassword</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> password;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setPassword</span><span class="params">(String password)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.password = password;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Integer <span class="title">getAge</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAge</span><span class="params">(Integer age)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.age = age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Integer <span class="title">getSex</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> sex;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSex</span><span class="params">(Integer sex)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.sex = sex;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getBirthday</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> birthday;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBirthday</span><span class="params">(Date birthday)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.birthday = birthday;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getCreated</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> created;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCreated</span><span class="params">(Date created)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.created = created;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Date <span class="title">getUpdated</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> updated;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUpdated</span><span class="params">(Date updated)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.updated = updated;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="4-2-3-启动测试"><a href="#4-2-3-启动测试" class="headerlink" title="4.2.3.启动测试"></a>4.2.3.启动测试</h3><p>因为我们没有配置端口,那么默认就是8080,我们访问:<a href="http://localhost/consumer/user?id=1" target="_blank" rel="noopener">http://localhost/consumer/user?id=1</a></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1535587919022.png" alt="1535587919022"></p><p>一个简单的远程服务调用案例就实现了。</p><h2 id="4-3-有没有问题?"><a href="#4-3-有没有问题?" class="headerlink" title="4.3.有没有问题?"></a>4.3.有没有问题?</h2><p>简单回顾一下,刚才我们写了什么:</p><ul><li>itcast-service-provider:一个提供根据id查询用户的微服务。</li><li>itcast-service-consumer:一个服务调用者,通过RestTemplate远程调用itcast-service-provider。</li></ul><p>存在什么问题?</p><ul><li>在consumer中,我们把url地址硬编码到了代码中,不方便后期维护</li><li>consumer需要记忆provider的地址,如果出现变更,可能得不到通知,地址将失效</li><li>consumer不清楚provider的状态,服务宕机也不知道</li><li>provider只有1台服务,不具备高可用性</li><li>即便provider形成集群,consumer还需自己实现负载均衡</li></ul><p>其实上面说的问题,概括一下就是分布式服务必然要面临的问题:</p><ul><li>服务管理<ul><li>如何自动注册和发现</li><li>如何实现状态监管</li><li>如何实现动态路由</li></ul></li><li>服务如何实现负载均衡</li><li>服务如何解决容灾问题</li><li>服务如何实现统一配置</li></ul><p>以上的问题,我们都将在SpringCloud中得到答案。</p><h1 id="5-Eureka注册中心"><a href="#5-Eureka注册中心" class="headerlink" title="5.Eureka注册中心"></a>5.Eureka注册中心</h1><h2 id="5-1-认识Eureka"><a href="#5-1-认识Eureka" class="headerlink" title="5.1.认识Eureka"></a>5.1.认识Eureka</h2><p>首先我们来解决第一问题,服务的管理。</p><blockquote><p>问题分析</p></blockquote><p>在刚才的案例中,itcast-service-provider对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。</p><blockquote><p>网约车</p></blockquote><p>这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。</p><p>此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。</p><p>此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!</p><blockquote><p>Eureka做什么?</p></blockquote><p>Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。</p><p>同时,服务提供方与Eureka之间通过<code>“心跳”</code>机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。</p><p>这就实现了服务的自动注册、发现、状态监控。</p><h2 id="5-2-原理图"><a href="#5-2-原理图" class="headerlink" title="5.2.原理图"></a>5.2.原理图</h2><blockquote><p>基本架构:</p></blockquote><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525597885059.png" alt="1525597885059"></p><ul><li>Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址</li><li>提供者:启动后向Eureka注册自己信息(地址,提供什么服务)</li><li>消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新</li><li>心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态</li></ul><h2 id="5-3-入门案例"><a href="#5-3-入门案例" class="headerlink" title="5.3.入门案例"></a>5.3.入门案例</h2><h3 id="5-3-1-搭建EurekaServer"><a href="#5-3-1-搭建EurekaServer" class="headerlink" title="5.3.1.搭建EurekaServer"></a>5.3.1.搭建EurekaServer</h3><p>接下来我们创建一个项目,启动一个EurekaServer:</p><p>依然使用spring提供的快速搭建工具:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528295484808.png" alt="1528295484808"></p><p>选择依赖:EurekaServer-服务注册中心依赖,Eureka Discovery-服务提供方和服务消费方。因为,对于eureka来说:服务提供方和服务消费方都属于客户端</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528295612501.png" alt="1528295612501"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528295690203.png" alt="1528295690203"></p><p>完整的Pom文件:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.eureka<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-eureka<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">packaging</span>></span>jar<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>itcast-eureka<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.build.sourceEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.build.sourceEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.reporting.outputEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.reporting.outputEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">spring-cloud.version</span>></span>Finchley.RC2<span class="tag"></<span class="name">spring-cloud.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-server<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><p>编写application.yml配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">10086</span> <span class="comment"># 端口</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">eureka-server</span> <span class="comment"># 应用名称,会在Eureka中显示</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span> <span class="comment"># EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:${server.port}/eureka</span></span><br></pre></td></tr></table></figure></div><p>修改引导类,在类上添加@EnableEurekaServer注解:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaServer</span> <span class="comment">// 声明当前springboot应用是一个eureka服务中心</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastEurekaApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastEurekaApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>启动服务,并访问:<a href="http://127.0.0.1:10086" target="_blank" rel="noopener">http://127.0.0.1:10086</a></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525604959508.png" alt="1525604959508"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1533793804268.png" alt="1533793804268"></p><h3 id="5-3-2-注册到Eureka"><a href="#5-3-2-注册到Eureka" class="headerlink" title="5.3.2.注册到Eureka"></a>5.3.2.注册到Eureka</h3><p>注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。</p><p>修改itcast-service-provider工程</p><ol><li>在pom.xml中,添加springcloud的相关依赖。</li><li>在application.yml中,添加springcloud的相关依赖。</li><li>在引导类上添加注解,把服务注入到eureka注册中心。</li></ol><p>具体操作</p><h4 id="5-3-2-1-pom-xml"><a href="#5-3-2-1-pom-xml" class="headerlink" title="5.3.2.1.pom.xml"></a>5.3.2.1.pom.xml</h4><p>参照itcast-eureka,先添加SpringCloud依赖:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- SpringCloud的依赖 --></span></span><br><span class="line"><span class="tag"><<span class="name">dependencyManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>Finchley.SR2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencyManagement</span>></span></span><br></pre></td></tr></table></figure></div><p>然后是Eureka客户端:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- Eureka客户端 --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>完整pom.xml:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.service<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-service-provider<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">packaging</span>></span>jar<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>itcast-service-provider<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.build.sourceEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.build.sourceEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.reporting.outputEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.reporting.outputEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-jdbc<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.mybatis.spring.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>runtime<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>tk.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mapper-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencyManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>Finchley.SR1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencyManagement</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><h4 id="5-3-2-2-application-yml"><a href="#5-3-2-2-application-yml" class="headerlink" title="5.3.2.2.application.yml"></a>5.3.2.2.application.yml</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">8081</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> datasource:</span></span><br><span class="line"><span class="attr"> url:</span> <span class="attr">jdbc:mysql://localhost:3306/heima</span></span><br><span class="line"><span class="attr"> username:</span> <span class="string">root</span></span><br><span class="line"><span class="attr"> password:</span> <span class="string">root</span></span><br><span class="line"><span class="attr"> driverClassName:</span> <span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">service-provider</span> <span class="comment"># 应用名称,注册到eureka后的服务名称</span></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line"><span class="attr"> type-aliases-package:</span> <span class="string">cn.itcast.service.pojo</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span> <span class="comment"># EurekaServer地址</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10086/eureka</span></span><br></pre></td></tr></table></figure></div><p>注意:</p><ul><li>这里我们添加了spring.application.name属性来指定应用名称,将来会作为应用的id使用。</li></ul><h4 id="5-3-2-3-引导类"><a href="#5-3-2-3-引导类" class="headerlink" title="5.3.2.3.引导类"></a>5.3.2.3.引导类</h4><p>在引导类上开启Eureka客户端功能</p><p>通过添加<code>@EnableDiscoveryClient</code>来开启Eureka客户端功能</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableDiscoveryClient</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastServiceProviderApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastServiceApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><blockquote><p>重启项目,访问<a href="http://127.0.0.1:10086" target="_blank" rel="noopener">Eureka监控页面</a>查看</p></blockquote><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1545178177947.png" alt="1545178177947"></p><p>我们发现service-provider服务已经注册成功了</p><h3 id="5-3-3-从Eureka获取服务"><a href="#5-3-3-从Eureka获取服务" class="headerlink" title="5.3.3.从Eureka获取服务"></a>5.3.3.从Eureka获取服务</h3><p>接下来我们修改itcast-service-consumer,尝试从EurekaServer获取服务。</p><p>方法与消费者类似,只需要在项目中添加EurekaClient依赖,就可以通过服务名称来获取信息了!</p><ol><li>pom.xml</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.service<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-service-consumer<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">packaging</span>></span>jar<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">name</span>></span>itcast-service-consumer<span class="tag"></<span class="name">name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>Demo project for Spring Boot<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">relativePath</span>/></span> <span class="comment"><!-- lookup parent from repository --></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.build.sourceEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.build.sourceEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">project.reporting.outputEncoding</span>></span>UTF-8<span class="tag"></<span class="name">project.reporting.outputEncoding</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="comment"><!-- Eureka客户端 --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- SpringCloud的依赖 --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencyManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>Finchley.SR2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencyManagement</span>></span></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><ol><li>修改配置</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">80</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">service-consumer</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://localhost:10086/eureka</span></span><br></pre></td></tr></table></figure></div><ol><li>在启动类开启Eureka客户端</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableDiscoveryClient</span> <span class="comment">// 开启Eureka客户端</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ItcastServiceConsumerApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(ItcastServiceConsumerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>修改UserController代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例:</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> DiscoveryClient discoveryClient; <span class="comment">// eureka客户端,可以获取到eureka中服务的信息</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id)</span>{</span><br><span class="line"> <span class="comment">// 根据服务名称,获取服务实例。有可能是集群,所以是service实例集合</span></span><br><span class="line"> List<ServiceInstance> instances = discoveryClient.getInstances(<span class="string">"service-provider"</span>);</span><br><span class="line"> <span class="comment">// 因为只有一个Service-provider。所以获取第一个实例</span></span><br><span class="line"> ServiceInstance instance = instances.get(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 获取ip和端口信息,拼接成服务地址</span></span><br><span class="line"> String baseUrl = <span class="string">"http://"</span> + instance.getHost() + <span class="string">":"</span> + instance.getPort() + <span class="string">"/user/"</span> + id;</span><br><span class="line"> User user = <span class="keyword">this</span>.restTemplate.getForObject(baseUrl, User.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>5)Debug跟踪运行:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528534110188.png" alt="1528534110188"></p><p>生成的URL:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528534148651.png" alt="1528534148651"></p><p>访问结果:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1535674665806.png" alt="1535674665806"></p><h2 id="5-4-Eureka详解"><a href="#5-4-Eureka详解" class="headerlink" title="5.4.Eureka详解"></a>5.4.Eureka详解</h2><p>接下来我们详细讲解Eureka的原理及配置。</p><h3 id="5-4-1-基础架构"><a href="#5-4-1-基础架构" class="headerlink" title="5.4.1.基础架构"></a>5.4.1.基础架构</h3><p>Eureka架构中的三个核心角色:</p><ul><li><p>服务注册中心</p><p>Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的itcast-eureka。</p></li><li><p>服务提供者</p><p>提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的itcast-service-provider。</p></li><li><p>服务消费者</p><p>消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的itcast-service-consumer。</p></li></ul><h3 id="5-4-2-高可用的Eureka-Server"><a href="#5-4-2-高可用的Eureka-Server" class="headerlink" title="5.4.2.高可用的Eureka Server"></a>5.4.2.高可用的Eureka Server</h3><p>Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。</p><blockquote><p>服务同步</p></blockquote><p>多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现<strong>数据同步</strong>。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。</p><blockquote><p>动手搭建高可用的EurekaServer</p></blockquote><p>我们假设要运行两个EurekaServer的集群,端口分别为:10086和10087。只需要把itcast-eureka启动两次即可。</p><p>1)启动第一个eurekaServer,我们修改原来的EurekaServer配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">10086</span> <span class="comment"># 端口</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">eureka-server</span> <span class="comment"># 应用名称,会在Eureka中显示</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span> <span class="comment"># 配置其他Eureka服务的地址,而不是自己,比如10087</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10087/eureka</span></span><br></pre></td></tr></table></figure></div><p>所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:</p><ul><li>把service-url的值改成了另外一台EurekaServer的地址,而不是自己</li></ul><p>启动报错,很正常。因为10087服务没有启动:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528691515859.png" alt="1528691515859"></p><p>2)启动第二个eurekaServer,再次修改itcast-eureka的配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">10087</span> <span class="comment"># 端口</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">eureka-server</span> <span class="comment"># 应用名称,会在Eureka中显示</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span> <span class="comment"># 配置其他Eureka服务的地址,而不是自己,比如10087</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10086/eureka</span></span><br></pre></td></tr></table></figure></div><p>注意:idea中一个应用不能启动两次,我们需要重新配置一个启动器:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528540668018.png" alt="1528540668018"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528691728618.png" alt="1528691728618"></p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528693484031.png" alt="1528693484031"></p><p>然后启动即可。</p><p>3)访问集群,测试:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528693648200.png" alt="1528693648200"></p><p>4)客户端注册服务到集群</p><p>因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span> <span class="comment"># EurekaServer地址,多个地址以','隔开</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka</span></span><br></pre></td></tr></table></figure></div><p>10086:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528693943950.png" alt="1528693943950"></p><p>10087:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528693979654.png" alt="1528693979654"></p><h3 id="5-4-3-服务提供者"><a href="#5-4-3-服务提供者" class="headerlink" title="5.4.3.服务提供者"></a>5.4.3.服务提供者</h3><p>服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。</p><blockquote><p>服务注册</p></blockquote><p>服务提供者在启动时,会检测配置属性中的:<code>eureka.client.register-with-eureka=true</code>参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。</p><ul><li>第一层Map的Key就是服务id,一般是配置中的<code>spring.application.name</code>属性</li><li>第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:<code>locahost:service-provider:8081</code></li><li>值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。</li></ul><blockquote><p>服务续约</p></blockquote><p>在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);</p><p>有两个重要参数可以修改服务续约的行为:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> instance:</span></span><br><span class="line"><span class="attr"> lease-expiration-duration-in-seconds:</span> <span class="number">90</span></span><br><span class="line"><span class="attr"> lease-renewal-interval-in-seconds:</span> <span class="number">30</span></span><br></pre></td></tr></table></figure></div><ul><li>lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒</li><li>lease-expiration-duration-in-seconds:服务失效时间,默认值90秒</li></ul><p>也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。</p><p>但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> instance:</span></span><br><span class="line"><span class="attr"> lease-expiration-duration-in-seconds:</span> <span class="number">10</span> <span class="comment"># 10秒即过期</span></span><br><span class="line"><span class="attr"> lease-renewal-interval-in-seconds:</span> <span class="number">5</span> <span class="comment"># 5秒一次心跳</span></span><br></pre></td></tr></table></figure></div><h3 id="5-4-4-服务消费者"><a href="#5-4-4-服务消费者" class="headerlink" title="5.4.4.服务消费者"></a>5.4.4.服务消费者</h3><blockquote><p>获取服务列表</p></blockquote><p>当服务消费者启动时,会检测<code>eureka.client.fetch-registry=true</code>参数的值,如果为true,则会拉取Eureka Server服务的列表只读备份,然后缓存在本地。并且<code>每隔30秒</code>会重新获取并更新数据。我们可以通过下面的参数来修改:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> registry-fetch-interval-seconds:</span> <span class="number">5</span></span><br></pre></td></tr></table></figure></div><p>生产环境中,我们不需要修改这个值。</p><p>但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。</p><h3 id="5-4-5-失效剔除和自我保护"><a href="#5-4-5-失效剔除和自我保护" class="headerlink" title="5.4.5.失效剔除和自我保护"></a>5.4.5.失效剔除和自我保护</h3><blockquote><p>服务下线</p></blockquote><p>当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。</p><blockquote><p>失效剔除</p></blockquote><p>有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。</p><p>可以通过<code>eureka.server.eviction-interval-timer-in-ms</code>参数对其进行修改,单位是毫秒,生产环境不要修改。</p><p>这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如:10秒</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528696142799.png" alt="1528696142799"></p><blockquote><p>自我保护</p></blockquote><p>我们关停一个服务,就会在Eureka面板看到一条警告:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525618396076.png" alt="1525618396076"></p><p>这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。</p><p>但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:(itcast-eureka)</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> server:</span></span><br><span class="line"><span class="attr"> enable-self-preservation:</span> <span class="literal">false</span> <span class="comment"># 关闭自我保护模式(缺省为打开)</span></span><br><span class="line"><span class="attr"> eviction-interval-timer-in-ms:</span> <span class="number">1000</span> <span class="comment"># 扫描失效服务的间隔时间(缺省为60*1000ms)</span></span><br></pre></td></tr></table></figure></div><h1 id="6-负载均衡Ribbon"><a href="#6-负载均衡Ribbon" class="headerlink" title="6.负载均衡Ribbon"></a>6.负载均衡Ribbon</h1><p>在刚才的案例中,我们启动了一个itcast-service-provider,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。</p><p>但是实际环境中,我们往往会开启很多个itcast-service-provider的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?</p><p>一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。</p><p>不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。</p><p>什么是Ribbon:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525619257397.png" alt="1525619257397"></p><p>接下来,我们就来使用Ribbon实现负载均衡。</p><h2 id="6-1-启动两个服务实例"><a href="#6-1-启动两个服务实例" class="headerlink" title="6.1.启动两个服务实例"></a>6.1.启动两个服务实例</h2><p>首先参照itcast-eureka启动两个ItcastServiceProviderApplication实例,一个8081,一个8082。</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1540644966386.png" alt="1540644966386"></p><p>Eureka监控面板:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1540645032363.png" alt="1540645032363"></p><h2 id="6-2-开启负载均衡"><a href="#6-2-开启负载均衡" class="headerlink" title="6.2.开启负载均衡"></a>6.2.开启负载均衡</h2><p>因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码。</p><p>修改itcast-service-consumer的引导类,在RestTemplate的配置方法上添加<code>@LoadBalanced</code>注解:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@LoadBalanced</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"consumer/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//@Autowired</span></span><br><span class="line"> <span class="comment">//private DiscoveryClient discoveryClient; // 注入discoveryClient,通过该客户端获取服务列表</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@RequestParam(<span class="string">"id"</span>)</span> Long id)</span>{</span><br><span class="line"> <span class="comment">// 通过client获取服务提供方的服务列表,这里我们只有一个</span></span><br><span class="line"> <span class="comment">// ServiceInstance instance = discoveryClient.getInstances("service-provider").get(0);</span></span><br><span class="line"> String baseUrl = <span class="string">"http://service-provider/user/"</span> + id;</span><br><span class="line"> User user = <span class="keyword">this</span>.restTemplate.getForObject(baseUrl, User.class);</span><br><span class="line"> <span class="keyword">return</span> user;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>访问页面,查看结果:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1535674665806.png" alt="1535674665806"></p><p>完美!</p><h2 id="6-3-源码跟踪"><a href="#6-3-源码跟踪" class="headerlink" title="6.3.源码跟踪"></a>6.3.源码跟踪</h2><p>为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。</p><p>显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是<code>LoadBalancerInterceptor</code></p><p>在如下代码打断点:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528774637934.png" alt="1528774637934"></p><p>一路源码跟踪:RestTemplate.getForObject —> RestTemplate.execute —> RestTemplate.doExecute:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528776129378.png" alt="1528776129378"></p><p>点击进入AbstractClientHttpRequest.execute —> AbstractBufferingClientHttpRequest.executeInternal —> InterceptingClientHttpRequest.executeInternal —> InterceptingClientHttpRequest.execute:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528776489965.png" alt="1528776489965"></p><p>继续跟入:LoadBalancerInterceptor.intercept方法</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528775270103.png" alt="1528775270103"></p><p>继续跟入execute方法:发现获取了8082端口的服务</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528775890956.png" alt="1528775890956"></p><p>再跟下一次,发现获取的是8081:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528775845812.png" alt="1528775845812"></p><h2 id="6-4-负载均衡策略"><a href="#6-4-负载均衡策略" class="headerlink" title="6.4.负载均衡策略"></a>6.4.负载均衡策略</h2><p>Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:</p><p>编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,找到choose方法的接口方法,是这样介绍的:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525622320277.png" alt="1525622320277"></p><p>现在这个就是负载均衡获取实例的方法。</p><p>我们注入这个类的对象,然后对其测试:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528780835917.png" alt="1528780835917"></p><p>测试内容:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner.class)</span><br><span class="line"><span class="meta">@SpringBootTest</span>(classes = ItcastServiceConsumerApplication.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoadBalanceTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RibbonLoadBalancerClient client;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testLoadBalance</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> ServiceInstance instance = <span class="keyword">this</span>.client.choose(<span class="string">"service-provider"</span>);</span><br><span class="line"> System.out.println(instance.getHost() + <span class="string">":"</span> +instance.getPort());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>结果:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1535338345659.png" alt="1535338345659"></p><p>符合了我们的预期推测,确实是轮询方式。</p><p>我们是否可以修改负载均衡的策略呢?</p><p>继续跟踪源码,发现这么一段代码:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525622652849.png" alt="1525622652849"></p><p>我们看看这个rule是谁:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525622699666.png" alt="1525622699666"></p><p>这里的rule默认值是一个<code>RoundRobinRule</code>,看类的介绍:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525622754316.png" alt="1525622754316"></p><p>这不就是轮询的意思嘛。</p><p>我们注意到,这个类其实是实现了接口IRule的,查看一下:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1525622817451.png" alt="1525622817451"></p><p>定义负载均衡的规则接口。</p><p>它有以下实现:</p><p> <img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528782624098.png" alt="1528782624098"></p><p>SpringBoot也帮我们提供了修改负载均衡规则的配置入口,在itcast-service-consumer的application.yml中添加如下配置:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="YAML"><figure class="iseeu highlight /yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr"> port:</span> <span class="number">80</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr"> application:</span></span><br><span class="line"><span class="attr"> name:</span> <span class="string">service-consumer</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr"> client:</span></span><br><span class="line"><span class="attr"> service-url:</span></span><br><span class="line"><span class="attr"> defaultZone:</span> <span class="attr">http://127.0.0.1:10086/eureka</span></span><br><span class="line"><span class="attr">service-provider:</span></span><br><span class="line"><span class="attr"> ribbon:</span></span><br><span class="line"><span class="attr"> NFLoadBalancerRuleClassName:</span> <span class="string">com.netflix.loadbalancer.RandomRule</span></span><br></pre></td></tr></table></figure></div><p>格式是:<code>{服务名称}.ribbon.NFLoadBalancerRuleClassName</code>,值就是IRule的实现类。</p><p>再次测试,发现结果变成了随机:</p><p><img src="/2019/12/11/Spring Cloud快速开发入门第01篇/1528782514987.png" alt="1528782514987"></p>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>了解系统架构的演变</li>
<li>了解RPC与Http的区别</li>
<li>知道什么是S
</summary>
<category term="Spring Cloud" scheme="http://enfangzhong.github.io/categories/Spring-Cloud/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/categories/Spring-Cloud/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Cloud" scheme="http://enfangzhong.github.io/tags/Spring-Cloud/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
</entry>
<entry>
<title>Spring Boot快速开发入门</title>
<link href="http://enfangzhong.github.io/2019/12/11/Spring%20Boot%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/"/>
<id>http://enfangzhong.github.io/2019/12/11/Spring Boot快速开发入门/</id>
<published>2019-12-11T03:03:11.000Z</published>
<updated>2019-12-18T06:18:41.456Z</updated>
<content type="html"><![CDATA[<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>了解SpringBoot的作用</li><li>掌握java配置的方式</li><li>了解SpringBoot自动配置原理</li><li>掌握SpringBoot的基本使用</li><li>了解Thymeleaf的基本使用</li></ul><h1 id="1-了解SpringBoot"><a href="#1-了解SpringBoot" class="headerlink" title="1. 了解SpringBoot"></a>1. 了解SpringBoot</h1><p>在这一部分,我们主要了解以下3个问题:</p><ul><li>什么是SpringBoot</li><li>为什么要学习SpringBoot</li><li>SpringBoot的特点</li></ul><h2 id="1-1-什么是SpringBoot"><a href="#1-1-什么是SpringBoot" class="headerlink" title="1.1.什么是SpringBoot"></a>1.1.什么是SpringBoot</h2><p>SpringBoot是Spring项目中的一个子工程,与我们所熟知的Spring-framework 同属于spring的产品:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527681455097.png" alt="1527681455097"></p><p>我们可以看到下面的一段介绍:</p><blockquote><p>Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.</p><p>We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.</p></blockquote><p>翻译一下:</p><blockquote><p>Spring Boot你只需要“run”就可以非常轻易的构建独立的、生产级别的spring应用。</p><p>我们为spring平台和第三方依赖库提供了一种固定化的使用方式,使你能非常轻松的开始开发你的应用程序。大部分Spring Boot应用只需要很少的配置。</p></blockquote><p>其实人们把Spring Boot称为搭建程序的<code>脚手架</code>。其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注于业务而非配置。</p><p>我们可以使用SpringBoot创建java应用,并使用java –jar 启动它,就能得到一个生产级别的web工程。</p><h2 id="1-2-为什么要学习SpringBoot"><a href="#1-2-为什么要学习SpringBoot" class="headerlink" title="1.2.为什么要学习SpringBoot"></a>1.2.为什么要学习SpringBoot</h2><p>java一直被人诟病的一点就是臃肿、麻烦。当我们还在辛苦的搭建项目时,可能Python程序员已经把功能写好了,究其原因主要是两点:</p><ul><li><p>复杂的配置</p><p>项目各种配置其实是开发时的损耗, 因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。</p></li><li><p>混乱的依赖管理</p><p>项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这也是件棘手的问题。并且,依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。</p></li></ul><p>而SpringBoot让这一切成为过去!</p><h2 id="1-3-SpringBoot的特点"><a href="#1-3-SpringBoot的特点" class="headerlink" title="1.3.SpringBoot的特点"></a>1.3.SpringBoot的特点</h2><p>Spring Boot 主要特征是:</p><ul><li>创建独立的spring应用程序</li><li>直接内嵌tomcat、jetty和undertow(不需要打包成war包部署)</li><li>提供了固定化的“starter”配置,以简化构建配置</li><li>尽可能的自动配置spring和第三方库</li><li>提供产品级的功能,如:安全指标、运行状况监测和外部化配置等</li><li>绝对不会生成代码,并且不需要XML配置</li></ul><p>总之,Spring Boot为所有 Spring 的开发者提供一个开箱即用的、非常快速的、广泛接受的入门体验</p><p>更多细节,大家可以到<a href="http://projects.spring.io/spring-boot/" target="_blank" rel="noopener">官网</a>查看。</p><h1 id="2-快速入门"><a href="#2-快速入门" class="headerlink" title="2.快速入门"></a>2.快速入门</h1><p>接下来,我们就来利用SpringBoot搭建一个web工程,体会一下SpringBoot的魅力所在!</p><p>环境要求:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527695636167.png" alt="1527695636167"></p><h2 id="2-1-创建工程"><a href="#2-1-创建工程" class="headerlink" title="2.1.创建工程"></a>2.1.创建工程</h2><p>我们先新建一个空的demo工程,如下:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527690475282.png" alt="1527690475282"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527690564245.png" alt="1527690564245"></p><p>创建以moduel:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527690810779.png" alt="1527690810779"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527694194289.png" alt="1527694194289"></p><p>填写坐标信息:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527694371964.png" alt="1527694371964"></p><p>目录结构:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527694504570.png" alt="1527694504570"></p><p>创建完成后的目录结构:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527694634778.png" alt="1527694634778"></p><h2 id="2-2-引入依赖"><a href="#2-2-引入依赖" class="headerlink" title="2.2.引入依赖"></a>2.2.引入依赖</h2><p>看到这里很多同学会有疑惑,前面说传统开发的问题之一就是依赖管理混乱,怎么这里我们还需要管理依赖呢?难道SpringBoot不帮我们管理吗?</p><p>别着急,现在我们的项目与SpringBoot还没有什么关联。SpringBoot提供了一个名为spring-boot-starter-parent的工程,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标即可!</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527751740943.png" alt="1527751740943"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.springboot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-springboot<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.0-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 所有的springboot的工程都以spring父工程为父工程 --></span></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><h2 id="2-3-编写HelloController"><a href="#2-3-编写HelloController" class="headerlink" title="2.3.编写HelloController"></a>2.3.编写HelloController</h2><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540894662227.png" alt="1540894662227"></p><p>代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"show"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello Spring Boot!"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(HelloController.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="2-4-启动测试"><a href="#2-4-启动测试" class="headerlink" title="2.4.启动测试"></a>2.4.启动测试</h2><p><img src="/2019/12/11/Spring Boot快速开发入门/1527755353246.png" alt="1527755353246"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527755521637.png" alt="1527755521637"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527755975973.png" alt="1527755975973"></p><p>bingo!访问成功!</p><h2 id="2-5-详解"><a href="#2-5-详解" class="headerlink" title="2.5.详解"></a>2.5.详解</h2><p>入门工程中:pom.xml里引入了启动器的概念以@EnableAutoConfiguration注解。</p><h3 id="2-5-1-启动器"><a href="#2-5-1-启动器" class="headerlink" title="2.5.1.启动器"></a>2.5.1.启动器</h3><p>为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为<code>启动器</code>。spring-boot-starter-parent工程将依赖关系声明为一个或者多个启动器,我们可以根据项目需求引入相应的启动器,因为我们是web项目,这里我们引入web启动器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencies</span>></span></span><br></pre></td></tr></table></figure></div><p>需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。</p><p>这个时候,我们会发现项目中多出了大量的依赖:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540894098029.png" alt="1540894098029"></p><p>这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。</p><h3 id="2-5-2-EnableAutoConfiguration"><a href="#2-5-2-EnableAutoConfiguration" class="headerlink" title="2.5.2.@EnableAutoConfiguration"></a>2.5.2.@EnableAutoConfiguration</h3><p>关于这个注解,官网上有一段说明:</p><blockquote><p>Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.</p></blockquote><p>简单翻译以下:</p><blockquote><p>开启spring应用程序的自动配置,SpringBoot基于你所添加的依赖和你自己定义的bean,试图去猜测并配置你想要的配置。比如我们引入了<code>spring-boot-starter-web</code>,而这个启动器中帮我们添加了<code>tomcat</code>、<code>SpringMVC</code>的依赖。此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!</p></blockquote><p>总结,SpringBoot内部对大量的第三方库或Spring内部库进行了默认配置,这些配置是否生效,取决于我们是否引入了对应库所需的依赖,如果有那么默认配置就会生效。</p><p>所以,我们使用SpringBoot构建一个项目,只需要引入所需依赖,配置就可以交给SpringBoot处理了。</p><h2 id="2-6-优化入门程序"><a href="#2-6-优化入门程序" class="headerlink" title="2.6.优化入门程序"></a>2.6.优化入门程序</h2><p>现在工程中只有一个Controller,可以这么玩;那么如果有多个Controller,怎么办呢?</p><p>添加Hello2Controller:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527949030771.png" alt="1527949030771"></p><p>代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Hello2Controller</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"show2"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello Spring Boot2!"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>启动重新启动,访问show2测试,失败:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528087951866.png" alt="1528087951866"></p><p>难道要在每一个Controller中都添加一个main方法和@EnableAutoConfiguration注解,这样启动一个springboot程序也太麻烦了。也无法同时启动多个Controller,因为每个main方法都监听8080端口。所以,一个springboot程序应该只有一个springboot的main方法。</p><p>所以,springboot程序引入了一个全局的引导类。</p><h3 id="2-5-1-添加引导类"><a href="#2-5-1-添加引导类" class="headerlink" title="2.5.1.添加引导类"></a>2.5.1.添加引导类</h3><p>通常请求下,我们在一个springboot工程中都会在基包下创建一个引导类,一些springboot的全局注解(@EnableAutoConfiguration注解)以及springboot程序的入口main方法都放在该类中。</p><p>在springboot的程序的基包下(引导类和Controller包在同级目录下),创建TestApplication.class:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527760765673.png" alt="1527760765673"></p><p>内容如下:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(TestApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>并修改HelloController,去掉main方法及@EnableAutoConfiguration:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"show"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello Spring Boot!"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>启动引导类,访问show测试:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528088990216.png" alt="1528088990216"></p><p>发现所有的Controller都不能访问,为什么?</p><p>回想以前程序,我们在配置文件中添加了注解扫描,它能扫描指定包下的所有Controller,而现在并没有。怎么解决——@ComponentScan注解</p><h3 id="2-5-2-ComponentScan"><a href="#2-5-2-ComponentScan" class="headerlink" title="2.5.2.@ComponentScan"></a>2.5.2.@ComponentScan</h3><p>spring框架除了提供配置方式的注解扫描<code><context:component-scan /></code>,还提供了注解方式的注解扫描<code>@ComponentScan</code>。</p><p>在TestApplication.class中,使用@ComponentScan注解:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(TestApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>重新启动,访问show或者show2:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528089792848.png" alt="1528089792848"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527772795552.png" alt="1527772795552"></p><p>我们跟进该注解的源码,并没有看到什么特殊的地方。我们查看注释:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527818066552.png" alt="1527818066552"></p><p>大概的意思:</p><blockquote><p>配置组件扫描的指令。提供了类似与<code><context:component-scan></code>标签的作用</p><p>通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包</p></blockquote><p>而我们的@ComponentScan注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。<strong>一般启动类会放在一个比较浅的包目录中。</strong></p><h3 id="2-5-3-SpringBootApplication"><a href="#2-5-3-SpringBootApplication" class="headerlink" title="2.5.3.@SpringBootApplication"></a>2.5.3.@SpringBootApplication</h3><p>我们现在的引导类中使用了@EnableAutoConfiguration和@ComponentScan注解,有点麻烦。springboot提供了一种简便的玩法:@SpringBootApplication注解</p><p>使用@SpringBootApplication改造TestApplication:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(TestApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>点击进入,查看源码:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528093556068.png" alt="1528093556068"></p><p>发现@SpringBootApplication其实是一个组合注解,这里重点的注解有3个:</p><ul><li>@SpringBootConfiguration</li><li>@EnableAutoConfiguration:开启自动配置</li><li>@ComponentScan:开启注解扫描</li></ul><h3 id="2-5-4-SpringBootConfiguration"><a href="#2-5-4-SpringBootConfiguration" class="headerlink" title="2.5.4.@SpringBootConfiguration"></a>2.5.4.@SpringBootConfiguration</h3><p>@SpringBootConfiguration注解的源码:</p><p>我们继续点击查看源码:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528095223949.png" alt="1528095223949"></p><p>通过这段我们可以看出,在这个注解上面,又有一个<code>@Configuration</code>注解。通过上面的注释阅读我们知道:这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了<code>@Configuration</code>的类,并且读取其中的配置信息。而<code>@SpringBootConfiguration</code>是来声明当前类是SpringBoot应用的配置类,项目中只能有一个。所以一般我们无需自己添加。</p><h1 id="3-默认配置原理"><a href="#3-默认配置原理" class="headerlink" title="3.默认配置原理"></a>3.默认配置原理</h1><p>springboot的默认配置方式和我们之前玩的配置方式不太一样,没有任何的xml。那么如果自己要新增配置该怎么办?比如我们要配置一个数据库连接池,以前会这么玩:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 配置连接池 --></span></span><br><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"dataSource"</span> <span class="attr">class</span>=<span class="string">"com.alibaba.druid.pool.DruidDataSource"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">init-method</span>=<span class="string">"init"</span> <span class="attr">destroy-method</span>=<span class="string">"close"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"${jdbc.url}"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"${jdbc.username}"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"${jdbc.password}"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">bean</span>></span></span><br></pre></td></tr></table></figure></div><p>现在该怎么做呢?</p><h2 id="3-1-回顾历史"><a href="#3-1-回顾历史" class="headerlink" title="3.1.回顾历史"></a>3.1.回顾历史</h2><p>事实上,在Spring3.0开始,Spring官方就已经开始推荐使用java配置来代替传统的xml配置了,我们不妨来回顾一下Spring的历史:</p><ul><li><p>Spring1.0时代</p><p>在此时因为jdk1.5刚刚出来,注解开发并未盛行,因此一切Spring配置都是xml格式,想象一下所有的bean都用xml配置,细思极恐啊,心疼那个时候的程序员2秒</p></li><li><p>Spring2.0时代</p><p>Spring引入了注解开发,但是因为并不完善,因此并未完全替代xml,此时的程序员往往是把xml与注解进行结合,貌似我们之前都是这种方式。</p></li><li><p>Spring3.0及以后</p><p>3.0以后Spring的注解已经非常完善了,因此Spring推荐大家使用完全的java配置来代替以前的xml,不过似乎在国内并未推广盛行。然后当SpringBoot来临,人们才慢慢认识到java配置的优雅。</p></li></ul><p>有句古话说的好:拥抱变化,拥抱未来。所以我们也应该顺应时代潮流,做时尚的弄潮儿,一起来学习下java配置的玩法。</p><h2 id="3-2-尝试java配置"><a href="#3-2-尝试java配置" class="headerlink" title="3.2.尝试java配置"></a>3.2.尝试java配置</h2><p>java配置主要靠java类和一些注解来达到和xml配置一样的效果,比较常用的注解有:</p><ul><li><code>@Configuration</code>:声明一个类作为配置类,代替xml文件</li><li><code>@Bean</code>:声明在方法上,将方法的返回值加入Bean容器,代替<code><bean></code>标签</li><li><code>@Value</code>:属性注入 </li><li><code>@PropertySource</code>:指定外部属性文件。</li></ul><p>我们接下来用java配置来尝试实现连接池配置</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540895699735.png" alt="1540895699735"></p><h3 id="3-2-1-引入依赖"><a href="#3-2-1-引入依赖" class="headerlink" title="3.2.1.引入依赖"></a>3.2.1.引入依赖</h3><p>首先在pom.xml中,引入Druid连接池依赖:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.github.drtrang<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>druid-spring-boot2-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.1.10<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><h3 id="3-2-2-添加jdbc-properties"><a href="#3-2-2-添加jdbc-properties" class="headerlink" title="3.2.2.添加jdbc.properties"></a>3.2.2.添加jdbc.properties</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">jdbc.driverClassName=com.mysql.jdbc.Driver</span><br><span class="line">jdbc.url=jdbc:mysql://127.0.0.1:3306/leyou</span><br><span class="line">jdbc.username=root</span><br><span class="line">jdbc.password=123</span><br></pre></td></tr></table></figure></div><h3 id="3-2-3-配置数据源"><a href="#3-2-3-配置数据源" class="headerlink" title="3.2.3.配置数据源"></a>3.2.3.配置数据源</h3><p>创建JdbcConfiguration类:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@PropertySource</span>(<span class="string">"classpath:jdbc.properties"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${jdbc.url}"</span>)</span><br><span class="line"> String url;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${jdbc.driverClassName}"</span>)</span><br><span class="line"> String driverClassName;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${jdbc.username}"</span>)</span><br><span class="line"> String username;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${jdbc.password}"</span>)</span><br><span class="line"> String password;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">dataSource</span><span class="params">()</span> </span>{</span><br><span class="line"> DruidDataSource dataSource = <span class="keyword">new</span> DruidDataSource();</span><br><span class="line"> dataSource.setUrl(url);</span><br><span class="line"> dataSource.setDriverClassName(driverClassName);</span><br><span class="line"> dataSource.setUsername(username);</span><br><span class="line"> dataSource.setPassword(password);</span><br><span class="line"> <span class="keyword">return</span> dataSource;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>解读:</p><ul><li><code>@Configuration</code>:声明<code>JdbcConfiguration</code>是一个配置类。</li><li><code>@PropertySource</code>:指定属性文件的路径是:<code>classpath:jdbc.properties</code></li><li>通过<code>@Value</code>为属性注入值。</li><li>通过@Bean将 <code>dataSource()</code>方法声明为一个注册Bean的方法,Spring会自动调用该方法,将方法的返回值加入Spring容器中。相当于以前的bean标签</li></ul><p>然后就可以在任意位置通过<code>@Autowired</code>注入DataSource了!</p><h3 id="3-2-4-测试"><a href="#3-2-4-测试" class="headerlink" title="3.2.4.测试"></a>3.2.4.测试</h3><p>我们在<code>HelloController</code>中测试:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> DataSource dataSource;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"show"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello Spring Boot!"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在test方法中打一个断点,然后Debug运行并查看:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528098961065.png" alt="1528098961065"></p><p>属性注入成功了!</p><h2 id="3-3-SpringBoot的属性注入"><a href="#3-3-SpringBoot的属性注入" class="headerlink" title="3.3.SpringBoot的属性注入"></a>3.3.SpringBoot的属性注入</h2><p>在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。</p><p>在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。</p><p>1)新建<code>JdbcProperties</code>,用来进行属性注入:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540895952978.png" alt="1540895952978"></p><p>代码:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"jdbc"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcProperties</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String url;</span><br><span class="line"> <span class="keyword">private</span> String driverClassName;</span><br><span class="line"> <span class="keyword">private</span> String username;</span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line"> <span class="comment">// ... 略</span></span><br><span class="line"> <span class="comment">// getters 和 setters</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li><p>在类上通过@ConfigurationProperties注解声明当前类为属性读取类</p></li><li><p><code>prefix="jdbc"</code>读取属性文件中,前缀为jdbc的值。</p></li><li><p>在类上定义各个属性,名称必须与属性文件中<code>jdbc.</code>后面部分一致,并且必须具有getter和setter方法</p></li><li><p>需要注意的是,这里我们并没有指定属性文件的地址,SpringBoot默认会读取文件名为application.properties的资源文件,所以我们<strong>把jdbc.properties名称改为application.properties</strong></p></li></ul><p>2)在JdbcConfiguration中使用这个属性:</p><ul><li><p>通过<code>@EnableConfigurationProperties(JdbcProperties.class)</code>来声明要使用<code>JdbcProperties</code>这个类的对象</p></li><li><p>然后你可以通过以下方式在JdbcConfiguration类中注入JdbcProperties:</p><ol><li>@Autowired注入</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties</span>(JdbcProperties.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> JdbcProperties jdbcProperties;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">dataSource</span><span class="params">()</span> </span>{</span><br><span class="line"> DruidDataSource dataSource = <span class="keyword">new</span> DruidDataSource();</span><br><span class="line"> dataSource.setUrl(jdbcProperties.getUrl());</span><br><span class="line"> dataSource.setDriverClassName(jdbcProperties.getDriverClassName());</span><br><span class="line"> dataSource.setUsername(jdbcProperties.getUsername());</span><br><span class="line"> dataSource.setPassword(jdbcProperties.getPassword());</span><br><span class="line"> <span class="keyword">return</span> dataSource;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>构造函数注入</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties</span>(JdbcProperties.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> JdbcProperties jdbcProperties;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">JdbcConfiguration</span><span class="params">(JdbcProperties jdbcProperties)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.jdbcProperties = jdbcProperties;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">dataSource</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 略</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>@Bean方法的参数注入</li></ol><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties</span>(JdbcProperties.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">dataSource</span><span class="params">(JdbcProperties jdbcProperties)</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div></li></ul><p>本例中,我们采用第三种方式。</p><p>3)测试结果:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527783292437.png" alt="1527783292437"></p><p>大家会觉得这种方式似乎更麻烦了,事实上这种方式有更强大的功能,也是SpringBoot推荐的注入方式。两者对比关系:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528103259908.png" alt="1528103259908"></p><p>优势:</p><ul><li><p>Relaxed binding:松散绑定</p><ul><li>不严格要求属性文件中的属性名与成员变量名一致。支持驼峰,中划线,下划线等等转换,甚至支持对象引导。比如:user.friend.name:代表的是user对象中的friend属性中的name属性,显然friend也是对象。@value注解就难以完成这样的注入方式。</li><li>meta-data support:元数据支持,帮助IDE生成属性提示(写开源框架会用到)。</li></ul></li></ul><h2 id="3-4-更优雅的注入"><a href="#3-4-更优雅的注入" class="headerlink" title="3.4.更优雅的注入"></a>3.4.更优雅的注入</h2><p>事实上,如果一段属性只有一个Bean需要使用,我们无需将其注入到一个类(JdbcProperties)中。而是直接在需要的地方声明即可:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcConfiguration</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="comment">// 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中</span></span><br><span class="line"> <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"jdbc"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">dataSource</span><span class="params">()</span> </span>{</span><br><span class="line"> DruidDataSource dataSource = <span class="keyword">new</span> DruidDataSource();</span><br><span class="line"> <span class="keyword">return</span> dataSource;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们直接把<code>@ConfigurationProperties(prefix = "jdbc")</code>声明在需要使用的<code>@Bean</code>的方法上,然后SpringBoot就会自动调用这个Bean(此处是DataSource)的set方法,然后完成注入。使用的前提是:<strong>该类必须有对应属性的set方法!</strong></p><p>我们将jdbc的url改成:/heima,再次测试:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528103369170.png" alt="1528103369170"></p><h2 id="3-5-SpringBoot中的默认配置"><a href="#3-5-SpringBoot中的默认配置" class="headerlink" title="3.5.SpringBoot中的默认配置"></a>3.5.SpringBoot中的默认配置</h2><p>通过刚才的学习,我们知道@EnableAutoConfiguration会开启SpringBoot的自动配置,并且根据你引入的依赖来生效对应的默认配置。那么问题来了:</p><ul><li>这些默认配置是怎么配置的,在哪里配置的呢?</li><li>为何依赖引入就会触发配置呢?</li><li>这些默认配置的属性来自哪里呢?</li></ul><p>其实在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527931944702.png" alt="1527931944702"></p><p>还有:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527931968031.png" alt="1527931968031"></p><p>非常多,几乎涵盖了现在主流的开源框架,例如:</p><ul><li>redis</li><li>jms</li><li>amqp</li><li>jdbc</li><li>jackson</li><li>mongodb</li><li>jpa</li><li>solr</li><li>elasticsearch</li></ul><p>… 等等</p><p>我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1527933162533.png" alt="1527933162533"></p><p>打开WebMvcAutoConfiguration:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527933633048.png" alt="1527933633048"></p><p>我们看到这个类上的4个注解:</p><ul><li><p><code>@Configuration</code>:声明这个类是一个配置类</p></li><li><p><code>@ConditionalOnWebApplication(type = Type.SERVLET)</code></p><p>ConditionalOn,翻译就是在某个条件下,此处就是满足项目的类是是Type.SERVLET类型,也就是一个普通web工程,显然我们就是</p></li><li><p><code>@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })</code></p><p>这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效!</p></li><li><p><code>@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)</code></p><p>这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的类,那么这个默认配置就会失效!</p></li></ul><p>接着,我们查看该类中定义了什么:</p><p>视图解析器:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527933646831.png" alt="1527933646831"></p><p>处理器适配器(HandlerAdapter):</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1527933659948.png" alt="1527933659948"></p><p>还有很多,这里就不一一截图了。</p><p>另外,这些默认配置的属性来自哪里呢?</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528096733440.png" alt="1528096733440"></p><p>我们看到,这里通过@EnableAutoConfiguration注解引入了两个属性:WebMvcProperties和ResourceProperties。</p><p>我们查看这两个属性类:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528096851318.png" alt="1528096851318"></p><p>找到了内部资源视图解析器的prefix和suffix属性。</p><p>ResourceProperties中主要定义了静态资源(.js,.html,.css等)的路径:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528096892588.png" alt="1528096892588"></p><p>如果我们要覆盖这些默认属性,只需要在application.properties中定义与其前缀prefix和字段名一致的属性即可。</p><h2 id="3-6-总结"><a href="#3-6-总结" class="headerlink" title="3.6.总结"></a>3.6.总结</h2><p>SpringBoot为我们提供了默认配置,而默认配置生效的条件一般有两个:</p><ul><li>你引入了相关依赖</li><li>你自己没有配置</li></ul><p>1)启动器</p><p>之所以,我们如果不想配置,只需要引入依赖即可,而依赖版本我们也不用操心,因为只要引入了SpringBoot提供的stater(启动器),就会自动管理依赖及版本了。</p><p>因此,玩SpringBoot的第一件事情,就是找启动器,SpringBoot提供了大量的默认启动器,参考课前资料中提供的《SpringBoot启动器.txt》</p><p>2)全局配置</p><p>另外,SpringBoot的默认配置,都会读取默认属性,而这些属性可以通过自定义<code>application.properties</code>文件来进行覆盖。这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。</p><p>因此,玩SpringBoot的第二件事情,就是通过<code>application.properties</code>来覆盖默认属性值,形成自定义配置。我们需要知道SpringBoot的默认属性key,非常多,参考课前资料提供的:《SpringBoot全局属性.md》</p><h1 id="4-SpringBoot实战"><a href="#4-SpringBoot实战" class="headerlink" title="4.SpringBoot实战"></a>4.SpringBoot实战</h1><p>接下来,我们来看看如何用SpringBoot来玩转以前的SSM,我们沿用之前讲解SSM用到的数据库tb_user和实体类User</p><h2 id="4-1-创建工程"><a href="#4-1-创建工程" class="headerlink" title="4.1.创建工程"></a>4.1.创建工程</h2><p><img src="/2019/12/11/Spring Boot快速开发入门/1540896476056.png" alt="1540896476056"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540896950344.png" alt="1540896950344"></p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540896657008.png" alt="1540896657008"></p><h2 id="4-2-编写基本代码"><a href="#4-2-编写基本代码" class="headerlink" title="4.2.编写基本代码"></a>4.2.编写基本代码</h2><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540898508682.png" alt="1540898508682"></p><p>pom.xml:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.user<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-user<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.0-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><p>参照上边的项目,编写引导类:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(UserApplication.class);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>编写UserController:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"hello"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello ssm"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="4-3-整合SpringMVC"><a href="#4-3-整合SpringMVC" class="headerlink" title="4.3.整合SpringMVC"></a>4.3.整合SpringMVC</h2><p>虽然默认配置已经可以使用SpringMVC了,不过我们有时候需要进行自定义配置。</p><h3 id="4-3-1-修改端口"><a href="#4-3-1-修改端口" class="headerlink" title="4.3.1.修改端口"></a>4.3.1.修改端口</h3><p>添加全局配置文件:application.properties</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540898582724.png" alt="1540898582724"></p><p>端口通过以下方式配置</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540898053088.png" alt="1540898053088"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 映射端口</span><br><span class="line">server.port=80</span><br></pre></td></tr></table></figure></div><p>重启服务后测试:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528116232569.png" alt="1528116232569"></p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528116322747.png" alt="1528116322747"></p><h3 id="4-3-2-访问静态资源"><a href="#4-3-2-访问静态资源" class="headerlink" title="4.3.2.访问静态资源"></a>4.3.2.访问静态资源</h3><p>现在,我们的项目是一个jar工程,那么就没有webapp,我们的静态资源该放哪里呢?</p><p>回顾我们上面看的源码,有一个叫做ResourceProperties的类,里面就定义了静态资源的默认查找路径: <img src="/2019/12/11/Spring Boot快速开发入门/1528096892588.png" alt="1528096892588"></p><p>默认的静态资源路径为:</p><ul><li>classpath:/META-INF/resources/</li><li>classpath:/resources/</li><li>classpath:/static/</li><li>classpath:/public/</li></ul><p>只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。</p><p>我们习惯会把静态资源放在<code>classpath:/static/</code>目录下。我们创建目录,并且添加一些静态资源:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540898730442.png" alt="1540898730442"></p><p>重启项目后测试:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540898831238.png" alt="1540898831238"></p><h3 id="4-3-3-添加拦截器"><a href="#4-3-3-添加拦截器" class="headerlink" title="4.3.3.添加拦截器"></a>4.3.3.添加拦截器</h3><p>拦截器也是我们经常需要使用的,在SpringBoot中该如何配置呢?</p><p>拦截器不是一个普通属性,而是一个类,所以就要用到java配置方式了。在SpringBoot官方文档中有这么一段说明:</p><blockquote><p>If you want to keep Spring Boot MVC features and you want to add additional <a href="https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/web.html#mvc" target="_blank" rel="noopener">MVC configuration</a> (interceptors, formatters, view controllers, and other features), you can add your own <code>@Configuration</code> class of type <code>WebMvcConfigurer</code> but <strong>without</strong> <code>@EnableWebMvc</code>. If you wish to provide custom instances of <code>RequestMappingHandlerMapping</code>, <code>RequestMappingHandlerAdapter</code>, or <code>ExceptionHandlerExceptionResolver</code>, you can declare a <code>WebMvcRegistrationsAdapter</code> instance to provide such components.</p><p>If you want to take complete control of Spring MVC, you can add your own <code>@Configuration</code> annotated with <code>@EnableWebMvc</code>.</p></blockquote><p>翻译:</p><blockquote><p>如果你想要保持Spring Boot 的一些默认MVC特征,同时又想自定义一些MVC配置(包括:拦截器,格式化器, 视图控制器、消息转换器 等等),你应该让一个类实现<code>WebMvcConfigurer</code>,并且添加<code>@Configuration</code>注解,但是<strong>千万不要</strong>加<code>@EnableWebMvc</code>注解。如果你想要自定义<code>HandlerMapping</code>、<code>HandlerAdapter</code>、<code>ExceptionResolver</code>等组件,你可以创建一个<code>WebMvcRegistrationsAdapter</code>实例 来提供以上组件。</p><p>如果你想要完全自定义SpringMVC,不保留SpringBoot提供的一切特征,你可以自己定义类并且添加<code>@Configuration</code>注解和<code>@EnableWebMvc</code>注解</p></blockquote><p>总结:通过实现<code>WebMvcConfigurer</code>并添加<code>@Configuration</code>注解来实现自定义部分SpringMvc配置。</p><p>实现如下:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540899012303.png" alt="1540899012303"></p><p>首先我们定义一个拦截器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyInterceptor</span> <span class="keyword">implements</span> <span class="title">HandlerInterceptor</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"preHandle method is running!"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"postHandle method is running!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"afterCompletion method is running!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>然后定义配置类,注册拦截器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MvcConfiguration</span> <span class="keyword">implements</span> <span class="title">WebMvcConfigurer</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> HandlerInterceptor myInterceptor;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 重写接口中的addInterceptors方法,添加自定义拦截器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> registry</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> </span>{</span><br><span class="line"> registry.addInterceptor(myInterceptor).addPathPatterns(<span class="string">"/**"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>接下来运行并查看日志:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="VERILOG"><figure class="iseeu highlight /verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">preHandle method is running!</span><br><span class="line">postHandle method is running!</span><br><span class="line">afterCompletion method is running!</span><br></pre></td></tr></table></figure></div><p>你会发现日志中只有这些打印信息,springMVC的日志信息都没有,因为springMVC记录的log级别是debug,springboot默认是显示info以上,我们需要进行配置。</p><p>SpringBoot通过<code>logging.level.*=debug</code>来配置日志级别,*填写包名</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 设置org.springframework包的日志级别为debug</span><br><span class="line">logging.level.org.springframework=debug</span><br></pre></td></tr></table></figure></div><p>再次运行查看:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540899090277.png" alt="1540899090277"></p><h2 id="4-4-整合连接池"><a href="#4-4-整合连接池" class="headerlink" title="4.4.整合连接池"></a>4.4.整合连接池</h2><p>jdbc连接池是spring配置中的重要一环,在SpringBoot中该如何处理呢?</p><p>答案是不需要处理,我们只要找到SpringBoot提供的启动器即可:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528126755717.png" alt="1528126755717"></p><p>在pom.xml中引入jdbc的启动器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!--jdbc的启动器,默认使用HikariCP连接池--></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-jdbc<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!--不要忘记数据库驱动,因为springboot不知道我们使用的什么数据库,这里选择mysql--></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>SpringBoot已经自动帮我们引入了一个连接池:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528126862203.png" alt="1528126862203"></p><p>HikariCP应该是目前速度最快的连接池了,我们看看它与c3p0的对比:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1525516441005.png" alt="1525516441005"></p><p>因此,我们只需要指定连接池参数即可:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># 连接四大参数</span><br><span class="line">spring.datasource.url=jdbc:mysql://localhost:3306/heima</span><br><span class="line">spring.datasource.username=root</span><br><span class="line">spring.datasource.password=root</span><br><span class="line"># 可省略,SpringBoot自动推断</span><br><span class="line">spring.datasource.driverClassName=com.mysql.jdbc.Driver</span><br><span class="line"></span><br><span class="line">spring.datasource.hikari.idle-timeout=60000</span><br><span class="line">spring.datasource.hikari.maximum-pool-size=30</span><br><span class="line">spring.datasource.hikari.minimum-idle=10</span><br></pre></td></tr></table></figure></div><p>当然,如果你更喜欢Druid连接池,也可以使用Druid官方提供的启动器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- Druid连接池 --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.alibaba<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>druid-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.1.6<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>而连接信息的配置与上面是类似的,只不过在连接池特有属性上,方式略有不同:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#初始化连接数</span><br><span class="line">spring.datasource.druid.initial-size=1</span><br><span class="line">#最小空闲连接</span><br><span class="line">spring.datasource.druid.min-idle=1</span><br><span class="line">#最大活动连接</span><br><span class="line">spring.datasource.druid.max-active=20</span><br><span class="line">#获取连接时测试是否可用</span><br><span class="line">spring.datasource.druid.test-on-borrow=true</span><br><span class="line">#监控页面启动</span><br><span class="line">spring.datasource.druid.stat-view-servlet.allow=true</span><br></pre></td></tr></table></figure></div><h2 id="4-5-整合mybatis"><a href="#4-5-整合mybatis" class="headerlink" title="4.5.整合mybatis"></a>4.5.整合mybatis</h2><h3 id="4-5-1-mybatis"><a href="#4-5-1-mybatis" class="headerlink" title="4.5.1.mybatis"></a>4.5.1.mybatis</h3><p>SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis<a href="https://github.com/mybatis/spring-boot-starter" target="_blank" rel="noopener">官方</a>自己实现了:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!--mybatis --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.mybatis.spring.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>配置,基本没有需要配置的:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># mybatis 别名扫描</span><br><span class="line">mybatis.type-aliases-package=cn.itcast.pojo</span><br><span class="line"># mapper.xml文件位置,如果没有映射文件,请注释掉</span><br><span class="line">mybatis.mapper-locations=classpath:mappers/*.xml</span><br></pre></td></tr></table></figure></div><p>需要注意,这里没有配置mapper接口扫描包,因此我们需要给每一个Mapper接口添加<code>@Mapper</code>注解,才能被识别。</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528128785010.png" alt="1528128785010"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>user对象参照课前资料,需要通用mapper的注解:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540899330478.png" alt="1540899330478"></p><p>接下来,就去集成通用mapper。</p><h3 id="4-5-2-通用mapper"><a href="#4-5-2-通用mapper" class="headerlink" title="4.5.2.通用mapper"></a>4.5.2.通用mapper</h3><p>通用Mapper的作者也为自己的插件编写了启动器,我们直接引入即可:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 通用mapper --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>tk.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mapper-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>不需要做任何配置就可以使用了。</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> <span class="keyword">extends</span> <span class="title">tk</span>.<span class="title">mybatis</span>.<span class="title">mapper</span>.<span class="title">common</span>.<span class="title">Mapper</span><<span class="title">User</span>></span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="4-6-整合事务"><a href="#4-6-整合事务" class="headerlink" title="4.6.整合事务"></a>4.6.整合事务</h2><p>其实,我们引入jdbc或者web的启动器,就已经引入事务相关的依赖及默认配置了</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528128711716.png" alt="1528128711716"></p><p>至于事务,SpringBoot中通过注解来控制。就是我们熟知的<code>@Transactional</code></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryById</span><span class="params">(Long id)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.userMapper.selectByPrimaryKey(id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Transactional</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteById</span><span class="params">(Long id)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.userMapper.deleteByPrimaryKey(id);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="4-7-启动测试"><a href="#4-7-启动测试" class="headerlink" title="4.7.启动测试"></a>4.7.启动测试</h2><p>在UserController中添加测试方法,内容:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"{id}"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">queryUserById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span>Long id)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.userService.queryById(id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping</span>(<span class="string">"hello"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello ssm"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们启动项目,查看:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1540900351215.png" alt="1540900351215"></p><h2 id="4-8-完整项目结构"><a href="#4-8-完整项目结构" class="headerlink" title="4.8.完整项目结构"></a>4.8.完整项目结构</h2><p> <img src="/2019/12/11/Spring Boot快速开发入门/1540900302611.png" alt="1540900302611"></p><p>完整的pom.xml:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cn.itcast.user<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>itcast-user<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.0-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.6.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="comment"><!--jdbc的启动器,默认使用HikariCP连接池--></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-jdbc<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="comment"><!--不要忘记数据库驱动,因为springboot不知道我们使用的什么数据库,这里选择mysql--></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!--mybatis --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.mybatis.spring.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 通用mapper --></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>tk.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mapper-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></div><p>完整的application.properties:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">server.port=80</span><br><span class="line"></span><br><span class="line">logging.level.org.springframework=debug</span><br><span class="line"></span><br><span class="line">spring.datasource.url=jdbc:mysql://localhost:3306/heima</span><br><span class="line">spring.datasource.username=root</span><br><span class="line">spring.datasource.password=root</span><br><span class="line"></span><br><span class="line"># mybatis 别名扫描</span><br><span class="line">mybatis.type-aliases-package=cn.itcast.pojo</span><br><span class="line"># mapper.xml文件位置,如果没有映射文件,请注释掉</span><br><span class="line"># mybatis.mapper-locations=classpath:mappers/*.xml</span><br></pre></td></tr></table></figure></div><h1 id="5-Thymeleaf快速入门"><a href="#5-Thymeleaf快速入门" class="headerlink" title="5.Thymeleaf快速入门"></a>5.Thymeleaf快速入门</h1><p>SpringBoot并不推荐使用jsp,但是支持一些模板引擎技术:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1525517263421.png" alt="1525517263421"></p><p>以前大家用的比较多的是Freemarker,但是我们今天的主角是Thymeleaf!</p><h2 id="5-1-为什么是Thymeleaf?"><a href="#5-1-为什么是Thymeleaf?" class="headerlink" title="5.1.为什么是Thymeleaf?"></a>5.1.为什么是Thymeleaf?</h2><p>简单说, Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较于其他的模板引擎,它有如下四个极吸引人的特点:</p><ul><li>动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。</li><li>开箱即用:它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。</li><li>多方言支持:Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。</li><li>与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf。代码几乎没有任何区别,就是在模板语法上有区别。</li></ul><p>接下来,我们就通过入门案例来体会Thymeleaf的魅力:</p><h2 id="5-2-提供数据"><a href="#5-2-提供数据" class="headerlink" title="5.2.提供数据"></a>5.2.提供数据</h2><p>编写一个controller方法,返回一些用户数据,放入模型中,将来在页面渲染</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="JAVA"><figure class="iseeu highlight /java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/all"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">all</span><span class="params">(ModelMap model)</span> </span>{</span><br><span class="line"> <span class="comment">// 查询用户</span></span><br><span class="line"> List<User> users = <span class="keyword">this</span>.userService.queryAll();</span><br><span class="line"> <span class="comment">// 放入模型</span></span><br><span class="line"> model.addAttribute(<span class="string">"users"</span>, users);</span><br><span class="line"> <span class="comment">// 返回模板名称(就是classpath:/templates/目录下的html文件名)</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"users"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="5-3-引入启动器"><a href="#5-3-引入启动器" class="headerlink" title="5.3.引入启动器"></a>5.3.引入启动器</h2><p>直接引入启动器:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="XML"><figure class="iseeu highlight /xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-thymeleaf<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></div><p>SpringBoot会自动为Thymeleaf注册一个视图解析器:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528133744136.png" alt="1528133744136"></p><p>与解析JSP的InternalViewResolver类似,Thymeleaf也会根据前缀和后缀来确定模板文件的位置:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528133816179.png" alt="1528133816179"></p><ul><li>默认前缀:<code>classpath:/templates/</code></li><li>默认后缀:<code>.html</code></li></ul><p>所以如果我们返回视图:<code>users</code>,会指向到 <code>classpath:/templates/users.html</code></p><p>一般我们无需进行修改,默认即可。</p><h2 id="5-4-静态页面"><a href="#5-4-静态页面" class="headerlink" title="5.4.静态页面"></a>5.4.静态页面</h2><p>根据上面的文档介绍,模板默认放在classpath下的templates文件夹,我们新建一个html文件放入其中:</p><p> <img src="/2019/12/11/Spring Boot快速开发入门/1528134057401.png" alt="1528134057401"></p><p>编写html模板,渲染模型中的数据:</p><p>注意,把html 的名称空间,改成:<code>xmlns:th="http://www.thymeleaf.org"</code> 会有语法提示</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="HTML"><figure class="iseeu highlight /html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">xmlns:th</span>=<span class="string">"http://www.thymeleaf.org"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>首页<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span> <span class="attr">type</span>=<span class="string">"text/css"</span>></span><span class="undefined"></span></span><br><span class="line"><span class="undefined"> table {border-collapse: collapse; font-size: 14px; width: 80%; margin: auto}</span></span><br><span class="line"><span class="undefined"> table, th, td {border: 1px solid darkslategray;padding: 10px}</span></span><br><span class="line"><span class="undefined"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"text-align: center"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">style</span>=<span class="string">"color: darkslategray; font-size: 30px"</span>></span>欢迎光临!<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">hr</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">table</span> <span class="attr">class</span>=<span class="string">"list"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>id<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>姓名<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>用户名<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>年龄<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>性别<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>生日<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span> <span class="attr">th:each</span>=<span class="string">"user : ${users}"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.id}"</span>></span>1<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.name}"</span>></span>张三<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.userName}"</span>></span>zhangsan<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.age}"</span>></span>20<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.sex}"</span>></span>男<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span> <span class="attr">th:text</span>=<span class="string">"${user.birthday}"</span>></span>1980-02-30<span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">table</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure></div><p>我们看到这里使用了以下语法:</p><ul><li><code>${}</code> :这个类似与el表达式,但其实是ognl的语法,比el表达式更加强大</li><li><code>th-</code>指令:<code>th-</code>是利用了Html5中的自定义属性来实现的。如果不支持H5,可以用<code>data-th-</code>来代替<ul><li><code>th:each</code>:类似于<code>c:foreach</code> 遍历集合,但是语法更加简洁</li><li><code>th:text</code>:声明标签中的文本<ul><li>例如<code><td th-text='${user.id}'>1</td></code>,如果user.id有值,会覆盖默认的1</li><li>如果没有值,则会显示td中默认的1。这正是thymeleaf能够动静结合的原因,模板解析失败不影响页面的显示效果,因为会显示默认值!</li></ul></li></ul></li></ul><h2 id="5-5-测试"><a href="#5-5-测试" class="headerlink" title="5.5.测试"></a>5.5.测试</h2><p>接下来,我们打开页面测试一下:</p><p><img src="/2019/12/11/Spring Boot快速开发入门/1528134027726.png" alt="1528134027726"></p><h2 id="5-6-模板缓存"><a href="#5-6-模板缓存" class="headerlink" title="5.6.模板缓存"></a>5.6.模板缓存</h2><p>Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力。但是这给我们开发带来了不便,修改页面后并不会立刻看到效果,我们开发阶段可以关掉缓存使用:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 开发阶段关闭thymeleaf的模板缓存</span><br><span class="line">spring.thymeleaf.cache=false</span><br></pre></td></tr></table></figure></div><p><strong>注意</strong>:</p><pre><code>在Idea中,我们需要在修改页面后按快捷键:`Ctrl + Shift + F9` 对项目进行rebuild才可以。eclipse中没有测试过。</code></pre><p>我们可以修改页面,测试一下。</p>]]></content>
<summary type="html">
<h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul>
<li>了解SpringBoot的作用</li>
<li>掌握java配置的方式</li>
<li>了解
</summary>
<category term="Spring Boot" scheme="http://enfangzhong.github.io/categories/Spring-Boot/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/categories/Spring-Boot/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="微服务" scheme="http://enfangzhong.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="Spring Boot" scheme="http://enfangzhong.github.io/tags/Spring-Boot/"/>
</entry>
<entry>
<title>Hexo个人博客一些写作标签</title>
<link href="http://enfangzhong.github.io/2019/12/08/Hexo%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E4%B8%80%E4%BA%9B%E5%86%99%E4%BD%9C%E6%A0%87%E7%AD%BE/"/>
<id>http://enfangzhong.github.io/2019/12/08/Hexo个人博客一些写作标签/</id>
<published>2019-12-07T22:55:11.000Z</published>
<updated>2019-12-18T06:16:31.311Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="一、代码标签"><a href="#一、代码标签" class="headerlink" title="一、代码标签"></a>一、代码标签</h3><p>代码:<br><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{% codeblock lang:command %}</span><br><span class="line">这是一行代码</span><br><span class="line">{% endcodeblock %}</span><br></pre></td></tr></table></figure></div></p><p> 效果: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">这是一行代码</span><br></pre></td></tr></table></figure></div><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">这是一行代码</span><br></pre></td></tr></table></figure></div><h3 id="二、文本居中标签"><a href="#二、文本居中标签" class="headerlink" title="二、文本居中标签"></a>二、文本居中标签</h3><p> 代码: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{% cq %}</span><br><span class="line">四郎,那年杏花微雨,你说你是果郡王。原来自那时起,一切便都是错的。</span><br><span class="line">{% endcq %}</span><br></pre></td></tr></table></figure></div><p> 效果: </p><blockquote class="blockquote-center"><p>四郎,那年杏花微雨,你说你是果郡王。原来自那时起,一切便都是错的。</p></blockquote><h3 id="三、note标签"><a href="#三、note标签" class="headerlink" title="三、note标签"></a>三、note标签</h3><p> _config 文件配置关键字:note, 我的配置如下: </p><p>style: flat<br>icons: true<br>border_radius: 3</p><p> 代码: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">{% note default %}</span><br><span class="line">default</span><br><span class="line">{% endnote %}</span><br><span class="line"></span><br><span class="line">{% note primary %}</span><br><span class="line">primary</span><br><span class="line">{% endnote %}</span><br><span class="line"></span><br><span class="line">{% note success %}</span><br><span class="line">success</span><br><span class="line">{% endnote %}</span><br><span class="line"></span><br><span class="line">{% note info %}</span><br><span class="line">info</span><br><span class="line">{% endnote %}</span><br><span class="line"></span><br><span class="line">{% note warning %}</span><br><span class="line">warning</span><br><span class="line">{% endnote %}</span><br><span class="line"></span><br><span class="line">{% note danger %}</span><br><span class="line">danger</span><br><span class="line">{% endnote %}</span><br></pre></td></tr></table></figure></div><p> 效果: </p><div class="note default"><p>default</p></div><div class="note primary"><p>primary</p></div><div class="note success"><p>success</p></div><div class="note info"><p>info</p></div><div class="note warning"><p>warning</p></div><div class="note danger"><p>danger</p></div><h3 id="四、label标签"><a href="#四、label标签" class="headerlink" title="四、label标签"></a>四、label标签</h3><p> _config 文件配置关键字:Label , 需要用的话把值设为true即可。 </p><p> 代码: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{% label default@默认 %}</span><br><span class="line"></span><br><span class="line">{% label primary@主要 %}</span><br><span class="line"></span><br><span class="line">{% label success@成功 %}</span><br><span class="line"></span><br><span class="line">{% label info@信息 %}</span><br><span class="line"></span><br><span class="line">{% label warning@警告 %}</span><br><span class="line"></span><br><span class="line">{% label danger@危险 %}</span><br></pre></td></tr></table></figure></div><p>效果:</p><span class="label default">默认</span><span class="label primary">主要</span><span class="label success">成功</span><span class="label info">信息</span><span class="label warning">警告</span><span class="label danger">危险</span><p><a href="https://github.com/Lruihao/lruihao.github.io" target="_blank" class="LinkCard">Lruihao博客</a></p><p><a href="https://bestzuo.cn" class="LinkCard" target="_blank" rel="noopener">Sanarous的个人博客</a></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h3 id="一、代码标签"><a href="#一、代码标签" class="headerlink" title="一、代码标签"></a>一、代码标签</h3><p>代码:<br><div class="highlight-wrap"autocom
</summary>
<category term="博客" scheme="http://enfangzhong.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
<category term="博客" scheme="http://enfangzhong.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="写作标签" scheme="http://enfangzhong.github.io/tags/%E5%86%99%E4%BD%9C%E6%A0%87%E7%AD%BE/"/>
</entry>
<entry>
<title>Hexo个人博客自定义友链页面</title>
<link href="http://enfangzhong.github.io/2019/12/08/Hexo%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%8B%E9%93%BE%E9%A1%B5%E9%9D%A2/"/>
<id>http://enfangzhong.github.io/2019/12/08/Hexo个人博客自定义友链页面/</id>
<published>2019-12-07T21:55:11.000Z</published>
<updated>2019-12-18T06:16:55.264Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="一、常规操作:在主题配置文件中添加"><a href="#一、常规操作:在主题配置文件中添加" class="headerlink" title="一、常规操作:在主题配置文件中添加"></a>一、常规操作:在主题配置文件中添加</h3><p>在next主题的配置文件\themes\next_config.yml 中添加友链<br><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># Blog rolls</span><br><span class="line">links_icon: link</span><br><span class="line">links_title: Links</span><br><span class="line"># links_layout: block</span><br><span class="line">links_layout: inline</span><br><span class="line">links:</span><br><span class="line"> 博采众长: https://lruihao.cn/</span><br><span class="line"> 乐余地: https://www.leridy.pw/</span><br><span class="line"> 同盟源: https://tmy123.com/</span><br><span class="line"> Yremp: https://yremp.live</span><br></pre></td></tr></table></figure></div></p><p> next主题 的友链,默认是在主题配置文件中 links 下添加,当链接变多以后,侧栏页面的排版很不美观。当友链达到10+以上,那么侧边栏就会很不雅观。</p><p> <img src="/2019/12/08/Hexo个人博客自定义友链页面/u=3395334754,1068522490&fm=26&gp=0.jpg" alt="img"></p><p><img src="/2019/12/08/Hexo个人博客自定义友链页面/image-20191208052458630.png" alt="image-20191208052458630"></p><h3 id="二、骚操作:自定义友链页面"><a href="#二、骚操作:自定义友链页面" class="headerlink" title="二、骚操作:自定义友链页面"></a>二、骚操作:自定义友链页面</h3><p>这时候就需要给友链新增一个单独的页面了,下面说一下具体步骤。</p><h4 id="2-1-新增-links-页面"><a href="#2-1-新增-links-页面" class="headerlink" title="2.1 新增 links 页面"></a>2.1 新增 links 页面</h4><p> 在控制台使用命令创建: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new page links</span><br></pre></td></tr></table></figure></div><p> 也可在博客根目录 /source 下手动创建 links 文件夹和里边的 index.md 文件 </p><p> 然后在博客根目录 /source 下会生成一个 links 文件夹,打开其中的 index.md 文件,在头部写入 type = “links”,这个一定要写,如下: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: 友情链接</span><br><span class="line">date: 2019-12-08 03:21:39</span><br><span class="line">type: "links"</span><br><span class="line">---</span><br></pre></td></tr></table></figure></div><h4 id="2-2-配置-menu"><a href="#2-2-配置-menu" class="headerlink" title="2.2 配置 menu"></a>2.2 配置 menu</h4><p> 主题配置文件中<code>menu</code>下添加: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">links: /links/ || link</span><br></pre></td></tr></table></figure></div><p> 在 <code>/themes/next/languages/zh-Hans.yml</code> 文件中 <code>menu</code> 下增加中文描述 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">links: 友链</span><br></pre></td></tr></table></figure></div><p>做完这些工作,接下来就是要增加友链页面的样式了</p><h4 id="2-3-友链页面样式"><a href="#2-3-友链页面样式" class="headerlink" title="2.3 友链页面样式"></a>2.3 友链页面样式</h4><p>效果图:</p><p><img src="/2019/12/08/Hexo个人博客自定义友链页面/image-20191208054017787.png" alt="image-20191208054017787"></p><h5 id="2-3-1新增-links-swig-页"><a href="#2-3-1新增-links-swig-页" class="headerlink" title="2.3.1新增 links.swig 页"></a>2.3.1新增 links.swig 页</h5><p> 在 /themes/next/layout/ 新建 links.swig,内容如下:<br> <div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"> {% block content %}</span><br><span class="line"> {######################}</span><br><span class="line"> {### LINKS BLOCK ###}</span><br><span class="line"> {######################}</span><br><span class="line"></span><br><span class="line"> <div id="links"></span><br><span class="line"> <style></span><br><span class="line"></span><br><span class="line"> #links{</span><br><span class="line"> margin-top: 5rem;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> .links-content{</span><br><span class="line"> margin-top:1rem;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> .link-navigation::after {</span><br><span class="line"> content: " ";</span><br><span class="line"> display: block;</span><br><span class="line"> clear: both;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> .card {</span><br><span class="line"> width: 300px;</span><br><span class="line"> font-size: 1rem;</span><br><span class="line"> padding: 10px 20px;</span><br><span class="line"> border-radius: 4px;</span><br><span class="line"> transition-duration: 0.15s;</span><br><span class="line"> margin-bottom: 1rem;</span><br><span class="line"> display:flex;</span><br><span class="line"> }</span><br><span class="line"> .card:nth-child(odd) {</span><br><span class="line"> float: left;</span><br><span class="line"> }</span><br><span class="line"> .card:nth-child(even) {</span><br><span class="line"> float: right;</span><br><span class="line"> }</span><br><span class="line"> .card:hover {</span><br><span class="line"> transform: scale(1.1);</span><br><span class="line"> box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);</span><br><span class="line"> }</span><br><span class="line"> .card a {</span><br><span class="line"> border:none;</span><br><span class="line"> }</span><br><span class="line"> .card .ava {</span><br><span class="line"> width: 3rem!important;</span><br><span class="line"> height: 3rem!important;</span><br><span class="line"> margin:0!important;</span><br><span class="line"> margin-right: 1em!important;</span><br><span class="line"> border-radius:4px;</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> .card .card-header {</span><br><span class="line"> font-style: italic;</span><br><span class="line"> overflow: hidden;</span><br><span class="line"> width: 236px;</span><br><span class="line"> }</span><br><span class="line"> .card .card-header a {</span><br><span class="line"> font-style: normal;</span><br><span class="line"> color: #2bbc8a;</span><br><span class="line"> font-weight: bold;</span><br><span class="line"> text-decoration: none;</span><br><span class="line"> }</span><br><span class="line"> .card .card-header a:hover {</span><br><span class="line"> color: #d480aa;</span><br><span class="line"> text-decoration: none;</span><br><span class="line"> }</span><br><span class="line"> .card .card-header .info {</span><br><span class="line"> font-style:normal;</span><br><span class="line"> color:#a3a3a3;</span><br><span class="line"> font-size:14px;</span><br><span class="line"> min-width: 0;</span><br><span class="line"> text-overflow: ellipsis;</span><br><span class="line"> overflow: hidden;</span><br><span class="line"> white-space: nowrap;</span><br><span class="line"> }</span><br><span class="line"> </style></span><br><span class="line"> <div class="links-content"></span><br><span class="line"> <div class="link-navigation"></span><br><span class="line"></span><br><span class="line"> {% for link in theme.mylinks %}</span><br><span class="line"></span><br><span class="line"> <div class="card"></span><br><span class="line"> <img class="ava" src="{{ link.avatar }}"/></span><br><span class="line"> <div class="card-header"></span><br><span class="line"> <div></span><br><span class="line"> <a href="{{ link.site }}" target="_blank"> {{ link.nickname }}</a></span><br><span class="line"> <a href="{{ link.site }}" target="_blank"><span class="focus-links">关注</span></a></span><br><span class="line"> </div></span><br><span class="line"> <div class="info">{{ link.info }}</div></span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"></span><br><span class="line"> {% endfor %}</span><br><span class="line"></span><br><span class="line"> </div></span><br><span class="line"> {{ page.content }}</span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"></span><br><span class="line"> {##########################}</span><br><span class="line"> {### END LINKS BLOCK ###}</span><br><span class="line"> {##########################}</span><br><span class="line">{% endblock %}</span><br></pre></td></tr></table></figure></div></p><h5 id="2-3-2-修改-page-swig"><a href="#2-3-2-修改-page-swig" class="headerlink" title="2.3.2 修改 page.swig"></a>2.3.2 修改 page.swig</h5><p> 修改 <code>/themes/next/layout/page.swig</code> 文件,在开头的 <code>block title</code> 内部 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#}{% elif page.type === "tags" and not page.title %}{#</span><br><span class="line"> #}{{ __('title.tag') + page_title_suffix }}{#</span><br></pre></td></tr></table></figure></div><p> 这个位置下添加代码: </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><!-- 友情链接--></span><br><span class="line">#}{% elif page.type === 'links' and not page.title %}{#</span><br><span class="line"> #}{{ __('title.links') + page_title_suffix }}{#</span><br></pre></td></tr></table></figure></div><p> 效果如下: </p><p> <img src="/2019/12/08/Hexo个人博客自定义友链页面/n1Q6RH.png" alt="img"> </p><h5 id="2-3-3引入-links-swig"><a href="#2-3-3引入-links-swig" class="headerlink" title="2.3.3引入 links.swig"></a>2.3.3引入 links.swig</h5><p> 接着在 <code>/themes/next/layout/page.swig</code> 中 <code>PAGE BODY</code> 内部,引入刚才新建的 <code>page.swig</code> : </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><!-- 友情链接--></span><br><span class="line">{% elif page.type === 'links' %}</span><br><span class="line"> {% include 'links.swig' %}</span><br></pre></td></tr></table></figure></div><p> 比如我是在 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{% elif page.type === 'categories' %}</span><br></pre></td></tr></table></figure></div><p> 这个<code>if</code>下追加的: </p><p> <img src="/2019/12/08/Hexo个人博客自定义友链页面/n1MvPe.png" alt="n1MvPe.png"> </p><p> 到这里就完成页面样式的配置了。 </p><h4 id="2-4-配置友链"><a href="#2-4-配置友链" class="headerlink" title="2.4 配置友链"></a>2.4 配置友链</h4><p> 接下来,在 <code>/themes/next/_config.yml</code> 文件中配置友链,末尾处新增 <code>mylinks</code> ,如下 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">mylinks:</span><br><span class="line"># 友链交换 已经添加贵站</span><br><span class="line"># 名称:AnFrank</span><br><span class="line"># 地址:https://enfangzhong.github.io/</span><br><span class="line"># 描述:既可以早九晚五又可以浪迹天涯。</span><br><span class="line"># 头像:https://enfangzhong.github.io/images/avatar.jpg</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- nickname: AnFrank #友链名称</span><br><span class="line"> site: https://enfangzhong.github.io/ #友链地址</span><br><span class="line"> info: 既可以早九晚五又可以浪迹天涯。 #友链说明</span><br><span class="line"> avatar: https://enfangzhong.github.io/images/avatar.jpg #友链头像</span><br><span class="line"></span><br><span class="line">- nickname: 博采众长 #友链名称</span><br><span class="line"> site: https://lruihao.cn/ #友链地址</span><br><span class="line"> info: 分享一些有趣程序、干货、技巧、开发教程、心情和学习记录等等。 #友链说明</span><br><span class="line"> avatar: https://lruihao.cn/images/avatar.png #友链头像</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">- nickname: Yremp</span><br><span class="line"> site: https://yremp.live</span><br><span class="line"> info: 流年,谁给过的倾城</span><br><span class="line"> avatar: https://cdn.jsdelivr.net/gh/yremp/[email protected]/img/custom/head.jpg</span><br><span class="line"> </span><br><span class="line">- nickname: Leaf's Blog</span><br><span class="line"> avatar: https://leafjame.github.io/images/beichen.png</span><br><span class="line"> site: https://leafjame.github.io</span><br><span class="line"> info: Java狮 北漂男 摄影 旅行 赚钱</span><br></pre></td></tr></table></figure></div><blockquote><p> 这里是配置了四个友链,多个的配置方式相同。 </p><p>hexo 部署命令三连,看看效果吧~ </p></blockquote><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo c && hexo g -d</span><br></pre></td></tr></table></figure></div>]]></content>
<summary type="html">
<p>[TOC]</p>
<h3 id="一、常规操作:在主题配置文件中添加"><a href="#一、常规操作:在主题配置文件中添加" class="headerlink" title="一、常规操作:在主题配置文件中添加"></a>一、常规操作:在主题配置文件中添加</h3>
</summary>
<category term="博客" scheme="http://enfangzhong.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
<category term="博客" scheme="http://enfangzhong.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="自定义友链页面" scheme="http://enfangzhong.github.io/tags/%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%8B%E9%93%BE%E9%A1%B5%E9%9D%A2/"/>
</entry>
<entry>
<title>Hexo个人博客添加标签云及效果展示</title>
<link href="http://enfangzhong.github.io/2019/12/08/Hexo%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E6%A0%87%E7%AD%BE%E4%BA%91%E5%8F%8A%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA/"/>
<id>http://enfangzhong.github.io/2019/12/08/Hexo个人博客添加标签云及效果展示/</id>
<published>2019-12-07T18:31:11.000Z</published>
<updated>2019-12-18T06:16:16.813Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="Hexo个人博客添加标签云及效果展示"><a href="#Hexo个人博客添加标签云及效果展示" class="headerlink" title="Hexo个人博客添加标签云及效果展示"></a>Hexo个人博客添加标签云及效果展示</h1><h2 id="一、hexo-tag-cloud插件介绍"><a href="#一、hexo-tag-cloud插件介绍" class="headerlink" title="一、hexo-tag-cloud插件介绍"></a>一、hexo-tag-cloud插件介绍</h2><p>hexo-tag-cloud插件是作者写的一个Hexo博客的标签云插件,旨在直观的展示标签的种类,美观大方且非常优雅。</p><p>1.1 插件下载地址:<br><a href="https://github.com/MikeCoder/hexo-tag-cloud" target="_blank" rel="noopener">插件的GitHub地址</a>: <a href="https://github.com/MikeCoder/hexo-tag-cloud" target="_blank" rel="noopener">https://github.com/MikeCoder/hexo-tag-cloud</a> </p><p>1.2 插件说明:<br><a href="https://github.com/MikeCoder/hexo-tag-cloud/blob/master/README.ZH.md" target="_blank" rel="noopener">说明地址</a>: <a href="https://github.com/MikeCoder/hexo-tag-cloud/blob/master/README.ZH.md" target="_blank" rel="noopener">https://github.com/MikeCoder/hexo-tag-cloud/blob/master/README.ZH.md</a> </p><p>1.3 标签云效果展示:</p><p><img src="/2019/12/08/Hexo个人博客添加标签云及效果展示/image-20191208020620913.png" alt="image-20191208020620913"><a href="https://enfangzhong.github.io/">我的博客主页</a>: <a href="https://enfangzhong.github.io/">https://enfangzhong.github.io/</a> </p><h2 id="二、安装插件"><a href="#二、安装插件" class="headerlink" title="二、安装插件"></a>二、安装插件</h2><p>进入到 <code>hexo</code> 的根目录,在 <code>package.json</code> 中添加依赖: <code>"hexo-tag-cloud": "2.0.*"</code> 操作如下:</p><h3 id="2-1-使用命令行进行安装"><a href="#2-1-使用命令行进行安装" class="headerlink" title="2.1 使用命令行进行安装"></a>2.1 使用命令行进行安装</h3><p>复制</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-tag-cloud@^2.0.* --save</span><br></pre></td></tr></table></figure></div><h3 id="2-2-Git-clone-下载"><a href="#2-2-Git-clone-下载" class="headerlink" title="2.2 Git clone 下载"></a>2.2 Git clone 下载</h3><p>使用命令行安装插件包的过程中可能会出现问题,安装失败,安装不完全。可以直接克隆插件到博客的插件文件夹<code>blog/node_modules</code>里。或者克隆到桌面,复制到博客的插件文件夹<code>blog/node_modules</code>里。</p><p><img src="/2019/12/08/Hexo个人博客添加标签云及效果展示/image-20191208021017404.png" alt="image-20191208021017404"></p><h2 id="三、配置插件"><a href="#三、配置插件" class="headerlink" title="三、配置插件"></a>三、配置插件</h2><p>插件的配置需要对应的环境,可以在主题文件夹里找一下,有没有对应的渲染文件,然后根据渲染文件的类型,选择对应的插件配置方法。</p><h3 id="1、swig-用户-Next主题在列"><a href="#1、swig-用户-Next主题在列" class="headerlink" title="1、swig 用户 (Next主题在列)"></a>1、swig 用户 (Next主题在列)</h3><p>在主题文件夹找到文件 <code>theme/next/layout/_macro/sidebar.swig</code>, 然后添加如下代码:</p> <div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">{% if site.tags.length > 1 %}</span><br><span class="line"><script type="text/javascript" charset="utf-8" src="/js/tagcloud.js"></script></span><br><span class="line"><script type="text/javascript" charset="utf-8" src="/js/tagcanvas.js"></script></span><br><span class="line"><div class="widget-wrap"></span><br><span class="line"> <h3 class="widget-title">标签云</h3></span><br><span class="line"> <div id="myCanvasContainer" class="widget tagcloud"></span><br><span class="line"> <canvas width="250" height="250" id="resCanvas" style="width=100%"></span><br><span class="line"> {{ list_tags() }}</span><br><span class="line"> </canvas></span><br><span class="line"> </div></span><br><span class="line"></div></span><br><span class="line">{% endif %}</span><br></pre></td></tr></table></figure></div><p>代码添加到后面即可,添加示意图如下: </p><p><img src="/2019/12/08/Hexo个人博客添加标签云及效果展示/image-20191208021343781.png" alt="image-20191208021343781"></p><h3 id="2、对于ejs的用户-默认主题landscape在列"><a href="#2、对于ejs的用户-默认主题landscape在列" class="headerlink" title="2、对于ejs的用户 (默认主题landscape在列)"></a>2、对于<code>ejs</code>的用户 (默认主题landscape在列)</h3><p>在主题文件夹找到文件<code>hexo/themes/landscape/layout/_widget/tagcloud.ejs</code>,将这个文件修改如下:</p><p>复制</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><% if (site.tags.length) { %></span><br><span class="line"> <script type="text/javascript" charset="utf-8" src="/js/tagcloud.js"></script></span><br><span class="line"> <script type="text/javascript" charset="utf-8" src="/js/tagcanvas.js"></script></span><br><span class="line"> <div class="widget-wrap"></span><br><span class="line"> <h3 class="widget-title">Tag Cloud</h3></span><br><span class="line"> <div id="myCanvasContainer" class="widget tagcloud"></span><br><span class="line"> <canvas width="250" height="250" id="resCanvas" style="width=100%"></span><br><span class="line"> <%- tagcloud() %></span><br><span class="line"> </canvas></span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"><% } %></span><br></pre></td></tr></table></figure></div><h3 id="3、对于jade用户-Apollo主题在列"><a href="#3、对于jade用户-Apollo主题在列" class="headerlink" title="3、对于jade用户 (Apollo主题在列)"></a>3、对于<code>jade</code>用户 (Apollo主题在列)</h3><p>找到 apollo/layout/archive.jade 文件,并且把 container 代码块修改为如下内容:</p><p>复制</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">block container</span><br><span class="line"> include mixins/post</span><br><span class="line"> .archive</span><br><span class="line"> h2(class='archive-year')= 'Tag Cloud'</span><br><span class="line"> script(type='text/javascript', charset='utf-8', src='/oj-code/js/tagcloud.js')</span><br><span class="line"> script(type='text/javascript', charset='utf-8', src='/oj-code/js/tagcanvas.js')</span><br><span class="line"> #myCanvasContainer.widget.tagcloud(align='center')</span><br><span class="line"> canvas#resCanvas(width='500', height='500', style='width=100%')</span><br><span class="line"> !=tagcloud()</span><br><span class="line"> !=tagcloud()</span><br><span class="line"> +postList()</span><br></pre></td></tr></table></figure></div><h2 id="四、主题配置"><a href="#四、主题配置" class="headerlink" title="四、主题配置"></a>四、主题配置</h2><p> 在next主题根目录,找到 <code>_config.yml</code>配置文件然后在最后添加如下的配置项,可以自定义标签云的字体和颜色,还有突出高亮: </p> <div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># hexo-tag-cloud</span><br><span class="line">tag_cloud:</span><br><span class="line"> textFont: Trebuchet MS, Helvetica</span><br><span class="line"> textColor: '#333'</span><br><span class="line"> textHeight: 25</span><br><span class="line"> outlineColor: '#E2E1D1'</span><br><span class="line"> maxSpeed: 0.1</span><br></pre></td></tr></table></figure></div><p> textColor: ‘#333’ 字体颜色<br>textHeight: 25 字体高度,根据部署的效果调整<br>maxSpeed: 0.1 文字滚动速度,根据自己喜好调整 </p><h2 id="五、部署"><a href="#五、部署" class="headerlink" title="五、部署"></a>五、部署</h2><h3 id="5-1-本地部署预览效果"><a href="#5-1-本地部署预览效果" class="headerlink" title="5.1 本地部署预览效果"></a>5.1 本地部署预览效果</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean && hexo g && hexo s</span><br></pre></td></tr></table></figure></div><h3 id="5-2-博客部署到github或者码云"><a href="#5-2-博客部署到github或者码云" class="headerlink" title="5.2 博客部署到github或者码云"></a>5.2 博客部署到github或者码云</h3><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean && hexo g && hexo d</span><br></pre></td></tr></table></figure></div><p>推荐使用 <code>&&</code> 作为组合命令的串联符号</p><p>注:一定要严格清理缓存,这样不容易出现问题,即需要执行<code>hexo clean</code></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1 id="Hexo个人博客添加标签云及效果展示"><a href="#Hexo个人博客添加标签云及效果展示" class="headerlink" title="Hexo个人博客添加标签云及效果展示"></a>Hexo个人博客添加标签云及效果展示<
</summary>
<category term="博客" scheme="http://enfangzhong.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
<category term="博客" scheme="http://enfangzhong.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="标签云" scheme="http://enfangzhong.github.io/tags/%E6%A0%87%E7%AD%BE%E4%BA%91/"/>
</entry>
<entry>
<title>Hexo个人博客添加APlayer音乐播放器功能</title>
<link href="http://enfangzhong.github.io/2019/12/08/Hexo%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0APlayer%E9%9F%B3%E4%B9%90%E6%92%AD%E6%94%BE%E5%99%A8%E5%8A%9F%E8%83%BD/"/>
<id>http://enfangzhong.github.io/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/</id>
<published>2019-12-07T16:54:11.000Z</published>
<updated>2019-12-18T06:16:03.680Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><blockquote><p>为什么会写hexo添加音乐播放器呢?初衷是有粉丝私信总是问我的博客中音乐播放器是怎么添加的。所以还是写一下,毕竟不是一两句话可以描述清楚。</p></blockquote><h1 id="Hexo个人博客添加APlayer音乐播放器功能"><a href="#Hexo个人博客添加APlayer音乐播放器功能" class="headerlink" title="Hexo个人博客添加APlayer音乐播放器功能"></a>Hexo个人博客添加APlayer音乐播放器功能</h1><h2 id="一、效果图"><a href="#一、效果图" class="headerlink" title="一、效果图"></a>一、效果图</h2><p><a href="https://enfangzhong.github.io/">音乐播放器效果地址:</a> <a href="https://enfangzhong.github.io/">https://enfangzhong.github.io/</a> </p><p><img src="/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/image-20191208015229566.png" alt="image-20191208015229566"></p><h2 id="二、引入APlayer音乐播放器"><a href="#二、引入APlayer音乐播放器" class="headerlink" title="二、引入APlayer音乐播放器"></a>二、引入APlayer音乐播放器</h2><h3 id="2-1-下载APlayer源码"><a href="#2-1-下载APlayer源码" class="headerlink" title="2.1 下载APlayer源码"></a>2.1 下载APlayer源码</h3><p><a href="https://github.com/MoePlayer/APlayer" target="_blank" rel="noopener">APlayer源码下载地址:</a><a href="https://github.com/MoePlayer/APlayer" target="_blank" rel="noopener">https://github.com/MoePlayer/APlayer</a></p><h3 id="2-2-将源码放到next主题的source文件夹中"><a href="#2-2-将源码放到next主题的source文件夹中" class="headerlink" title="2.2 将源码放到next主题的source文件夹中"></a>2.2 将源码放到next主题的source文件夹中</h3><p> 下载到APlayer源码压缩包,解压后把dist文件夹复制到\themes\next\source目录中。 </p><p><img src="/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/image-20191208011527449.png" alt="image-20191208011527449"></p><h3 id="2-3-放入自己喜欢的音乐"><a href="#2-3-放入自己喜欢的音乐" class="headerlink" title="2.3 放入自己喜欢的音乐"></a>2.3 放入自己喜欢的音乐</h3><p> 在dist目录里,新建music.js文件,并把如下代码粘贴进去。 </p><p><img src="/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/image-20191208011840592.png" alt="image-20191208011840592"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">const ap = new APlayer({</span><br><span class="line"> container: document.getElementById('aplayer'),</span><br><span class="line"> fixed: true,</span><br><span class="line"> autoplay: false,</span><br><span class="line"> audio: [</span><br><span class="line">{</span><br><span class="line"> name: "平凡之路",</span><br><span class="line"> artist: '朴树',</span><br><span class="line"> url: 'http://www.ytmp3.cn/down/59211.mp3',</span><br><span class="line"> cover: 'http://p1.music.126.net/W_5XiCv3rGS1-J7EXpHSCQ==/18885211718782327.jpg?param=130y130',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: '这些民谣 - 一次听个够',</span><br><span class="line"> artist: '翁大涵',</span><br><span class="line"> url: 'http://www.ytmp3.cn/down/60222.mp3',</span><br><span class="line"> cover: 'http://p2.music.126.net/Wx5GNJEpay2JbfVUJc4Aew==/109951163094853876.jpg?param=130y130',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: '你的酒馆对我打了烊',</span><br><span class="line"> artist: '陈雪凝',</span><br><span class="line"> url: 'http://www.ytmp3.cn/down/59770.mp3',</span><br><span class="line"> cover: 'http://p1.music.126.net/LiRR__0pJHSivqBHZzbMUw==/109951163816225567.jpg?param=130y130',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: 'Something Just Like This',</span><br><span class="line"> artist: 'The Chainsmokers',</span><br><span class="line"> url: 'http://www.ytmp3.cn/down/50463.mp3',</span><br><span class="line"> cover: 'http://p2.music.126.net/ggnyubDdMxrhpqYvpZbhEQ==/3302932937412681.jpg?param=130y130',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: 'Good Time',</span><br><span class="line"> artist: 'Owl City&Carly Rae Jepsen',</span><br><span class="line"> url: 'http://www.ytmp3.cn/down/34148.mp3',</span><br><span class="line"> cover: 'http://p1.music.126.net/c5NVKUIAUcyN4BQUDbGnEg==/109951163221157827.jpg?param=130y130',</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">});</span><br></pre></td></tr></table></figure></div><h3 id="2-4在next主题下的layout中引入APlayer音乐播放器源码"><a href="#2-4在next主题下的layout中引入APlayer音乐播放器源码" class="headerlink" title="2.4在next主题下的layout中引入APlayer音乐播放器源码"></a>2.4在next主题下的layout中引入APlayer音乐播放器源码</h3><p>在\themes\next\layout_layout.swig文件中,<body>里新增如下代码: </body></p><p><img src="/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/image-20191208012423145.png" alt="image-20191208012423145"></p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><!-- 加入APlayer音乐播放器 --></span><br><span class="line"><link rel="stylesheet" href="/dist/APlayer.min.css"></span><br><span class="line"><div id="aplayer"></div></span><br><span class="line"><script type="text/javascript" src="/dist/APlayer.min.js"></script></span><br><span class="line"><script type="text/javascript" src="/dist/music.js"></script></span><br></pre></td></tr></table></figure></div><h3 id="2-5-重新部署"><a href="#2-5-重新部署" class="headerlink" title="2.5 重新部署"></a>2.5 重新部署</h3><p>在blog目录下开启重新部署命令:</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo c && hexo g -d</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/08/Hexo个人博客添加APlayer音乐播放器功能/image-20191208012722037.png" alt="image-20191208012722037"></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<blockquote>
<p>为什么会写hexo添加音乐播放器呢?初衷是有粉丝私信总是问我的博客中音乐播放器是怎么添加的。所以还是写一下,毕竟不是一两句话可以描述清楚。</p>
</blockquote>
<h1 id="Hexo个人博客添加APlay
</summary>
<category term="博客" scheme="http://enfangzhong.github.io/categories/%E5%8D%9A%E5%AE%A2/"/>
<category term="博客" scheme="http://enfangzhong.github.io/tags/%E5%8D%9A%E5%AE%A2/"/>
<category term="音乐播放器" scheme="http://enfangzhong.github.io/tags/%E9%9F%B3%E4%B9%90%E6%92%AD%E6%94%BE%E5%99%A8/"/>
</entry>
<entry>
<title>SQL语句面试经典50题</title>
<link href="http://enfangzhong.github.io/2019/12/03/SQL%E8%AF%AD%E5%8F%A5%E9%9D%A2%E8%AF%95%E7%BB%8F%E5%85%B850%E9%A2%98/"/>
<id>http://enfangzhong.github.io/2019/12/03/SQL语句面试经典50题/</id>
<published>2019-12-03T05:26:11.000Z</published>
<updated>2019-12-19T01:09:45.104Z</updated>
<content type="html"><![CDATA[<p>SQL语句面试经典50题</p><hr><p>2019/12/03 19:36 今天更新到这</p><p>2019/12/04 22:05 项目中完成了表单中的上传下载接口与前端对接。今天加班到八点,话说加班可以调休。希望过年调几天。</p><p>2019/12/05 01:43 今天研究了一下Flowable 工作流框架,明天继续。夜深了,休息。</p><p>2019/12/06 13:23 刚才在b站看了一个程序员老哥被裁的纪念品上面写着:感谢有你,一路同行,怀念我们一起奋斗的时光。</p><hr><h1 id="0-学习目标"><a href="#0-学习目标" class="headerlink" title="0.学习目标"></a>0.学习目标</h1><ul><li>了解SQL的作用</li><li>掌握SQL语句的编写</li></ul><h1 id="1-为什么要练习sql语句?"><a href="#1-为什么要练习sql语句?" class="headerlink" title="1. 为什么要练习sql语句?"></a>1. 为什么要练习sql语句?</h1><p>做为一个后端开发人员,sql语句的编写是至关重要的。在实习的第一天到公司,我的leader戴哥就是让我练习sql语句。尽管用mybatis plus,但是sql语句可以处理复杂的逻辑。</p><p>会写sql的程序员,才是真的的crud boy。</p><p>练习sql语句,主要看了猴子和小番茄的知乎博文分析。</p><p>这里有sql面试的50题,帮助大家更进一步的熟悉SQL. SQL是数据分析师的必备基础技能,希望大家跟我一起来打怪升级,最后成为某一领域的数据科学家。</p><p>常见的SQL面试题:</p><blockquote><p>经典50题 - 知乎 <a href="https://zhuanlan.zhihu.com/p/38354000" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/38354000</a> </p><p>SQL面试必会50题 - 知乎 <a href="https://zhuanlan.zhihu.com/p/43289968" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/43289968</a> </p></blockquote><h1 id="2-练习题分析阶段"><a href="#2-练习题分析阶段" class="headerlink" title="2.练习题分析阶段"></a>2.练习题分析阶段</h1><p><img src="/2019/12/03/SQL语句面试经典50题/v2-888536dd71af452945e0a6a478a4e1f1_1200x500.jpg" alt="常见的SQL面试题:经典50题"> </p><p>已知有如下4张表:</p><p>学生表:student(学号,学生姓名,出生年月,性别)</p><p>成绩表:score(学号,课程号,成绩)</p><p>课程表:course(课程号,课程名称,教师号)</p><p>教师表:teacher(教师号,教师姓名)</p><p>1.学生表<br>Student(s_id,s_name,s_birth,s_sex) —学生编号,学生姓名, 出生年月,学生性别<br>2.课程表<br>Course(c_id,c_name,t_id) – —课程编号, 课程名称, 教师编号<br>3.教师表<br>Teacher(t_id,t_name) —教师编号,教师姓名<br>4.成绩表<br>Score(s_id,c_id,s_score) —学生编号,课程编号,分数</p><p>根据以上信息按照下面要求写出对应的SQL语句。</p><p>ps:这些题考察SQL的编写能力,对于这类型的题目,需要你先把4张表之间的关联关系搞清楚了,最好的办法是自己在草稿纸上画出关联图,然后再编写对应的SQL语句就比较容易了。下图是我画的这4张表的关系图,可以看出它们之间是通过哪些外键关联起来的: </p><p><img src="/2019/12/03/SQL语句面试经典50题/v2-86fd263583a6cead51675982c1735e68_hd.jpg" alt="img"> </p><h1 id="3-准备阶段"><a href="#3-准备阶段" class="headerlink" title="3.准备阶段"></a>3.准备阶段</h1><h2 id="3-1-创建数据库和表"><a href="#3-1-创建数据库和表" class="headerlink" title="3.1.创建数据库和表"></a>3.1.创建数据库和表</h2><p>为了演示题目的运行过程,我们先按下面语句在客户端navicat中创建数据库和表。</p><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-9fb2b06e6889bc7db47d4ce61164ad10_hd.jpg" alt="img"> </p><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-7054b72fc575d2ffcaa6152883609050_hd.jpg" alt="img"> </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">-- 建表</span><br><span class="line">-- 学生表</span><br><span class="line">CREATE TABLE `Student`(</span><br><span class="line">`s_id` VARCHAR(20),</span><br><span class="line">`s_name` VARCHAR(20) NOT NULL DEFAULT '',</span><br><span class="line">`s_birth` VARCHAR(20) NOT NULL DEFAULT '',</span><br><span class="line">`s_sex` VARCHAR(10) NOT NULL DEFAULT '',</span><br><span class="line">PRIMARY KEY(`s_id`)</span><br><span class="line">);</span><br><span class="line">-- 课程表</span><br><span class="line">CREATE TABLE `Course`(</span><br><span class="line">`c_id` VARCHAR(20),</span><br><span class="line">`c_name` VARCHAR(20) NOT NULL DEFAULT '',</span><br><span class="line">`t_id` VARCHAR(20) NOT NULL,</span><br><span class="line">PRIMARY KEY(`c_id`)</span><br><span class="line">);</span><br><span class="line">-- 教师表</span><br><span class="line">CREATE TABLE `Teacher`(</span><br><span class="line">`t_id` VARCHAR(20),</span><br><span class="line">`t_name` VARCHAR(20) NOT NULL DEFAULT '',</span><br><span class="line">PRIMARY KEY(`t_id`)</span><br><span class="line">);</span><br><span class="line">-- 成绩表</span><br><span class="line">CREATE TABLE `Score`(</span><br><span class="line">`s_id` VARCHAR(20),</span><br><span class="line">`c_id` VARCHAR(20),</span><br><span class="line">`s_score` INT(3),</span><br><span class="line">PRIMARY KEY(`s_id`,`c_id`)</span><br><span class="line">);</span><br><span class="line">-- 插入学生表测试数据</span><br><span class="line">insert into Student values('01' , '赵雷' , '1990-01-01' , '男');</span><br><span class="line">insert into Student values('02' , '钱电' , '1990-12-21' , '男');</span><br><span class="line">insert into Student values('03' , '孙风' , '1990-05-20' , '男');</span><br><span class="line">insert into Student values('04' , '李云' , '1990-08-06' , '男');</span><br><span class="line">insert into Student values('05' , '周梅' , '1991-12-01' , '女');</span><br><span class="line">insert into Student values('06' , '吴兰' , '1992-03-01' , '女');</span><br><span class="line">insert into Student values('07' , '郑竹' , '1989-07-01' , '女');</span><br><span class="line">insert into Student values('08' , '王菊' , '1990-01-20' , '女');</span><br><span class="line">-- 课程表测试数据</span><br><span class="line">insert into Course values('01' , '语文' , '02');</span><br><span class="line">insert into Course values('02' , '数学' , '01');</span><br><span class="line">insert into Course values('03' , '英语' , '03');</span><br><span class="line"></span><br><span class="line">-- 教师表测试数据</span><br><span class="line">insert into Teacher values('01' , '张三');</span><br><span class="line">insert into Teacher values('02' , '李四');</span><br><span class="line">insert into Teacher values('03' , '王五');</span><br><span class="line"></span><br><span class="line">-- 成绩表测试数据</span><br><span class="line">insert into Score values('01' , '01' , 80);</span><br><span class="line">insert into Score values('01' , '02' , 90);</span><br><span class="line">insert into Score values('01' , '03' , 99);</span><br><span class="line">insert into Score values('02' , '01' , 70);</span><br><span class="line">insert into Score values('02' , '02' , 60);</span><br><span class="line">insert into Score values('02' , '03' , 80);</span><br><span class="line">insert into Score values('03' , '01' , 80);</span><br><span class="line">insert into Score values('03' , '02' , 80);</span><br><span class="line">insert into Score values('03' , '03' , 80);</span><br><span class="line">insert into Score values('04' , '01' , 50);</span><br><span class="line">insert into Score values('04' , '02' , 30);</span><br><span class="line">insert into Score values('04' , '03' , 20);</span><br><span class="line">insert into Score values('05' , '01' , 76);</span><br><span class="line">insert into Score values('05' , '02' , 87);</span><br><span class="line">insert into Score values('06' , '01' , 31);</span><br><span class="line">insert into Score values('06' , '03' , 34);</span><br><span class="line">insert into Score values('07' , '02' , 89);</span><br><span class="line">insert into Score values('07' , '03' , 98);</span><br></pre></td></tr></table></figure></div><p>Student表:</p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212102315915.png" alt="image-20191212102315915"></p><p>Course表:</p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212102510760.png" alt="image-20191212102510760"></p><p> Teacher表:</p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212102618628.png" alt=" Teacher"></p><p> Score表:</p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212102654572.png" alt=" Score "></p><h1 id="4-面试题"><a href="#4-面试题" class="headerlink" title="4.面试题"></a>4.面试题</h1><p>为了方便学习,我将50道面试题进行了分类练习</p><h2 id="4-1-练习:简单查询"><a href="#4-1-练习:简单查询" class="headerlink" title="4.1.练习:简单查询"></a>4.1.练习:简单查询</h2><p><img src="/2019/12/03/SQL语句面试经典50题/v2-5a377ec35954190b6ee6b07e2c74c575_hd.jpg" alt="img"> </p><h4 id="1-查询姓“猴”的学生名单"><a href="#1-查询姓“猴”的学生名单" class="headerlink" title="1.查询姓“猴”的学生名单"></a>1.查询姓“猴”的学生名单</h4><p><img src="/2019/12/03/SQL语句面试经典50题/v2-74404bfe16150f5a9f4a473297e1e079_hd.jpg" alt="img"> </p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212105344854.png" alt="image-20191212105344854"></p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212105601321.png" alt="image-20191212105601321"></p><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212105632018.png" alt="image-20191212105632018"></p><h4 id="2-查询姓“孟”老师的个数"><a href="#2-查询姓“孟”老师的个数" class="headerlink" title="2.查询姓“孟”老师的个数"></a>2.查询姓“孟”老师的个数</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SELECT COUNT(*) AS 姓“孟”老师的个数</span><br><span class="line">FROM teacher</span><br><span class="line">WHERE t_name LIKE '孟%'</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212105917024.png" alt="image-20191212105917024"></p><h2 id="4-2-练习:汇总、分组、分组条件查询"><a href="#4-2-练习:汇总、分组、分组条件查询" class="headerlink" title="4.2.练习:汇总、分组、分组条件查询"></a>4.2.练习:汇总、分组、分组条件查询</h2><h3 id="4-2-1-练习:汇总"><a href="#4-2-1-练习:汇总" class="headerlink" title="4.2.1.练习:汇总"></a>4.2.1.练习:汇总</h3><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-983199041b422ac426e919544ae4450e_hd.jpg" alt="img"> </p><h4 id="3-面试题:查询课程编号为“0002”的总成绩"><a href="#3-面试题:查询课程编号为“0002”的总成绩" class="headerlink" title="3. 面试题:查询课程编号为“0002”的总成绩"></a>3. 面试题:查询课程编号为“0002”的总成绩</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [总成绩:汇总函数sum]</span><br><span class="line">from 从哪张表中查找数据[成绩表score]</span><br><span class="line">where 查询条件 [课程号是0002]</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT sum(s_score)</span><br><span class="line">FROM score</span><br><span class="line"></span><br><span class="line">WHERE c_id ='0002'</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212110022584.png" alt="image-20191212110022584"></p><h4 id="4-查询选了课程的学生人数"><a href="#4-查询选了课程的学生人数" class="headerlink" title="4. 查询选了课程的学生人数"></a>4. 查询选了课程的学生人数</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">这个题目翻译成大白话就是:查询有多少人选了课程</span><br><span class="line">select 学号,成绩表里学号有重复值需要去掉</span><br><span class="line">from 从课程表查找score;</span><br><span class="line">*/</span><br><span class="line">SELECT COUNT(DISTINCT c_id) AS 学生人数</span><br><span class="line"></span><br><span class="line">FROM score</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212110055542.png" alt="image-20191212110055542"></p><h3 id="4-2-2-练习:分组"><a href="#4-2-2-练习:分组" class="headerlink" title="4.2.2 练习:分组"></a>4.2.2 练习:分组</h3><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-a5f8fc41ba4bf08d49b2dcfac4094831_hd.jpg" alt="img"> </p><h4 id="5-查询各科成绩最高和最低的分"><a href="#5-查询各科成绩最高和最低的分" class="headerlink" title="5.查询各科成绩最高和最低的分"></a>5.查询各科成绩最高和最低的分</h4><p>以如下的形式显示:课程号,最高分,最低分 </p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [课程ID:是课程号的别名,最高分:max(成绩) ,最低分:min(成绩)]</span><br><span class="line">from 从哪张表中查找数据 [成绩表score]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [各科成绩:也就是每门课程的成绩,需要按课程号分组];</span><br><span class="line">*/</span><br><span class="line">/*</span><br><span class="line">上述题的拆分:课程id为0002的最高分与最低分</span><br><span class="line"></span><br><span class="line">SELECT MAX(s_score) AS 最高分, MIN(s_score) AS 最低分</span><br><span class="line">FROM score</span><br><span class="line">WHERE c_id = '0002'</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT</span><br><span class="line">c_id,MAX(s_score) AS 最高分,MIN(s_score) AS 最低分</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY c_id</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212110934135.png" alt="image-20191212110934135"></p><h4 id="6-查询每门课程被选修的学生数"><a href="#6-查询每门课程被选修的学生数" class="headerlink" title="6.查询每门课程被选修的学生数"></a>6.查询每门课程被选修的学生数</h4><p>— 通过对成绩表的课程id进行分组,然后对该学号进行计数</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [课程号,选修该课程的学生数:汇总函数count]</span><br><span class="line">from 从哪张表中查找数据 [成绩表score]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [每门课程:按课程号分组];</span><br><span class="line">*/</span><br><span class="line">SELECT c_id,COUNT(c_id)</span><br><span class="line">FROM</span><br><span class="line">score</span><br><span class="line">GROUP BY c_id</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212111222219.png" alt="image-20191212111222219"></p><h4 id="7-查询男生、女生人数"><a href="#7-查询男生、女生人数" class="headerlink" title="7.查询男生、女生人数"></a>7.查询男生、女生人数</h4><ul><li><p>在学生表中对性别进行分组 计数</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [性别,对应性别的人数:汇总函数count]</span><br><span class="line">from 从哪张表中查找数据 [性别在学生表中,所以查找的是学生表student]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [男生、女生人数:按性别分组]</span><br><span class="line">having 对分组结果指定条件 [没有]</span><br><span class="line">order by 对查询结果排序[没有];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SELECT s_sex,COUNT(s_sex) AS 个数</span><br><span class="line">FROM student</span><br><span class="line">GROUP BY s_sex</span><br></pre></td></tr></table></figure></div></li></ul><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212111302108.png" alt="image-20191212111302108"></p><h3 id="4-2-3-分组结果的条件"><a href="#4-2-3-分组结果的条件" class="headerlink" title="4.2.3 分组结果的条件"></a>4.2.3 分组结果的条件</h3><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-e272c0f9609047fa50073f46f84139c4_hd.jpg" alt="img"> </p><h4 id="8-查询平均成绩大于60分学生的学号和平均成绩"><a href="#8-查询平均成绩大于60分学生的学号和平均成绩" class="headerlink" title="8.查询平均成绩大于60分学生的学号和平均成绩"></a>8.查询平均成绩大于60分学生的学号和平均成绩</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">题目翻译成大白话:</span><br><span class="line">平均成绩:展开来说就是计算每个学生的平均成绩</span><br><span class="line">这里涉及到“每个”就是要分组了</span><br><span class="line">平均成绩大于60分,就是对分组结果指定条件</span><br><span class="line"></span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]</span><br><span class="line">from 从哪张表中查找数据 [成绩在成绩表中,所以查找的是成绩表score]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [平均成绩:先按学号分组,再计算平均成绩]</span><br><span class="line">having 对分组结果指定条件 [平均成绩大于60分]</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">-- 在成绩表中对学号进行分组求平均成绩 having条件是平均成绩>60分</span><br><span class="line">select s_id,AVG(s_score)</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY s_id</span><br><span class="line">HAVING AVG(s_score)>60</span><br><span class="line">ORDER BY AVG(s_score) DESC</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212111352401.png" alt="image-20191212111352401"></p><h4 id="9-查询至少选修两门课程的学生学号"><a href="#9-查询至少选修两门课程的学生学号" class="headerlink" title="9.查询至少选修两门课程的学生学号"></a>9.查询至少选修两门课程的学生学号</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">翻译成大白话:</span><br><span class="line">第1步,需要先计算出每个学生选修的课程数据,需要按学号分组</span><br><span class="line">第2步,至少选修两门课程:也就是每个学生选修课程数目>=2,对分组结果指定条件</span><br><span class="line"></span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [学号,每个学生选修课程数目:汇总函数count]</span><br><span class="line">from 从哪张表中查找数据 [课程的学生学号:课程表score]</span><br><span class="line">where 查询条件 [至少选修两门课程:需要先计算出每个学生选修了多少门课,需要用分组,所以这里没有where子句]</span><br><span class="line">group by 分组 [每个学生选修课程数目:按课程号分组,然后用汇总函数count计算出选修了多少门课]</span><br><span class="line">having 对分组结果指定条件 [至少选修两门课程:每个学生选修课程数目>=2]</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT s_id,COUNT(c_id) as 选修课程数目</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY c_id</span><br><span class="line">HAVING COUNT(c_id)>=2</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212130502309.png" alt="image-20191212130502309"></p><h4 id="10-查询同名同性学生名单并统计同名人数"><a href="#10-查询同名同性学生名单并统计同名人数" class="headerlink" title="10.查询同名同性学生名单并统计同名人数"></a>10.查询同名同性学生名单并统计同名人数</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">翻译成大白话,问题解析:</span><br><span class="line">1)查找出姓名相同的学生有谁,每个姓名相同学生的人数</span><br><span class="line">查询结果:姓名,人数</span><br><span class="line">条件:怎么算姓名相同?按姓名分组后人数大于等于2,因为同名的人数大于等于2</span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [姓名,人数:汇总函数count(*)]</span><br><span class="line">from 从哪张表中查找数据 [学生表student]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [姓名相同:按姓名分组]</span><br><span class="line">having 对分组结果指定条件 [姓名相同:count(*)>=2]</span><br><span class="line">order by 对查询结果排序[没有];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT s_name,COUNT(s_name)</span><br><span class="line">FROM student</span><br><span class="line">GROUP BY s_name</span><br><span class="line">HAVING COUNT(s_name)>=2</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212131157051.png" alt="image-20191212131157051"></p><h4 id="11-查询不及格的课程并按课程号从大到小排列"><a href="#11-查询不及格的课程并按课程号从大到小排列" class="headerlink" title="11.查询不及格的课程并按课程号从大到小排列"></a>11.查询不及格的课程并按课程号从大到小排列</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [课程号]</span><br><span class="line">from 从哪张表中查找数据 [成绩表score]</span><br><span class="line">where 查询条件 [不及格:成绩 <60]</span><br><span class="line">group by 分组 [没有]</span><br><span class="line">having 对分组结果指定条件 [没有]</span><br><span class="line">order by 对查询结果排序[课程号从大到小排列:降序desc];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT c_id</span><br><span class="line">FROM score</span><br><span class="line">WHERE s_score<60</span><br><span class="line">GROUP BY c_id</span><br><span class="line">ORDER BY c_id</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212131322831.png" alt="image-20191212131322831"></p><h4 id="12-查询每门课程的平均成绩,结果按平均成绩升序排序,平均成绩相同时,按课程号降序排列"><a href="#12-查询每门课程的平均成绩,结果按平均成绩升序排序,平均成绩相同时,按课程号降序排列" class="headerlink" title="12.查询每门课程的平均成绩,结果按平均成绩升序排序,平均成绩相同时,按课程号降序排列"></a>12.查询每门课程的平均成绩,结果按平均成绩升序排序,平均成绩相同时,按课程号降序排列</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [课程号,平均成绩:汇总函数avg(成绩)]</span><br><span class="line">from 从哪张表中查找数据 [成绩表score]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [每门课程:按课程号分组]</span><br><span class="line">having 对分组结果指定条件 [没有]</span><br><span class="line">order by 对查询结果排序[按平均成绩升序排序:asc,平均成绩相同时,按课程号降序排列:desc];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT c_id,AVG(s_score) as 平均成绩</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY c_id</span><br><span class="line">ORDER BY AVG(s_score) ASC,c_id DESC</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212131432805.png" alt="image-20191212131432805"></p><h4 id="13-检索课程编号为“0004”且分数小于60的学生学号,结果按按分数降序排列"><a href="#13-检索课程编号为“0004”且分数小于60的学生学号,结果按按分数降序排列" class="headerlink" title="13.检索课程编号为“0004”且分数小于60的学生学号,结果按按分数降序排列"></a>13.检索课程编号为“0004”且分数小于60的学生学号,结果按按分数降序排列</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 []</span><br><span class="line">from 从哪张表中查找数据 [成绩表score]</span><br><span class="line">where 查询条件 [课程编号为“04”且分数小于60]</span><br><span class="line">group by 分组 [没有]</span><br><span class="line">having 对分组结果指定条件 []</span><br><span class="line">order by 对查询结果排序[查询结果按按分数降序排列];</span><br><span class="line">*/</span><br><span class="line">select s_id,s_score</span><br><span class="line">FROM score</span><br><span class="line">WHERE c_id = "0004" AND s_score >=60</span><br><span class="line">ORDER BY s_score DESC</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212131500424.png" alt="image-20191212131500424"></p><h4 id="14-统计每门课程的学生选修人数-超过2人的课程才统计"><a href="#14-统计每门课程的学生选修人数-超过2人的课程才统计" class="headerlink" title="14.统计每门课程的学生选修人数(超过2人的课程才统计)"></a>14.统计每门课程的学生选修人数(超过2人的课程才统计)</h4><p>要求输出课程号和选修人数,查询结果按人数降序排序,若人数相同,按课程号升序排序</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/* </span><br><span class="line">分析思路</span><br><span class="line">select 查询结果 [要求输出课程号和选修人数]</span><br><span class="line">from 从哪张表中查找数据 []</span><br><span class="line">where 查询条件 []</span><br><span class="line">group by 分组 [每门课程:按课程号分组]</span><br><span class="line">having 对分组结果指定条件 [学生选修人数(超过2人的课程才统计):每门课程学生人数>2]</span><br><span class="line">order by 对查询结果排序[查询结果按人数降序排序,若人数相同,按课程号升序排序];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">SELECT c_id,COUNT(score.s_id) as '选修人数'</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY c_id</span><br><span class="line">HAVING COUNT(score.s_id) > 2</span><br><span class="line">ORDER BY COUNT(score.s_id) DESC,c_id ASC</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212134936442.png" alt="image-20191212134936442"></p><h4 id="15-查询两门以上不及格课程的同学的学号及其平均成绩"><a href="#15-查询两门以上不及格课程的同学的学号及其平均成绩" class="headerlink" title="15.查询两门以上不及格课程的同学的学号及其平均成绩"></a>15.查询两门以上不及格课程的同学的学号及其平均成绩</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">分析思路</span><br><span class="line">先分解题目:</span><br><span class="line">1)[两门以上][不及格课程]限制条件</span><br><span class="line">2)[同学的学号及其平均成绩],也就是每个学生的平均成绩,显示学号,平均成绩</span><br><span class="line">分析过程:</span><br><span class="line">第1步:得到每个学生的平均成绩,显示学号,平均成绩</span><br><span class="line">第2步:再加上限制条件:</span><br><span class="line">1)不及格课程</span><br><span class="line">2)两门以上[不及格课程]:课程数目>2</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/* </span><br><span class="line">第1步:得到每个学生的平均成绩,显示学号,平均成绩</span><br><span class="line">select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]</span><br><span class="line">from 从哪张表中查找数据 [涉及到成绩:成绩表score]</span><br><span class="line">where 查询条件 [没有]</span><br><span class="line">group by 分组 [每个学生的平均:按学号分组]</span><br><span class="line">having 对分组结果指定条件 [没有]</span><br><span class="line">order by 对查询结果排序[没有];</span><br><span class="line">*/</span><br><span class="line">select 学号, avg(成绩) as 平均成绩</span><br><span class="line">from score</span><br><span class="line">group by 学号;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/* </span><br><span class="line">第2步:再加上限制条件:</span><br><span class="line">1)不及格课程</span><br><span class="line">2)两门以上[不及格课程]</span><br><span class="line">select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]</span><br><span class="line">from 从哪张表中查找数据 [涉及到成绩:成绩表score]</span><br><span class="line">where 查询条件 [限制条件:不及格课程,平均成绩<60]</span><br><span class="line">group by 分组 [每个学生的平均:按学号分组]</span><br><span class="line">having 对分组结果指定条件 [限制条件:课程数目>2,汇总函数count(课程号)>2]</span><br><span class="line">order by 对查询结果排序[没有];</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SELECT s_id,COUNT(s_score),avg(s_score) AS 平均成绩</span><br><span class="line">FROM score</span><br><span class="line">WHERE s_score<60</span><br><span class="line">GROUP BY s_id</span><br><span class="line">HAVING COUNT(s_score)>=2</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191212140305267.png" alt="image-20191212140305267"></p><h3 id="4-3-复杂查询"><a href="#4-3-复杂查询" class="headerlink" title="4.3.复杂查询"></a>4.3.复杂查询</h3><h4 id="16-查询所有课程成绩小于60分学生的学号、姓名"><a href="#16-查询所有课程成绩小于60分学生的学号、姓名" class="headerlink" title="16.查询所有课程成绩小于60分学生的学号、姓名"></a>16.查询所有课程成绩小于60分学生的学号、姓名</h4><p>这道题目知乎猴子解法是存在问题的。我这边提供我的理解吧。</p><blockquote><p>第一种做法:将学号分组 在每个学号组 找成绩最高值<60的学号</p></blockquote><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">SELECT score.s_id,student.s_name</span><br><span class="line">FROM score</span><br><span class="line">INNER JOIN student</span><br><span class="line">ON score.s_id = student.s_id</span><br><span class="line">GROUP BY s_id</span><br><span class="line">HAVING MAX(s_score) <60</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191215011833878.png" alt="image-20191215011833878"></p><p>第二种解法:</p><p>step1: 查询出学生课程数的统计量</p><p>step2: 查询出学生课程成绩<60分对应课程数的统计量</p><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">SELECT A.s_id,S.s_name</span><br><span class="line">FROM</span><br><span class="line"></span><br><span class="line">(SELECT s_id,COUNT(c_id) AS cnt</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY score.s_id) AS A</span><br><span class="line"></span><br><span class="line">INNER JOIN</span><br><span class="line"></span><br><span class="line">(SELECT s_id,COUNT(c_id) AS cnt</span><br><span class="line">FROM score</span><br><span class="line">WHERE s_score<60</span><br><span class="line">GROUP BY s_id) AS B</span><br><span class="line"></span><br><span class="line">ON A.s_id = B.s_id</span><br><span class="line"></span><br><span class="line">INNER JOIN student AS S</span><br><span class="line">ON A.s_id = S.s_id</span><br><span class="line"></span><br><span class="line">WHERE A.cnt = B.cnt</span><br></pre></td></tr></table></figure></div><p><img src="/2019/12/03/SQL语句面试经典50题/image-20191216100937996.png" alt="image-20191216100937996"></p><h4 id="17、查询没有学全所有课的学生的学号、姓名"><a href="#17、查询没有学全所有课的学生的学号、姓名" class="headerlink" title="17、查询没有学全所有课的学生的学号、姓名"></a>17、查询没有学全所有课的学生的学号、姓名</h4><div class="highlight-wrap"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">-- 第一种解法:</span><br><span class="line">-- 首先学号分组 做having条件:每个学号统计出不同课程数量 小于 课程表中不同课程数量</span><br><span class="line">-- 上面结果为没有学全所有课程的学号</span><br><span class="line">-- 然后在学生表中 WHERE s_id IN ()条件 查找出学号、姓名。(这一步也可以用 INNER JOIN ON。)</span><br><span class="line"></span><br><span class="line">SELECT s_id,s_name FROM student</span><br><span class="line">WHERE s_id IN</span><br><span class="line">(</span><br><span class="line">SELECT s_id</span><br><span class="line">FROM score</span><br><span class="line">GROUP BY s_id</span><br><span class="line">HAVING COUNT(DISTINCT c_id) < (SELECT COUNT(DISTINCT c_id) FROM course)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">-- 这种解法后来发现score表会存在一些学生没有成绩。比如说王菊同学一门课程成绩都不存在。</span><br><span class="line">-- 但是上述做法没有把王菊同学查找出来。</span><br><span class="line"></span><br><span class="line">-- 第二种解法:</span><br></pre></td></tr></table></figure></div><h4 id="18、查询出只选修了两门课程的全部学生的学号和姓名"><a href="#18、查询出只选修了两门课程的全部学生的学号和姓名" class="headerlink" title="18、查询出只选修了两门课程的全部学生的学号和姓名"></a>18、查询出只选修了两门课程的全部学生的学号和姓名</h4><h4 id="1990年出生的学生名单"><a href="#1990年出生的学生名单" class="headerlink" title="1990年出生的学生名单"></a>1990年出生的学生名单</h4><h4 id="查询各科成绩前两名的记录"><a href="#查询各科成绩前两名的记录" class="headerlink" title="查询各科成绩前两名的记录"></a>查询各科成绩前两名的记录</h4><h4 id="分组取每组最大值、最小值,每组最大的N条(top-N)记录。"><a href="#分组取每组最大值、最小值,每组最大的N条(top-N)记录。" class="headerlink" title="分组取每组最大值、最小值,每组最大的N条(top N)记录。"></a>分组取每组最大值、最小值,每组最大的N条(top N)记录。</h4><h3 id="4-4-sql面试题:topN问题"><a href="#4-4-sql面试题:topN问题" class="headerlink" title="4.4 sql面试题:topN问题"></a>4.4 sql面试题:topN问题</h3><h3 id="4-5-多表查询"><a href="#4-5-多表查询" class="headerlink" title="4.5 多表查询"></a>4.5 多表查询</h3><p> <img src="/2019/12/03/SQL语句面试经典50题/v2-d3517e45d5a4ca25655133b714486550_hd.jpg" alt="img"> </p><h4 id="查询所有学生的学号、姓名、选课数、总成绩"><a href="#查询所有学生的学号、姓名、选课数、总成绩" class="headerlink" title="查询所有学生的学号、姓名、选课数、总成绩"></a>查询所有学生的学号、姓名、选课数、总成绩</h4><h4 id="查询平均成绩大于85的所有学生的学号、姓名和平均成绩"><a href="#查询平均成绩大于85的所有学生的学号、姓名和平均成绩" class="headerlink" title="查询平均成绩大于85的所有学生的学号、姓名和平均成绩"></a>查询平均成绩大于85的所有学生的学号、姓名和平均成绩</h4><h4 id="查询学生的选课情况:学号,姓名,课程号,课程名称"><a href="#查询学生的选课情况:学号,姓名,课程号,课程名称" class="headerlink" title="查询学生的选课情况:学号,姓名,课程号,课程名称"></a>查询学生的选课情况:学号,姓名,课程号,课程名称</h4><h4 id="查询出每门课程的及格人数和不及格人数"><a href="#查询出每门课程的及格人数和不及格人数" class="headerlink" title="查询出每门课程的及格人数和不及格人数"></a>查询出每门课程的及格人数和不及格人数</h4><h4 id="使用分段-100-85-85-70-70-60-lt-60-来统计各科成绩,分别统计:各分数段人数,课程号和课程名称"><a href="#使用分段-100-85-85-70-70-60-lt-60-来统计各科成绩,分别统计:各分数段人数,课程号和课程名称" class="headerlink" title="使用分段[100-85],[85-70],[70-60],[<60]来统计各科成绩,分别统计:各分数段人数,课程号和课程名称"></a>使用分段[100-85],[85-70],[70-60],[<60]来统计各科成绩,分别统计:各分数段人数,课程号和课程名称</h4><h4 id="查询课程编号为0003且课程成绩在80分以上的学生的学号和姓名"><a href="#查询课程编号为0003且课程成绩在80分以上的学生的学号和姓名" class="headerlink" title="查询课程编号为0003且课程成绩在80分以上的学生的学号和姓名|"></a>查询课程编号为0003且课程成绩在80分以上的学生的学号和姓名|</h4><h3 id="4-6-sql面试题:行列如何互换?"><a href="#4-6-sql面试题:行列如何互换?" class="headerlink" title="4.6 sql面试题:行列如何互换?"></a>4.6 sql面试题:行列如何互换?</h3><p>下面是学生的成绩表(表名score,列名:学号、课程号、成绩)</p><p><img src="/2019/12/03/SQL语句面试经典50题/v2-4d2a6f3afb29d0a5b596a4fd57f82cd0_hd.jpg" alt="img"></p><p>使用sql实现将该表行转列为下面的表结构</p><p><img src="/2019/12/03/SQL语句面试经典50题/v2-a628c7d239662b9bee8e1a3892810861_hd.jpg" alt="img"></p><p>【面试题类型总结】这类题目属于行列如何互换,解题思路如下:</p>]]></content>
<summary type="html">
<p>SQL语句面试经典50题</p>
<hr>
<p>2019/12/03 19:36 今天更新到这</p>
<p>2019/12/04 22:05 项目中完成了表单中的上传下载接口与前端对接。今天加班到八点,话说加班可以调休。希望过年调几天。</p>
<p>2
</summary>
<category term="SQL" scheme="http://enfangzhong.github.io/categories/SQL/"/>
<category term="SQL" scheme="http://enfangzhong.github.io/tags/SQL/"/>
</entry>
<entry>
<title>秋招的那些事</title>
<link href="http://enfangzhong.github.io/2019/11/11/%E7%A7%8B%E6%8B%9B%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/"/>
<id>http://enfangzhong.github.io/2019/11/11/秋招的那些事/</id>
<published>2019-11-11T03:03:11.000Z</published>
<updated>2019-12-29T13:21:35.793Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="秋招的那些事"><a href="#秋招的那些事" class="headerlink" title="秋招的那些事"></a>秋招的那些事</h1><h3 id="一、秋招"><a href="#一、秋招" class="headerlink" title="一、秋招"></a>一、秋招</h3><h4 id="1-1-提前批"><a href="#1-1-提前批" class="headerlink" title="1.1 提前批"></a>1.1 提前批</h4><p>博主在秋招之前参与了提前批的神仙打架。根据记忆提前批就面了字节跳动、京东、浦发银行,当时投递的是推荐算法,没有准备好就上战场了。</p><h4 id="1-2-秋招"><a href="#1-2-秋招" class="headerlink" title="1.2 秋招"></a>1.2 秋招</h4><p>每年秋招是9-11月这个阶段,一般互联网公司都集中在9月。10月还存在一些公司鞭尸。毕竟offer收割机都是手握多个offer。</p><p><img src="/2019/11/11/秋招的那些事/image-20191130222614545.png" alt="image-20191130222614545"></p><h3 id="二、实习"><a href="#二、实习" class="headerlink" title="二、实习"></a>二、实习</h3><blockquote><p>2.1 博主研二下期间(3-5月)还在写小paper。花了时间准备一篇专利,只是<del>~</del>~~~~。错过了实习的金三银四。</p><p>2.2 搞完论文大概是5月份了吧,当时全身心的投入到了算法与大数据的知识海洋中了,没有参与实习。</p><p>2.3 我实习在武汉最湿冷的时候。出不出太阳,全看心情。偶尔昨天气温20度,今天就5度。平时下下小雨。</p><p>2.4 南湖大道与雄庄路的十字路口总是那么的拥挤。每天早上循环播放着下一站茶山刘by房东的猫度过。</p></blockquote><p> </p><p>北京朋友的朋友圈全是雪雪雪。雪花儿,武汉期待你的到来。</p><p><img src="/2019/11/11/秋招的那些事/image-20191130224535426.png" alt="拥堵的南湖大道与雄庄路的十字路口"></p><p> 听说今天北京下雪了。雪花儿,我在武汉等你的到来。</p><p><img src="/2019/11/11/秋招的那些事/image-20191130230241101.png" alt="image-20191130230241101"></p><p>——————————————————2019/11/30 夜深了,今天暂且写到这咯。</p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1 id="秋招的那些事"><a href="#秋招的那些事" class="headerlink" title="秋招的那些事"></a>秋招的那些事</h1><h3 id="一、秋招"><a href="#一、秋招" class="headerl
</summary>
<category term="秋招" scheme="http://enfangzhong.github.io/categories/%E7%A7%8B%E6%8B%9B/"/>
<category term="面试" scheme="http://enfangzhong.github.io/categories/%E7%A7%8B%E6%8B%9B/%E9%9D%A2%E8%AF%95/"/>
<category term="秋招" scheme="http://enfangzhong.github.io/tags/%E7%A7%8B%E6%8B%9B/"/>
<category term="面试" scheme="http://enfangzhong.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
</entry>
<entry>
<title>个性化推荐算法实践第12章推荐算法回顾与总结</title>
<link href="http://enfangzhong.github.io/2019/06/01/%E4%B8%AA%E6%80%A7%E5%8C%96%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95%E5%AE%9E%E8%B7%B5%E7%AC%AC12%E7%AB%A0%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95%E5%9B%9E%E9%A1%BE%E4%B8%8E%E6%80%BB%E7%BB%93/"/>
<id>http://enfangzhong.github.io/2019/06/01/个性化推荐算法实践第12章推荐算法回顾与总结/</id>
<published>2019-06-01T14:03:11.000Z</published>
<updated>2019-12-18T06:23:59.355Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="个性化推荐算法实践第12章推荐算法回顾与总结"><a href="#个性化推荐算法实践第12章推荐算法回顾与总结" class="headerlink" title="个性化推荐算法实践第12章推荐算法回顾与总结"></a>个性化推荐算法实践第12章推荐算法回顾与总结</h1><p> 推荐算法实战课程,课程是有问答专区,如果你有问题可以在问答专区提问,我会在每天固定时间解答课程,结合问答专区能够让你更快的掌握知识。</p><p>开始本章节的内容之前我们首先来回顾一下上一章节的内容,上一章节我们对之前所讲述过的排序部分的内容进行了总结与回顾。</p><p>本章节我们对课程所讲述过的全部内容进行总结与回顾,下面一起来看一下本章节的内容大纲。</p><p>一、个性化推荐算法离线架构</p><p>个性化推荐算法离线架构,无论是在个性化召回部分还是在个性化排序部分,我们都有一套离线处理,得到我们模型的流程,我们将这套流程的抽象一下给大家解析一下。</p><p>二、个性化推荐算法在线架构</p><p>无论是在个性化召回部分还是在个性化排序不分,我们都曾经将在线部分的架构呢,给大家讲解过,这里我们一起回顾一下。</p><p>三、本课程所讲述过的算法模型的内容回顾</p><h3 id="一、个性化推荐算法离线架构"><a href="#一、个性化推荐算法离线架构" class="headerlink" title="一、个性化推荐算法离线架构"></a>一、个性化推荐算法离线架构</h3><p>下面一起来看一下离线架构。</p><p><img src="/2019/06/01/个性化推荐算法实践第12章推荐算法回顾与总结/1566830509493.png" alt="1566830509493"></p><p>我们原始的日志呢,包含以下四个部分。</p><p>①用户的点击与用户的展示日志。</p><p>②我们记录用户信息的日志,这里包含用户的静态信息和统计的一些动态的信息,包括用户喜欢什么样的类型的内容等等。</p><p>③item的信息</p><p>④实时流式信息(Streaming)。这里的流式是指包含用户的一些实时行为。比如说用户订阅某个频道、用户将某些物品加入到购物车等等等等。</p><p>基于以上的日志,我们首先进行样本的筛选,我们将测试样本有噪声的样本的剔除掉得到我们的样本,得到样本之后呢,我们再进行特征选择。</p><p>在个性化召回部分,可能我们只是更多的需要我们的点击与展示数据。但是在我们的个性化排序部分,可能我们还需要用户的信息(user info),项目的信息(item info)以及上下文信息等等的一些信息。</p><p>基于我们的特征选择与样本选择之后呢,我们得到了我们的训练数据集以及测试数据,接下来无论是在召回部分还是在排序部分,我们都使用的训练数据集去训练模型,使用我们的测试数据去评估我们模型的表现。如果我们模型的表现达到了我们的要求,我们就将我们得到的模型文件的导出。</p><p>这里在个性化召回部分呢,可能是导出的直接用户的推荐结果,也可能是item相似度的列表亦或是我们的深度学习的模型文件。</p><p>在排序部分我们得到的模型文件的基本都是模型的实例化本身。像逻辑回归部分呢,其实我们只需要得到不同特征对应的参数即可。</p><h3 id="二、个性化推荐算法在线架构"><a href="#二、个性化推荐算法在线架构" class="headerlink" title="二、个性化推荐算法在线架构"></a>二、个性化推荐算法在线架构</h3><h5 id="1、Recall在线架构"><a href="#1、Recall在线架构" class="headerlink" title="1、Recall在线架构"></a>1、Recall在线架构</h5><p><img src="/2019/06/01/个性化推荐算法实践第12章推荐算法回顾与总结/1566831234657.png" alt="1566831234657"></p><p>说完了离线架构,下面我们来一起回顾一下在线架构的召回部分,大部分情况下我们召回部分得到的结果呢,是直接写入到KV存储当中了。用户访问我们的Server的时候呢,直接召回到自己对应的推荐结果,我们拿到推荐结果对应的id呢,从我们的Detail Server当中的获取Detail 传给我们的排序部分,但是呢在一些复杂场景情况下,我们比如训练了一些深度学习的模型,那么我们在用户访问我们的Server的时候呢,我们首先得需要拼接一下用户侧(user info)的特征,然后呢访问我们的Server得到用户的向量表示然后再进行召回。</p><h5 id="2、Rank在线架构"><a href="#2、Rank在线架构" class="headerlink" title="2、Rank在线架构"></a>2、Rank在线架构</h5><p>说完了个性化召回的在线架构的,下面我们来一起看一下排序部分在线架构。排序部分在线架构根据我们模型的不同的分为以下几种。</p><p><img src="/2019/06/01/个性化推荐算法实践第12章推荐算法回顾与总结/1566832185932.png" alt="1566832185932"></p><p>1、如果是像<strong>逻辑回归</strong>或者是<strong>GBDT</strong>这种浅层模型。我们这里的Rank Server可以直接将模型加载到内存当中,与我们的推荐引擎的进行服务的交互。用户访问我们的Server的时候,我们首先召回得到我们的候选集列表,对于每一个候选集列表,那我们去KV当中的拼一下我们的特征,就是获得我们用户侧(user info)的特征以及项目侧(item info)的特征包括一些上下文特征等等。我们将拼接的特征的传递给让给Rank Server。Rank Server用模型来进行下预测,将预测的结果呢再传递给我们的推荐引擎。推荐引擎的依据我们每一个item预测的得分的进行一下排序,这个顺序呢就是展示给用户的顺序。</p><p>2、如果我们这里采用像WD这样的深度学习模型的话,我们这里的Rank Server需要与我们的TF Server进行交互,这里的Rank Server相当于我们这里的请求的透传,并且返回的结果呢,也透传给我们的推荐引擎。</p><h3 id="三、本课程所讲述过的算法模型的内容回顾"><a href="#三、本课程所讲述过的算法模型的内容回顾" class="headerlink" title="三、本课程所讲述过的算法模型的内容回顾"></a>三、本课程所讲述过的算法模型的内容回顾</h3><p>回顾完了在线架构,我们来一起看一下本课程具体讲解了哪些算法与模型?</p><p>首先来看个性化召回部分,这里我们介绍了基于领域的CF、LFM、Person Rank 、Item2Vec、ContentBased。我们CF是在个性化推荐算法实战入门必修课里介绍的,入门必修课是一门免费的课程大家都可以看到。还有我们基于内容的推荐以及呢,我们这里基于深度学习的推荐Item2Vec。</p><p>我们在排序部分的介绍了浅层模型Rank LR(逻辑回归),浅层模型组合GBDT。介绍了浅层模型不同模型的组合:LR+GBDT的混合模型。最后我们介绍了深度学习模型WD。以上的每一个算法或者模型呢,我们都是从物理意义数学公式推导,代码实战等几个方面来给大家介绍的,那么好了,本章节的内容到这里就全部结束了。本章节重点回顾了我们本次课程所讲述的全部内容。到这里本次个性化推荐算法实战课程的全部内容就结束了,非常感谢大家对于本课程的认真学习。祝大家在本次课程中学习一切顺利。</p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1 id="个性化推荐算法实践第12章推荐算法回顾与总结"><a href="#个性化推荐算法实践第12章推荐算法回顾与总结" class="headerlink" title="个性化推荐算法实践第12章推荐算法回顾与总结"></a>个性化推荐算法
</summary>
<category term="推荐算法" scheme="http://enfangzhong.github.io/categories/%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95/"/>
<category term="推荐算法" scheme="http://enfangzhong.github.io/tags/%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95/"/>
</entry>
</feed>