-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathatom.xml
1266 lines (1072 loc) · 314 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
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Giraffe's Home</title>
<subtitle>A Stupid Giraffe</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yemengying.com/"/>
<updated>2018-12-13T03:33:20.000Z</updated>
<id>http://yemengying.com/</id>
<author>
<name>Mengying Ye</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>哈希与区块链</title>
<link href="http://yemengying.com/2018/02/11/hash-blockchain/"/>
<id>http://yemengying.com/2018/02/11/hash-blockchain/</id>
<published>2018-02-11T14:49:44.000Z</published>
<updated>2018-12-13T03:33:20.000Z</updated>
<content type="html"><blockquote>
<p>为什么要了解下区块链呢?因为区块链最近实在是太火了,火到我爸都听说了,总让我给他科普一下。。。。 </p>
</blockquote>
<a id="more"></a>
<p>每次跟他说我也不知道,他就一脸不信,可能觉得和软件,计算机搭边的我都能知道。<br><img src="https://pic.yupoo.com/jiananshi/4a978934/4e0fbec5.png" alt="不信三连"></p>
<p>马上要过年回家了,为了我的压岁钱,我准备先简单了解下,以便过年被问的时候不会再一脸懵逼。要了解区块链,就要先从在区块链技术中起着重要作用的哈希开始。</p>
<h2 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h2><ul>
<li><a href="https://blockgeeks.com/guides/what-is-hashing/" target="_blank" rel="external">What Is Hashing? Under The Hood Of Blockchain - Blockgeeks</a></li>
<li><a href="https://www.cs.upc.edu/~mjserna/docencia/grauA/P17/Crypto.pdf" target="_blank" rel="external">https://www.cs.upc.edu/~mjserna/docencia/grauA/P17/Crypto.pdf</a></li>
<li><a href="http://chimera.labs.oreilly.com/books/1234000001802/ch07.html" target="_blank" rel="external">http://chimera.labs.oreilly.com/books/1234000001802/ch07.html</a></li>
<li><a href="https://blockgeeks.com/guides/what-is-bitcoin-cash/" target="_blank" rel="external">https://blockgeeks.com/guides/what-is-bitcoin-cash/</a></li>
<li><a href="https://www.amazon.cn/%E5%8C%BA%E5%9D%97%E9%93%BE-%E6%8A%80%E6%9C%AF%E9%A9%B1%E5%8A%A8%E9%87%91%E8%9E%8D-%E7%BE%8E-%E9%98%BF%E5%B0%94%E6%96%87%E5%BE%B7%C2%B7%E7%BA%B3%E6%8B%89%E4%BA%9A%E5%8D%97/dp/B073QHSM7P?psc=1" target="_blank" rel="external">《区块链技术驱动金融》</a></li>
</ul>
<h2 id="什么是哈希?"><a href="#什么是哈希?" class="headerlink" title="什么是哈希?"></a>什么是哈希?</h2><p>简单来说,哈希就是输入任意长度的字符串都可以产生固定大小的输出。在比特币这种加密货币中,交易就是输入,然后经过哈希算法(比特币采用的是 SHA - 256),产生固定长度的输出。</p>
<p>下面就是使用 SHA-256 算法的例子:</p>
<p><img src="https://pic.yupoo.com/jiananshi/3ec9133e/89977ce9.png" alt="SHA-256"></p>
<p>通过上面的例子可以看出,无论输入大或者小,输出都是固定256比特的长度。这一特性在处理大量数据和交易时是至关重要的。基于哈希这一特性,我们不用记输入数据是多么大,只需要记住hash值即可。</p>
<p>在我们进一步讨论之前,我们首先需要看看哈希函数的各种属性以及它们在区块链中的实现方式。</p>
<h2 id="加密哈希函数"><a href="#加密哈希函数" class="headerlink" title="加密哈希函数"></a>加密哈希函数</h2><p>加密哈希函数是一类特殊的哈希函数。为了让哈希函数达到密码安全,需要有以下几个特性:</p>
<h3 id="确定性-Deterministic"><a href="#确定性-Deterministic" class="headerlink" title="确定性(Deterministic)"></a>确定性(Deterministic)</h3><p>对于同一个输入,无论用哈希函数计算多少次,都会得到相同的结果。</p>
<h3 id="快速计算"><a href="#快速计算" class="headerlink" title="快速计算"></a>快速计算</h3><p>对于输入的字符串,能在合理的时间内算出哈希函数的输出,否则会影响系统的性能。</p>
<h3 id="隐秘性"><a href="#隐秘性" class="headerlink" title="隐秘性"></a>隐秘性</h3><p>如果我们已知字符串 A 的哈希值是 H(A),那么我们没有可行的办法算出 A 是什么。注意,这里说的是 “不可行” 而不是 “不可能”。 比如下面的例子中,知道输出哈希值是可以算出输入的。</p>
<p>假如我们掷骰子🎲,输出就是骰子上数字的哈希值。那么在知道输出的哈希值情况下,我们能否知道骰子上的数字呢?因为哈希函数是具有确定性的,相同输入的哈希值一定相同,所以我们只需计算 1-6 的哈希值是什么,然后对比就能知道骰子上的数字是什么了。</p>
<p>当然,我们能够根据哈希值猜出骰子的数字,是因为输入值只有 6 种可能性。如果我们的输入值来自一个分散的集合,那么想要通过输出推导出输入的唯一方法可能就是“暴力破解法”了。暴力破解就是,任意选择一个输入,计算其哈希值,与现有哈希值对比是否一致,不断重复这一过程,直到找到一个输入的哈希值与现有哈希值一致。</p>
<p>那么暴力破解法是否可行呢?假设我们现在处理的是128位的哈希值。<br><strong>最好的情况</strong>:第一次尝试就找到了答案,但这种情况可以说是几乎不可能的,比中大乐透还难。<br><strong>最坏的情况</strong>:在尝试 2^128 -1 次后得到了答案,也就是试过了所有可能的输入才找到。<br><strong>平均的情况</strong>: 在平均情况下,我们要尝试 2^128 / 2 = 2^127 次之后才能找到答案。2^127 = 1.7 X 10^38 , 也可以说是个天文数字了。</p>
<p>所以,在已知哈希值的情况下, 尽管可以通过暴力破解的方法找到输入的字符串是什么,但这会花费很长长长长长的时间,所以不用担心。</p>
<h3 id="抗篡改能力"><a href="#抗篡改能力" class="headerlink" title="抗篡改能力"></a>抗篡改能力</h3><p>对于任意一个输入,哪怕是很小的改动,其哈希改变也会非常大。比如 “This is a test” 对应的哈希值是<code>C7BE1ED902FB8DD4D48897C6452F5D7E509FBCDBE2808B16BCF4EDCE4C07D14E</code>, 而 “this is a test” 对应的哈希值是 <code>2E99758548972A8E8822AD47FA1017FF72F06F3FF6A016851F45C398732BC50C</code>。 </p>
<p>看上面的例子,即便只改变了输入字符串第一个字母的大小写,输出hash值也是完全不同的。用前段时间比较流行的区块链撸猫游戏类比一下,“This is a test” 的哈希值对应猫可能是这样的:</p>
<p><img src="https://pic.yupoo.com/jiananshi/f094a3f5/1640dc96.png" alt="喵~"></p>
<p>而只改了个大小写,“this is a test” 的哈希值对应猫可能就变成下面这样了:<br><img src="https://pic.yupoo.com/jiananshi/9146f91a/4cb2eb92.png" alt="喵喵~"></p>
<p>这一特性对于区块链来说十分重要,因为它决定了区块链是 immutable 的(不变的)。</p>
<h3 id="抗碰撞能力"><a href="#抗碰撞能力" class="headerlink" title="抗碰撞能力"></a>抗碰撞能力</h3><p>碰撞是指,对于相同的输入,经过哈希计算后产生了不同的输出。具有抗碰撞能力就是对于大部分的输入都有独一无二的输入。 这里说的是”大部分”,因为找不到碰撞,并不意味不存在碰撞。概率学中的生日悖论可以证明这一点。</p>
<blockquote>
<p>生日悖论: 指如果一个房间里有23个或23个以上的人,那么至少有两个人的生日相同的概率要大于50%。这就意味着在一个典型的标准小学班级(30人)中,存在两人生日相同的可能性更高。对于60或者更多的人,这种概率要大于99%。从引起逻辑矛盾的角度来说生日悖论并不是一种悖论,从这个数学事实与一般直觉相抵触的意义上,它才称得上是一个悖论。大多数人会认为,23人中有2人生日相同的概率应该远远小于50%。 </p>
</blockquote>
<p><img src="http://pic.yupoo.com/jiananshi/a6e044f3/d67f5e94.png" alt="生日悖论"></p>
<p>没有哪个哈希函数是完全具有防碰撞特性,但对于 SHA -256 之类的哈希函数,需要花费很长的时间来找到碰撞。所以我们完全可以认为 if H(A) = H(B) 那么 A=B.</p>
<h3 id="谜题友好"><a href="#谜题友好" class="headerlink" title="谜题友好"></a>谜题友好</h3><p>这一特性对加密货币来说至关重要(特别是在挖矿过程中)。先定义下什么是谜题友好。</p>
<blockquote>
<p>谜题友好: 如果对于任意 n 位输出值 y, 假定 k 选自高阶最小熵分布,如果无法找到一个可行的办法,在比 2^n 小很多的时间内找到 x , 保证 H(k|x) = y 成立,那么我们称哈希函数 H 具有谜题友好的特性。 </p>
</blockquote>
<p><strong>什么是高阶最小熵?</strong><br>高阶最小熵描述了分布的分散程度。在这样的分布中,任意数值被选定的概率的小到可以忽略不计的。举个例子,如果要从 1-5 中选择一个数,就是低阶最小熵的分布。如果从 1-无穷大中选择一个数,就是高阶最小熵的分布。</p>
<p><strong>’k|x’代表了什么?</strong><br>‘|’ 是连接符的意思,将两个字符串连接起来。举个例子’cute|giraffe’ = ‘cutegiraffe’。</p>
<p>再来回顾下谜题友好的定义。假设有一个 n 位输出值 y, 从高阶分布中选取一个任意值 k, 那么没有一个可行的办法,比 2^n 小很多的时间内找到 x ,使得 H(k| x) = y。</p>
<p>这里说的还是 “不可行”,而不是 “不可能”。整个的比特币采矿的过程就基于解谜。</p>
<p>下面是几个典型的加密哈希函数:</p>
<ul>
<li>MD5:产生 128 位哈希。</li>
<li>SHA-1:产生 160 位哈希。</li>
<li>SHA-256:产生 256 位哈希。也是比特币中使用的哈希函数。</li>
<li>Keccak-256:产生 256 位哈希,在 Ethereum 中使用。</li>
</ul>
<h2 id="哈希与数据结构"><a href="#哈希与数据结构" class="headerlink" title="哈希与数据结构"></a>哈希与数据结构</h2><p>如果想要理解区块链是怎样工作的,就必须要理解其中 3 种重要的数据结构 <strong>哈希指针</strong> , <strong>区块链</strong> 和 <strong>梅克尔树</strong>。</p>
<h3 id="哈希指针"><a href="#哈希指针" class="headerlink" title="哈希指针"></a>哈希指针</h3><p>与不同指针不同的是,哈希指针的值是通过数据计算出来的且指向数据所在位置,所以哈希指针可以告诉我们数据存储位置及数据的哈希值。通过哈希指针,我们可以很容易判断出数据是否有被篡改。</p>
<p>哈希指针在区块链中极为重要。区块链的结构就是由创世区块开始,之后的每个区块通过哈希指针进行连接。每一个区块中都包含了前一个区块的哈希指针,这样后面区块不仅可以查找到前面所有区块,也可以验证前面区块数据有没有被更改,从而保证了区块链不易篡改的特性。</p>
<p>哈希指针在区块链中第二个用处就是构建Merkle Tree(梅克尔树),下文会详细讲。</p>
<h3 id="区块链"><a href="#区块链" class="headerlink" title="区块链"></a>区块链</h3><p>区块链是一个基于哈希指针构建的一个有序的,反向链接的交易块链表,也就是说在区块链中每个区块都通过哈希指针连接到前一个区块上。大致结构如下图:</p>
<p><img src="https://pic.yupoo.com/jiananshi/a8ee7066/48a107a4.png" alt="block chain"></p>
<p>区块链也常被看做一个垂直的堆栈,区块在栈顶一次追加,第一个区块也就是整个堆栈的基础。所以也常常用“高度”(height) 这个词来描述某个区块到第一个区块的距离。</p>
<p>区块链中的每一个区块都有一个对区块头部进行 SHA256 加密哈希函数计算得出的哈希值作为标识。由于每个区块需要连接到前一个区块,所以每个区块头部专门有一个字段用来存储前一个区块(也叫父区块)的哈希值。这样每个区块都连接到了他们的父区块,从而创建了区块链。</p>
<p>尽管每一个区块只能由一个父区块,但却可能短时间内拥有多个子区块。也就是说可能存在多个区块头部中存储的父区块的哈希值是一样的。这种情况一般发生在不同的区块在同一时间被不同的矿工找到。这样就会造成区块链的分叉,如下图:<br><img src="http://pic.yupoo.com/jiananshi/f6c0930f/fd6bf4c0.png" alt="区块链分叉"></p>
<p>不过区块链的分叉只是暂时,会根据“最长链原则”来解决分叉。不是本文重点不再赘述。</p>
<p>刚刚第二节提到哈希指针可以保证区块链不易被篡改,下面来分析下原因。</p>
<p>假设有一个黑客想要篡改上图中 区块 2 的数据。由于哈希函数具有抗篡改能力,很小的改动,输出的哈希值会大不一样。所以如果改动区块2上的数据,那么 区块3 自身的哈希会发生变化。 而 区块3 的头部中存储了 区块2 的哈希值,所以 对区块2 的改动必然会影响到 区块3,以此类推,区块3后面的区块也会受到影响。所以一个区块的修改会级联影响到它之后的所有区块,而要修改之后的所有区块需要强大的算力,可以说是不可能的,这也就保证了区块链的不变性。</p>
<p>仔细看下区块链中每个区块的结构。</p>
<p><strong>区块的结构</strong><br>区块的内部结构分为 头部, 元数据,和一系列的交易记录。头部大小是 80个字节,而一个交易记录至少要50个字节,平均每个区块包含超过500个交易记录。所以,一个完整的区块的大小一般是它头部大小的1000倍。 下图是一个区块的大致结构。</p>
<p><img src="https://pic.yupoo.com/jiananshi/2ea67637/38ecf1fb.png" alt="structure"></p>
<p><strong>区块的头部</strong><br>如上图,区块的头部的组成分为3大块。第一块是前面区块的哈希值,用于连接到父区块。第二块包含了一个随机数,一个点数(用来表示找到这个区块的难度),和一个时间戳,这三个字段都与挖矿的过程息息相关。第三块是梅克尔树(merkle tree )的树根,merkle tree 用来将区块内的所有交易以一种非常高效的形式组织起来。</p>
<p>merkle tree 相关内容下文会有涉及,随机数、点数和时间戳都是与挖矿相关的。</p>
<p><strong>区块的标识:区块头部哈希值和区块高度</strong><br>一个区块最主要的标识就是区块自身头部进行二次哈希计算产生的加密哈希值。区块链中第一个区块的哈希值就是 <code>000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</code> 。</p>
<p>区块的另一个标识是它在区块链当中的位置。需要注意的是一个区块只能有一个高度,但在区块链存在分叉的情况下,可能存在多个区块具有一样的高度。这时,区块的高度就不能作为唯一标识了。</p>
<p><strong>第一个区块 : 创世区块</strong><br>区块链中的第一个区块是2009年创建的,叫做“创世区块”。它是区块链中所有区块的祖先,从任何一个区块向前追溯,最终都会到达创世区块。</p>
<p>通过 <code>blockchain.info</code> 之类的网站查看创世区块的内容 <code>https://blockchain.info/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</code><br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line"> <span class="attr">"hash"</span> : <span class="string">"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"</span>,</div><div class="line"> <span class="attr">"confirmations"</span> : <span class="number">308321</span>,</div><div class="line"> <span class="attr">"size"</span> : <span class="number">285</span>,</div><div class="line"> <span class="attr">"height"</span> : <span class="number">0</span>,</div><div class="line"> <span class="attr">"version"</span> : <span class="number">1</span>,</div><div class="line"> <span class="attr">"merkleroot"</span> : <span class="string">"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"</span>,</div><div class="line"> <span class="attr">"tx"</span> : [</div><div class="line"> <span class="string">"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"</span></div><div class="line"> ],</div><div class="line"> <span class="attr">"time"</span> : <span class="number">1231006505</span>,</div><div class="line"> <span class="attr">"nonce"</span> : <span class="number">2083236893</span>,</div><div class="line"> <span class="attr">"bits"</span> : <span class="string">"1d00ffff"</span>,</div><div class="line"> <span class="attr">"difficulty"</span> : <span class="number">1.00000000</span>,</div><div class="line"> <span class="attr">"nextblockhash"</span> : <span class="string">"00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"</span></div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>比特币的创始人中本聪还在创世区块里留下一句永不可修改的话“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks” (2009年1月3日,财政大臣正处于实施第二轮银行紧急援助的边缘), 这句话是泰晤士报当天的头版文章标题。</p>
<p><strong>连接区块到区块链</strong><br>比特币中每一个节点都存储了从创世区块开始的区块链的本地副本,本地区块链的副本会在新的区块被发现并且添加到区块链后更新。当一个节点从网络接收到一个区块时,会验证该区块,验证通过后将其添加到已有区块链上。</p>
<p>为了建立连接,每个节点都会检验接收到的区块的头部,找到头部中存储的父区块的哈希值。举个例子,一个节点的区块链中有 277314 个区块,最后一个区块头部的哈希值计算出是<code>00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249</code>。</p>
<p>这时节点接收到一个新的区块,内容如下:<br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line">&#123;</div><div class="line"> <span class="attr">"size"</span> : <span class="number">43560</span>,</div><div class="line"> <span class="attr">"version"</span> : <span class="number">2</span>,</div><div class="line"> <span class="attr">"previousblockhash"</span> :</div><div class="line"> <span class="string">"00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249"</span>,</div><div class="line"> <span class="attr">"merkleroot"</span> :</div><div class="line"> <span class="string">"5e049f4030e0ab2debb92378f53c0a6e09548aea083f3ab25e1d94ea1155e29d"</span>,</div><div class="line"> <span class="attr">"time"</span> : <span class="number">1388185038</span>,</div><div class="line"> <span class="attr">"difficulty"</span> : <span class="number">1180923195.25802612</span>,</div><div class="line"> <span class="attr">"nonce"</span> : <span class="number">4215469401</span>,</div><div class="line"> <span class="attr">"tx"</span> : [</div><div class="line"> <span class="string">"257e7497fb8bc68421eb2c7b699dbab234831600e7352f0d9e6522c7cf3f6c77"</span>,</div><div class="line"></div><div class="line"> #[... many more transactions omitted ...]</div><div class="line"></div><div class="line"> <span class="string">"05cfd38f6ae6aa83674cc99e4d75a1458c165b7ab84725eda41d018a09176634"</span></div><div class="line"> ]</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>在验证完成之后,节点会先找到新区块头部中存储的 父区块 的哈希值,比较与最后一个区块的哈希值是否一致。是一致的就把新区块连接到区块链上,这时区块链的高度就变为了277315。</p>
<h3 id="什么是梅克尔树?"><a href="#什么是梅克尔树?" class="headerlink" title="什么是梅克尔树?"></a>什么是梅克尔树?</h3><p>在区块的头部中,有存储一个梅克尔树根的 hash 值。所以先来了解下什么是梅克尔树。</p>
<p><img src="https://pic.yupoo.com/jiananshi/c6e23447/2bcf1f9a.png" alt="merkle tree"></p>
<p>上图就是梅克尔树的样子。</p>
<p>梅克尔树在区块链中用于组织和记录存储在区块中的交易,以便高效的验证某个交易是否存在在区块中。梅克尔树是通过不断的递归计算节点的哈希值直到只有一个hash值来构建的。</p>
<p>当梅克尔树中有N个数据时,最多只需要2*log2(N)计算就可以验证某个特定数据是否存在,所以梅克尔树是相当高效的。</p>
<p>梅克尔树是自底向上构建的。举个例子,假设我们现在有 A, B, C,D四笔交易需要存储记录在区块中,来看下是如何构建梅克尔树的。 首先要用 A,B,C,D 来构建树的叶子节点,将他们二次哈希后的哈希值存储在是叶子节点,就是上图中的 HA,HB,HC,HD。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">H(A) = SHA256(SHA256(A))</div></pre></td></tr></table></figure></p>
<p>接着再用相邻两个结点的hash值连接在一起经过二次哈希计算来构建它们的父节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">H(AB) = SHA256(SHA256(H(A) + H(B)))</div></pre></td></tr></table></figure></p>
<p>这一过程一直重复到只剩下顶层的一个节点,也就是存储在区块头部的树的根节点。</p>
<p>因为梅克尔树是一棵二叉树,它需要偶数个叶子节点。如果恰好是奇数个交易,那么最后一笔交易会被复制一遍,来创造偶数个叶子节点,以便达到平衡。下图中的交易 C 就被复制了一遍。</p>
<p><img src="http://pic.yupoo.com/jiananshi/17ae7320/1caf2511.png" alt=""></p>
<p>根据上面的方式我们可以为任意个数的交易构建梅克尔树,一个区块通常要记录几百到上千的交易。</p>
<p>为了验证一笔交易是否包含在区块中,节点只需要 计算 log2(N) 个哈希值,组成该交易到merkle树根的认证路径即可。正因为梅克尔树,区块链中的节点可以快速的产生一条包含10或12个哈希值的认证路径,来证明在区块中上千笔交易中某笔交易的存在。</p>
<h2 id="哈希与挖矿"><a href="#哈希与挖矿" class="headerlink" title="哈希与挖矿"></a>哈希与挖矿</h2><p>我们说的“挖矿”,就是指找到可以加入区块链的新的区块。矿工们要不断的工作在确保区块链的增长的同时获到新区块的奖励。早些时候,人们往往使用笔记本电脑挖矿,但随着时间的推移,大家开始组成矿池以便集中算力更高效的挖矿。</p>
<p>这里存在一个问题,每种加密货币都有一个上限,比如,比特币一共有2100万个。如果找到新区块的速度过快,那么很快所有的比特币就被挖完了。所以,需要控制找到新区块的速度。对于比特币,新区块创建的时间间隔被控制在10分钟左右。</p>
<p>为了控制区块的创建速度,设置了一个目标值。一个有效区块的头部哈希值必须要小于目标值。目标值是一个以一串0开头的64位的字符串,开头的0越多难度越大,每新产生2016个区块之后目标值会调整一次。区块的头部有个随机数的字段,其实挖矿的过程也就是找到一个可以使区块头部哈希值小于目标值的随机数的过程,也叫做解迷。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>哈希在区块链技术中是最基础的。如果想要了解区块链是什么,就必须要了解什么是哈希,它有什么特性,在区块链中起着什么作用。</p>
</content>
<summary type="html">
<blockquote>
<p>为什么要了解下区块链呢?因为区块链最近实在是太火了,火到我爸都听说了,总让我给他科普一下。。。。 </p>
</blockquote>
</summary>
<category term="哈希" scheme="http://yemengying.com/categories/%E5%93%88%E5%B8%8C/"/>
<category term="哈希" scheme="http://yemengying.com/tags/%E5%93%88%E5%B8%8C/"/>
</entry>
<entry>
<title>【译】Spring MVC 中的 DispatcherServlet</title>
<link href="http://yemengying.com/2017/10/07/spring-dispatcherServlet/"/>
<id>http://yemengying.com/2017/10/07/spring-dispatcherServlet/</id>
<published>2017-10-07T12:58:19.000Z</published>
<updated>2018-12-13T03:46:31.000Z</updated>
<content type="html"><blockquote>
<p>看白夜追凶看的,写个博客总感觉身后有人。。。。。</p>
</blockquote>
<a id="more"></a>
<h2 id="原文链接"><a href="#原文链接" class="headerlink" title="原文链接"></a>原文链接</h2><p><a href="http://javarevisited.blogspot.jp/2017/09/dispatcherservlet-of-spring-mvc-10-points-to-remember.html#axzz4ujB6NAWf" target="_blank" rel="external">原文</a></p>
<p>如果经常与 Spring MVC 打交道,那么很有必要了解什么是 DispatcherServlet。它是 Spring MVC 的核心,准确的说就是 MVC 设计模式中的 C 或 Controller。每个由 Spring MVC 处理的请求都要经过 DispatcherServlet。一般而言,它是前端控制器模式的实现,为应用提供一个统一入口。DispatcherServlet 是连接 Java 与 Spring 的桥梁,处理所有传入的请求。并且与其他声明在 web.xml 中的 Servlet 一样,也是通过一个 URL pattern 将每个请求映射到 DispatcherServlet。</p>
<p>DispatcherServlet 负责将请求委派给 Spring MVC 中其他的组件处理,比如注有 @Controller 或 @RestController 的 Controller类,Handler Mappers(处理器映射),View Resolvers(视图解析器) 等等。</p>
<p>尽管,请求映射是由 @ResquestMapping 注解完成的,但实际上是由 DispatcherServlet 将请求委派给相应的 Controller 来处理的。</p>
<p>在 RESTFul 的 web 服务中, DispatcherServlet 还负责选择正确的信息转换器,以便将响应结果转换成客户端期望的格式(JSON, XML 或 TEXT)。比如,如果客户端期望 JSON 格式,那么会使用 <code>MappingJacksonHttpMessageConverter</code> 或 <code>MappingJackson2HttpMessageConverter</code> (取决于 classpath 中可用的是 Jackson 1 还是 Jackson 2) 来将响应结果转成 JSON 字符串的格式。</p>
<h2 id="DispatcherServlet-如何处理请求"><a href="#DispatcherServlet-如何处理请求" class="headerlink" title="DispatcherServlet 如何处理请求"></a>DispatcherServlet 如何处理请求</h2><p>正如上面所说,DispatcherServlet 被用来处理所有传入的请求,并将它们路由到不同的 Controller 来进行进一步处理。它决定了由哪个 Controller 处理请求。</p>
<p>DispatcherServlet 使用处理器映射来将传入的请求路由到处理器。默认情况下,使用 <code>BeanNameUrlHandlerMapping</code> 和 由 @RequestMapping 注解驱动的<code>DefaultAnnotationHandlerMapping</code>。</p>
<p>为了找到正确的方法来处理请求,它会扫描所有声明了 @Controller 注解的类,并且通过 @RequestMapping 注解找到负责处理该请求的方法。@RequestMapping 注解可以通过路径来映射请求(比如: <code>@RequestMapping(“path”)</code>), 也可以通过 HTTP 方法(比如: <code>@RequestMapping(&quot;path&quot;, method=RequestMethod.GET)</code>), 也可以通过请求参数(比如: <code>@RequestMapping(&quot;path&quot;”, method=RequestMethod.POST, params=&quot;param1”)</code>),还可以通过 HTTP 请求头(比如: <code>@RequestMapping(&quot;path&quot;, header=&quot;content-type=text/*”)</code>)。我们也可以在类级别声明 @RequestMapping 注解来过滤传入的请求。</p>
<p>在请求处理之后,Controller 会将逻辑视图的名字和 model 返回给 DispatcherServlet。之后利用视图解析器定位到真正的 View 以便渲染结果。我们可以指定使用的视图解析器,默认情况下,DispatcherServlet 使用 <a href="http://javarevisited.blogspot.jp/2017/08/what-does-internalresourceviewresolver-do-in-spring-mvc.html#axzz4unX8dLL4" target="_blank" rel="external">InternalResourceViewResolver</a>来将逻辑视图的名字转换成真正的视图,比如 JSP。</p>
<p>选定视图之后,DispatcherServlet 会将数据模型与视图相结合,并将结果返回给客户端。并不是任何时候都需要视图,比如一个 RESTful 的 web 服务就不需要,它们的处理方法会利用 @ResponseBody 注解直接将请求结果返回给客户端。可以看<a href="http://courses.baeldung.com/p/rest-with-spring-live-class?affcode=22136_bkwjs9xa" target="_blank" rel="external">REST with Spring course</a>了解更多关于如何使用 Spring MVC 开发和测试 RESTful 服务的知识。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在这篇文章中,我分享了一些关于 DispatcherServlet 比较重要的一些知识点。这些不仅可以帮助大家更好的理解 DispatcherServlet,也可以鼓励大家进一步去学习相关的知识。</p>
<ol>
<li>DispatcherServlet 是 Spring MVC 应用中主要的控制器。所有的请求都会先经由 DispatcherServlet 处理,再由 Controller (声明有 @Controller 注解的类) 处理。</li>
<li>DispatcherServlet 是前端控制器模式的实现。前端控制器就是个用来处理网站所有请求的控制器。</li>
<li><p>就像其他的 Servlet, DispatcherServlet 也是声明和配置在 web.xml 文件中的:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">web-app</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">servlet</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>SpringMVC<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">servlet-class</span>&gt;</span>org.springframework.web.servlet.DispatcherServlet<span class="tag">&lt;/<span class="name">servlet-class</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">load-on-startup</span>&gt;</span>1<span class="tag">&lt;/<span class="name">load-on-startup</span>&gt;</span> </div><div class="line"><span class="tag">&lt;/<span class="name">servlet</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">servlet-mapping</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>SpringMVC<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span> </div><div class="line"><span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>*<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span> </div><div class="line"><span class="tag">&lt;/<span class="name">servlet-mapping</span>&gt;</span> </div><div class="line"><span class="tag">&lt;/<span class="name">web-app</span>&gt;</span></div></pre></td></tr></table></figure>
</li>
<li><p>DispatcherServlet 继承自 HttpServlet 基类。Servlet 引擎(比如 Tomcat) 创建该类的实例,并且调用它不同的方法,比如:init(), service(), destroy()。</p>
</li>
<li>DispatcherServlet 为 Spring MVC 应用提供统一入口,处理所有的请求。</li>
<li>DispatcherServlet 也完全与 Spring IoC 容器集成,可以使用 Spring 框架的每一个特性,比如依赖注入。</li>
<li>当 DispatcherServlet 被配置为 <code>load-on-startup = 1</code>,意味着该 servlet 会在启动时由容器创建,而不是在请求到达时。这样做会降低第一次请求的响应时间,因为DispatcherServlet 会在启动时做大量工作,包括扫描和查找所有的 Controller 和 RequestMapping。</li>
<li>在 DispatcherServlet 初始化期间,Spring 框架会在 WEB-INF 文件夹中查找名为 [servlet-name]-servlet.xml 的文件,并创建相应的 bean。比如,如果 servlet 像上面 web.xml 文件中配置的一样,名为 “SpringMVC”,那么会查找 “SpringMVC-Servlet.xml”的文件。如果全局作用域中有相同名字的bean,会被覆盖。可以用 servlet 初始化参数 <code>contextConfigLocation</code>更改配置文件的位置。<br><img src="http://oduh99f02.bkt.clouddn.com/2017-10-08-Desktop%20HD.png" alt="Dispatcher Servlet flow"></li>
<li>在 Spring MVC 框架中,每个 DispatcherServlet 都有它自己的 WebApplicationContext ,并且继承了根 WebApplicationContext 中定义的所有 bean。这些继承的 bean 在 servlet 指定的作用域中可以被重载,也可以在其指定作用域中定义新的 bean。</li>
<li>Spring MVC 中的 DispatcherServlet也允许返回 Servlet API 定义的 last-modification-date。为了决定请求最后修改时间,DispatcherServlet会先查找合适的 handler mapping,然后检测处理器是否实现了 LastModified 接口。如果实现了,就调用接口的 getLastModified(request) 方法,并将该值返回给客户端。</li>
</ol>
<p>以上就是关于 DispatcherSerlvet 的内容。正如上面所讲,DispacherServlet 是 Spring MVC 的骨干,是主要的控制器,用来将不同的 HTTP 请求路由当相应的 Controller。它是前端控制器设计模式的实现,并且为应用提供单一入口。可以在 web.xml 中配置 DispatcherServlet,但建议将 load-on-startup 设置为 1。这样容器会在启动时加载该 Serlvet 而不是请求到达时。这样能减少第一个请求的响应时间。</p>
<hr style="border-color: #333;border-width:2px; border-style: dotted; margin-top: 30px; margin-bottom: 30px;">
<p>出租车司机终于可以下了,等的花儿都谢了,宋康昊千万不要太帅~</p>
<p><img src="https://pic.yupoo.com/jiananshi/c494820d/08877f04.jpg" alt="miao a"></p>
</content>
<summary type="html">
<blockquote>
<p>看白夜追凶看的,写个博客总感觉身后有人。。。。。</p>
</blockquote>
</summary>
<category term="spring" scheme="http://yemengying.com/categories/spring/"/>
<category term="java" scheme="http://yemengying.com/tags/java/"/>
<category term="翻译" scheme="http://yemengying.com/tags/%E7%BF%BB%E8%AF%91/"/>
<category term="spring" scheme="http://yemengying.com/tags/spring/"/>
</entry>
<entry>
<title>Oracle 中的外键与锁</title>
<link href="http://yemengying.com/2017/09/04/oracle-foreignkey-lock/"/>
<id>http://yemengying.com/2017/09/04/oracle-foreignkey-lock/</id>
<published>2017-09-04T10:31:07.000Z</published>
<updated>2018-12-13T03:49:20.000Z</updated>
<content type="html"><blockquote>
<p>算是接上篇吧。。。 内容主要来自 Oracle 官方文档,自己重新画了下图。图中配色来自大神 draveness 的<a href="http://draveness.me/mysql-innodb.html" target="_blank" rel="external">文章</a>,小清新的配色真是美美哒。看来我在学画图的路上还要修炼很久啊。。。。。 </p>
</blockquote>
<a id="more"></a>
<p>锁是一种可以防止多个事务错误的更新共享数据的机制,在维护数据库并发性和一致性方面起着关键的作用。在 Oracle 堆组织表中, 数据库锁的行为与外键列是否有索引有关。如果外键未加索引,那么子表可能会被频繁锁住,从而导致死锁,降低并发性。所以,Oracle 官方建议绝对多数情况都为外键加索引,除非父表的唯一键/主键绝对不会更新或删除。</p>
<h2 id="未加索引的外键与锁"><a href="#未加索引的外键与锁" class="headerlink" title="未加索引的外键与锁"></a>未加索引的外键与锁</h2><p>当以下条件都满足时,数据库会获取子表的全表锁:</p>
<ul>
<li>子表的外键列未加索引</li>
<li>父表的主键被修改(比如:删除一行或主键被修改)或者合并到父表。在父表插入一条记录是不会锁住子表的。</li>
</ul>
<p>假设 hr.departments 是父表,hr.employees 是子表, hr.employees 中的 department_id 是未加索引的外键列。下图展现了修改父表 department_id = 60 这一记录的主键时,数据库加锁的情况:</p>
<p><img src="https://pic.yupoo.com/jiananshi/2ee4d87f/a5d4acb9.png" alt="unindexed_foreign_key"></p>
<p>在上图中,数据库在更新父表 department 60 这一记录的主键时,会获得子表 employees 的全表锁。这样其他会话可以查询子表但不允许更新子表。子表的表锁会在父表更新完成后立刻释放。如果修改父表多条记录,那么修改每一条都会获得子表的表锁并释放。</p>
<p>在 Oracle 9i 及以上版本中,全表锁都是短期的,仅在 DML 操作期间存在,而不是整个事务。但即便如此,还是要避免,因为全表锁可能会导致死锁。Tom 也曾说过,导致死锁的头号原因就是外键未加索引(第二号原因是表上的位图索引遭到并发更新)。</p>
<blockquote>
<p>需要注意,子表的 DML 不会获得父表的表锁 </p>
</blockquote>
<h2 id="加索引的外键与锁"><a href="#加索引的外键与锁" class="headerlink" title="加索引的外键与锁"></a>加索引的外键与锁</h2><p>当以下条件都满足时,数据库不会获得子表的全表锁。</p>
<ul>
<li>子表的外键列已加索引</li>
<li>父表的主键正在被修改(比如:删除一行或主键被修改)或合并到父表</li>
</ul>
<p>下面的图展示了子表 employees 的外键列 department_id 加了索引。 当一个事务从父表 department 中删除 department 280 时, 这一操作不会引起数据库获得子表的全表锁。</p>
<p>父表上的锁是为了防止其他事务获取表级排他锁,但不会阻止父表或子表上的 DML 操作。</p>
<p><img src="https://pic.yupoo.com/jiananshi/3acb7f13/cf7696ef.png" alt="indexed_foreign_key"></p>
<p>如果子表指明了 <code>ON DELETE CASCADE</code>, 那么删除父表会导致删除子表对应的记录。比如删除父表 department 280 这一记录,那么子表 employees 中 department_id 为 280 的记录也会被删除。 在这种情况下,加锁的规则与删除完主表后再删除子表的记录是一样的。</p>
<p>这篇是不是有点水啊。。。。是时候放出我姑家的小霸王 cookie 宝宝撑下场了。</p>
<blockquote>
<p>虽然我又咬人,又乱叫,还喜欢对着窗帘小便。但我知道我是个好狗狗。<br> ————cookie </p>
</blockquote>
<p><img src="https://pic.yupoo.com/jiananshi/47585e7b/8318af70.jpg" alt=""></p>
<p><img src="https://pic.yupoo.com/jiananshi/943bd285/7bae2334.jpg" alt=""></p>
<p><img src="https://pic.yupoo.com/jiananshi/8fa84a4a/ded05647.jpg" alt=""></p>
<p><img src="https://pic.yupoo.com/jiananshi/65bbfe90/2c9aa704.jpg" alt=""></p>
</content>
<summary type="html">
<blockquote>
<p>算是接上篇吧。。。 内容主要来自 Oracle 官方文档,自己重新画了下图。图中配色来自大神 draveness 的<a href="http://draveness.me/mysql-innodb.html">文章</a>,小清新的配色真是美美哒。看来我在学画图的路上还要修炼很久啊。。。。。 </p>
</blockquote>
</summary>
<category term="Oracle" scheme="http://yemengying.com/categories/Oracle/"/>
<category term="Oracle" scheme="http://yemengying.com/tags/Oracle/"/>
</entry>
<entry>
<title>小死锁</title>
<link href="http://yemengying.com/2017/07/15/deadlock-in-oracle/"/>
<id>http://yemengying.com/2017/07/15/deadlock-in-oracle/</id>
<published>2017-07-15T14:52:22.000Z</published>
<updated>2018-12-13T03:56:58.000Z</updated>
<content type="html"><blockquote>
<p> 最近线上偶尔就会报个死锁问题,上周终于解决了,周末整理下。虽然问题解决了,但是trace file里的死锁图还是不太理解。要是有人能给我讲讲那真是极好的,要是没人的话我就。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。再翻翻文档。 </p>
</blockquote>
<a id="more"></a>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>近一个月线上偶尔就会报死锁的问题,错误如下图:<br><img src="https://pic.yupoo.com/jiananshi/d4b73e70/8096062f.jpg" alt=""></p>
<p><code>“ORA-00060: deadlock detected while waiting for resource ”</code>这个错误说明Oracle 数据库监测到了死锁的存在。这时 Oracle 会回滚造成死锁的其中一个事务,另一个事务正常执行(不会察觉到发生了死锁),并向执行了回滚的事务抛出上面的错误信息。</p>
<p>在 DBA 的帮助下定位到了造成死锁的两块代码。由于项目有很多的悲观锁,即利用“SELECT…FOR UPDATE”对资源加排他行级锁,所以第一感觉就是看看这两段代码有没有按照相反的顺序对两个或多个资源进行加锁。</p>
<p>不过分析过代码之后却没有立刻找到可能造成死锁的原因,两块代码对数据库资源的操作如下表。</p>
<p><img src="https://yemengying.com/qiniu/image/2017-07-17-Screen%20Shot%202017-07-17%20at%2010.16.52%20PM.png" alt=""></p>
<p>从表面上看Session 1貌似中只锁了 actor1 并更新,Session 2中依次锁了 actor2 和 actor1,不满足互相等待对方加锁的资源,就算是Session1持有actor1锁时间过长,导致 Session2 一直拿不到 actor1 的锁,也应该报“lock wait timeout”,而不是死锁。</p>
<p>为了验证确实是这两段代码造成的死锁,写了测试代码,开了两个线程,模仿死锁的这两段代码,去掉了与数据库无关的业务逻辑,看看能否重现。毕竟心里还有点小怀疑,会不会是 DBA 搞错了,不是这两段代码的问题。<br>代码如下:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@SpringApplicationConfiguration</span>(classes = DeadLockTest.class)</div><div class="line"><span class="meta">@ImportAutoConfiguration</span>(&#123;CommonConfig.class&#125;)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeadLockTest</span> <span class="keyword">extends</span> <span class="title">BaseUnitDbTest</span> </span>&#123;</div><div class="line"> Long lenderId = <span class="number">16642L</span>;</div><div class="line"> Long borrowerId = <span class="number">16643L</span>;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> ActorService actorService;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="meta">@Qualifier</span>(CommonConfig.ORACLE_TRANSACTION_MANAGER_NAME)</div><div class="line"> <span class="keyword">private</span> PlatformTransactionManager platformTransactionManager;</div><div class="line"> <span class="keyword">private</span> ExecutorService es = Executors.newFixedThreadPool(<span class="number">5</span>, <span class="keyword">new</span> ThreadFactoryBuilder().setNameFormat(<span class="string">"Test-Thread-%d"</span>).build());</div><div class="line"></div><div class="line"> <span class="meta">@Test</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRefreshAndLockActor</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</div><div class="line"> es.invokeAll(Lists.newArrayList(<span class="keyword">this</span>::lock1, <span class="keyword">this</span>::lock2));</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> Void <span class="title">lock1</span><span class="params">()</span> </span>&#123;</div><div class="line"> TransactionTemplate t = <span class="keyword">new</span> TransactionTemplate(platformTransactionManager);</div><div class="line"> t.execute((s) -&gt; &#123;</div><div class="line"> System.out.println(<span class="string">"Before Lock "</span> + Thread.currentThread().getName());</div><div class="line"> Actor lender = actorService.refreshAndLockActor(lenderId);</div><div class="line"> <span class="keyword">try</span> &#123;</div><div class="line"> Thread.sleep(<span class="number">6000</span>);</div><div class="line"> &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</div><div class="line"> e.printStackTrace();</div><div class="line"> &#125;</div><div class="line"> lender.setLockedForInv(BigDecimal.ONE);</div><div class="line"> actorService.update(lender);</div><div class="line"> System.out.println(<span class="string">"After Lock "</span> + Thread.currentThread().getName());</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> &#125;);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> &#125;</div><div class="line"> <span class="function"><span class="keyword">public</span> Void <span class="title">lock2</span><span class="params">()</span> </span>&#123;</div><div class="line"> TransactionTemplate t = <span class="keyword">new</span> TransactionTemplate(platformTransactionManager);</div><div class="line"> t.execute((s) -&gt; &#123;</div><div class="line"> <span class="keyword">try</span> &#123;</div><div class="line"> Thread.sleep(<span class="number">1000</span>);</div><div class="line"> &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</div><div class="line"> e.printStackTrace();</div><div class="line"> &#125;</div><div class="line"> System.out.println(<span class="string">"Before Lock "</span> + Thread.currentThread().getName());</div><div class="line"> Actor borrower = actorService.refreshAndLockActor(borrowerId);</div><div class="line"> Actor lender = actorService.refreshAndLockActor(lenderId);</div><div class="line"> System.out.println(<span class="string">"After Lock "</span> + Thread.currentThread().getName());</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> &#125;);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>结果。。。真的报了死锁。。。。<br><img src="https://pic.yupoo.com/jiananshi/fba203b9/133b7b02.jpeg" alt=""><br>控制台的报错如下图</p>
<p><img src="https://yemengying.com/qiniu/image/2017-07-17-Screen%20Shot%202017-07-17%20at%2010.19.56%20PM.png" alt=""></p>
<h2 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h2><p>已经确定了造成死锁的两段代码,接下来就差找出原因了。<br><code>SELECT ... FOR UPDATE</code>比较直观,就是对资源加行级排他锁,应该没什么猫腻。那就肯定是在<code>UPDATE Actor1</code>的时候有什么不为人知的操作,导致 Session1 需要获取 Actor2 的锁,导致死锁。</p>
<p>第一怀疑的是触发器,虽然目前公司已经禁止使用触发器了,但由于历史原因主库还是遗留着一些触发器。和明佳排查了所有相关的触发器之后,基本排除了是由触发器引起的。</p>
<p>不过虽然这个问题不是触发器的锅,但还是提会到了,在涉及到触发器时,如果不是对系统特别熟,排查错误真的很困难。。。</p>
<p>排除了触发器之后,DBA 提出那就只能是外键导致的了。在 DBA 把 dev环境数据库的外键去掉后,再次执行测试代码,果然就不再报死锁了。</p>
<p>原来 Actor 表上的 refer_id 是一个关联 Actor 表主键的外键(Self-Referential Integrity Constraints),而 actor1 的 refer_id 正好是 actor2 的 id,所以在更新 actor1 的全字段的时候,也更新了 refer_id(其实值没变),由于外键的约束,在将 actor1 的 refer_id 更新为 actor2的 id 时,需要确保 actor2 是存在的,并且在更新过程中,不能被删除,所以 Session1 会申请 actor2 的锁(个人理解不一定准确)。而这时 actor2 的锁已经被 Session2 持有了,并且 Session2 正在等待 actor1 的锁,就发生了死锁。</p>
<p>用图来描述下:<br><img src="https://pic.yupoo.com/jiananshi/d09831f3/41ba745e.jpg" alt="deadlock"></p>
<h2 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h2><p>费了一天多才把问题找出来,用了几分钟就 fix 了。其实我只是需要更新 Actor 上的两个字段,根本不需要更新全部字段,只是当时在写的时候已经有更新全字段的方法了,就偷了个懒。。。。。。</p>
<p>所以解决办法就是不再调用更新全字段的方法,加了个只更新部分字段的方法,这样就不会在更新 actor1 的外键字段了,也就不会造成在更新 actor1 的时候去请求 actor2 的锁了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>虽然这个问题不是触发器引起的,但禁用触发器还是很有道理滴,不然出问题查到吐血</li>
<li>外键这个东东,也能不用就不用吧,由程序控制。其实现在公司已经不让用外键和触发器了,不过由于历史原因,一些老系统只能慢慢重构了。查了下,由于外键引起的死锁还是蛮多的,比较常见的是外键列不加索引,导致更新主表字段时锁住了子表,下篇blog可以学习下外键和死锁不得不说的那些事。算了,还是不立 flag 了,基本上说了下篇要写啥的,都没有下篇了。。。。</li>
<li>不要更新全字段。抛开这个死锁问题,更新全字段也是很影响效率的。还是只更新有改动的字段吧。</li>
<li>不能偷懒,当时省了5分钟,找 bug 花了一天多。。。都是泪</li>
</ul>
<h2 id="遗留问题"><a href="#遗留问题" class="headerlink" title="遗留问题"></a>遗留问题</h2><p>问题虽然解决了,但是还有点小疑问的。这个死锁在 trace file 中的死锁图如下:<br><img src="https://pic.yupoo.com/jiananshi/f886128b/74344a68.jpg" alt="trace file"></p>
<p>那么问题来了,两个 session 持有两个资源的 X 锁还是好理解的,但他们等待的为什么是 S 锁呢???至少 Session2 是在等待 actor1 的排他行级锁的,不应该是也是等待 X 么。求好心人的解答。<br><img src="https://yemengying.com/qiniu/image/2017-07-17-Screen%20Shot%202017-07-14%20at%204.37.56%20PM.png?imageMogr2/thumbnail/300x250!" alt=""></p>
</content>
<summary type="html">
<blockquote>
<p> 最近线上偶尔就会报个死锁问题,上周终于解决了,周末整理下。虽然问题解决了,但是trace file里的死锁图还是不太理解。要是有人能给我讲讲那真是极好的,要是没人的话我就。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。再翻翻文档。 </p>
</blockquote>
</summary>
<category term="Oracle" scheme="http://yemengying.com/categories/Oracle/"/>
<category term="Oracle" scheme="http://yemengying.com/tags/Oracle/"/>
</entry>
<entry>
<title>【译】Reddit如何统计每个帖子的浏览量</title>
<link href="http://yemengying.com/2017/06/04/reddit-view-counting/"/>
<id>http://yemengying.com/2017/06/04/reddit-view-counting/</id>
<published>2017-06-04T05:26:17.000Z</published>
<updated>2018-12-13T04:01:09.000Z</updated>
<content type="html"><blockquote>
<p>之前没听过也没了解过 HyperLogLog,通过翻译这篇文章正好简单学习下。欢迎指正错误~</p>
</blockquote>
<a id="more"></a>
<p><a href="https://redditblog.com/2017/05/24/view-counting-at-reddit/?utm_source=wanqu.co&amp;utm_campaign=Wanqu+Daily&amp;utm_medium=website" target="_blank" rel="external">原文链接</a>😁🤗😉</p>
<p><img src="https://pic.yupoo.com/jiananshi/8afc30ad/b2e3cd7c.png" alt=""></p>
<p>我们想要更好的向用户展示 Reddit 的规模。为了这一点,投票和评论数是一个帖子最重要的指标。然而,在 Reddit 上有相当多的用户只浏览内容,既不投票也不评论。所以我们想要建立一个能够计算一个帖子浏览数的系统。这一数字会被展示给帖子的创作者和版主,以便他们更好的了解某个帖子的活跃程度。</p>
<p><img src="https://pic.yupoo.com/jiananshi/24277564/dc03ba12.png" alt=""></p>
<p>在这篇博客中,我们将讨论我们是如何实现超大数据量的计数。</p>
<h2 id="计数机制"><a href="#计数机制" class="headerlink" title="计数机制"></a>计数机制</h2><p>对于计数系统我们主要有四种需求:</p>
<ul>
<li>帖子浏览数必须是实时或者近实时的,而不是每天或者每小时汇总。</li>
<li>同一用户在短时间内多次访问帖子,只算一个浏览量</li>
<li>显示的浏览量与真实浏览量间允许有小百分之几的误差</li>
<li>Reddit 是全球访问量第八的网站,系统要能在生产环境的规模上正常运行,仅允许几秒的延迟</li>
</ul>
<p>要全部满足以上四个需求的困难远远比听上去大的多。为了实时精准计数,我们需要知道某个用户是否曾经访问过这篇帖子。想要知道这个信息,我们就要为每篇帖子维护一个访问用户的集合,然后在每次计算浏览量时检查集合。一个 naive 的实现方式就是将访问用户的集合存储在内存的 hashMap 中,以帖子 Id 为 key。</p>
<p>这种实现方式对于访问量低的帖子是可行的,但一旦一个帖子变得流行,访问量剧增时就很难控制了。甚至有的帖子有超过 100 万的独立访客! 对于这样的帖子,存储独立访客的 ID 并且频繁查询某个用户是否之前曾访问过会给内存和 CPU 造成很大的负担。</p>
<p>因为我们不能提供准确的计数,我们查看了几种不同的<a href="https://en.wikipedia.org/wiki/Count-distinct_problem" target="_blank" rel="external">基数估计算法</a>。有两个符合我们需求的选择:</p>
<ul>
<li>一是线性概率计数法,很准确,但当计数集合变大时所需内存会线性变大。</li>
<li>二是基于 <a href="http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf" target="_blank" rel="external">HyperLogLog</a> (以下简称 HLL )的计数法。 HLL 空间复杂度较低,但是精确度不如线性计数。</li>
</ul>
<p>下面看下 HLL 会节省多少内存。如果我们需要存储 100 万个独立访客的 ID, 每个用户 ID 8 字节长,那么为了存储一篇帖子的独立访客我们就需要 8 M的内存。反之,如果采用 HLL 会显著减少内存占用。不同的 HLL 实现方式消耗的内存不同。如果采用<a href="http://antirez.com/news/75" target="_blank" rel="external">这篇文章</a>的实现方法,那么存储 100 万个 ID 仅需 12 KB,是原来的 0.15%!!</p>
<p><a href="http://highscalability.com/blog/2012/4/5/big-data-counting-how-to-count-a-billion-distinct-objects-us.html" target="_blank" rel="external">Big Data Counting: How to count a billion distinct objects using only 1.5KB of Memory - High Scalability -</a>这篇文章很好的总结了上面的算法。</p>
<p>许多 HLL 的实现都是结合了上面两种算法。在集合小的时候采用线性计数,当集合大小到达一定的阈值后切换到 HLL。前者通常被成为 ”稀疏“(sparse) HLL,后者被称为”稠密“(dense) HLL。这种结合了两种算法的实现有很大的好处,因为它对于小集合和大集合都能够保证精确度,同时保证了适度的内存增长。可以在 google 的这篇<a href="https://stefanheule.com/papers/edbt13-hyperloglog.pdf" target="_blank" rel="external">论文</a>中了解这种实现的详细内容。</p>
<p>现在我们已经确定要采用 HLL 算法了,不过在选择具体的实现时,我们考虑了以下三种不同的实现。因为我们的数据工程团队使用 Java 和 Scala,所以我们只考虑 Java 和 Scala 的实现。</p>
<ul>
<li>Twitter 提供的 <a href="https://twitter.github.io/algebird/datatypes/approx/hyperloglog.html" target="_blank" rel="external">Algebird</a>,采用 Scala 实现。Algebird 有很好的文档,但他们对于 sparse 和 dense HLL 的实现细节不是很容易理解。</li>
<li><a href="https://github.com/addthis/stream-lib" target="_blank" rel="external">stream-lib</a>中提供的 HyperLogLog++, 采用 Java 实现。stream-lib 中的代码文档齐全,但有些难理解如何合适的使用并且改造的符合我们的需求。</li>
<li><a href="http://antirez.com/news/75" target="_blank" rel="external">Redis HLL 实现</a>,这是我们最终选择的。我们认为 Redis 中 HLLs 的实现文档齐全、容易配置,提供的相关 API 也很容易集成。还有一个好处是,我们可以用一台专门的服务器部署,从而减轻性能上的压力。</li>
</ul>
<p><img src="//yemengying.com/qiniu/image/2017-06-03-Screen%20Shot%202017-06-03%20at%207.58.59%20PM.png" alt=""></p>
<p>Reddit 的数据管道依赖于 Kafka。当一个用户访问了一篇博客,会触发一个事件,事件会被发送到事件收集服务器,并被持久化在 Kafka 中。</p>
<p>之后,计数系统会依次顺序运行两个组件。在我们的计数系统架构中,第一部分是一个 Kafka 的消费者,我们称之为 Nazar。Nazar 会从 Kafka 中读取每个事件,并将它通过一系列配置的规则来判断该事件是否需要被计数。我们取这个名字仅仅是因为 Nazar 是一个眼睛形状的护身符,而 ”Nazar“ 系统就像眼睛一样使我们的计数系统远离不怀好意者的破坏。其中一个我们不将一个事件计算在内的原因就是同一个用户在很短时间内重复访问。Nazar 会修改事件,加上个标明是否应该被计数的布尔标识,并将事件重新放入 Kafka。</p>
<p>下面就到了系统的第二个部分。我们将第二个 Kafka 的消费者称作 Abacus,用来进行真正浏览量的计算,并且将计算结果显示在网站或客户端。Abacus 从 Kafka 中读取经过 Nazar 处理过的事件,并根据 Nazar 的处理结果决定是跳过这个事件还是将其加入计数。如果 Nazar 中的处理结果是可以加入计数,那么 Abacus 首先会检查这个事件所关联的帖子在 Redis 中是否已经存在了一个 HLL 计数器。如果已经存在,Abacus 会给 Redis 发送个 PFADD 的请求。如果不存在,那么 Abacus 会给 Cassandra 集群发送个请求(Cassandra 用来持久化 HLL 计数器和 计数值的),然后向 Redis 发送 <a href="https://redis.io/commands/set" target="_blank" rel="external">SET</a> 请求。这通常会发生在网友访问较老帖子的时候,这时该帖子的计数器很可能已经在 Redis 中过期了。</p>
<p>为了存储存在 Redis 中的计数器过期的老帖子的浏览量。Abacus 会周期性的将 Redis 中全部的 HLL 和 每篇帖子的浏览量写入到 Cassandra 集群中。为了避免集群过载,我们以 10 秒为周期批量写入。</p>
<p>下图是事件流的大致流程:<br><img src="https://pic.yupoo.com/jiananshi/387a1243/4a4db997.jpg" alt=""></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我们希望浏览量可以让发帖者了解帖子全部的访问量,也帮助版主快速定位自己社区中高访问量的帖子。在未来,我们计划利用我们数据管道在实时方面的潜力来为 Reddit 的用户提供更多的有用的反馈。</p>
<p>————————————————————分割线———————————————————-</p>
<p>这周一定要看完《地球上的星星》💪<br><img src="https://pic.yupoo.com/jiananshi/36d4bd87/8761312e.jpg" alt="Like stars on earth"></p>
</content>
<summary type="html">
<blockquote>
<p>之前没听过也没了解过 HyperLogLog,通过翻译这篇文章正好简单学习下。欢迎指正错误~</p>
</blockquote>
</summary>
<category term="翻译" scheme="http://yemengying.com/categories/%E7%BF%BB%E8%AF%91/"/>
<category term="翻译" scheme="http://yemengying.com/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>MySQL 唯一性约束与 NULL</title>
<link href="http://yemengying.com/2017/05/18/mysql-unique-key-null/"/>
<id>http://yemengying.com/2017/05/18/mysql-unique-key-null/</id>
<published>2017-05-18T14:35:54.000Z</published>
<updated>2018-12-13T04:02:58.000Z</updated>
<content type="html"><blockquote>
<p>很久之前的一个 bug 了,简单记录下。。。 </p>
</blockquote>
<a id="more"></a>
<p>之前做的一个需求,简化描述下就是接受其他组的 MQ 的消息,然后在数据库里插入一条记录。为了防止他们重复发消息,插入多条重复记录,所以在表中的几个列上加了个唯一性索引。<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">CREATE</span> <span class="keyword">UNIQUE</span> <span class="keyword">INDEX</span> IDX_UN_LOAN_PLAN_APP <span class="keyword">ON</span> testTable (A, B, C);</div></pre></td></tr></table></figure></p>
<p>这时 A,B,C 三列都是不允许 NULL 值的,唯一性约束也是 work 的。<br>后来由于需求的变化,修改了以前的唯一性约束,又多加了一列。(至于为什么加就不赘述了)。<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> testTable</div><div class="line"><span class="keyword">DROP</span> <span class="keyword">INDEX</span> IDX_UN_LOAN_PLAN_APP,</div><div class="line"><span class="keyword">ADD</span> <span class="keyword">UNIQUE</span> <span class="keyword">KEY</span> <span class="string">`IDX_UN_LOAN_PLAN_APP`</span> (A, B, C, D);</div></pre></td></tr></table></figure></p>
<p>新加的 D 是类型是 datetime, 允许为 NULL,默认值为 NULL。之所以默认值为 NULL,是考虑到不是所有记录都有这个时间的, 如果强行设置一个 Magic Value (比如’1970-01-01 08:00:00‘)当做默认值,看起来很奇怪。</p>
<p>蓝后。。。就出问题了。加了 D 之后,唯一性约束基本就失效了。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">Insert</span> <span class="keyword">into</span> testTable (A,B,C,D) <span class="keyword">VALUES</span> (<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="literal">NULL</span>); <span class="comment">--- OK</span></div><div class="line"><span class="keyword">Insert</span> <span class="keyword">into</span> testTable (A,B,C,D) <span class="keyword">VALUES</span> (<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="literal">NULL</span>); <span class="comment">--- OK</span></div><div class="line"><span class="keyword">Insert</span> <span class="keyword">into</span> testTable (A,B,C,D) <span class="keyword">VALUES</span> (<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="literal">NULL</span>); <span class="comment">--- OK</span></div></pre></td></tr></table></figure>
<p>上面的三条 SQL 都是可以执行成功的,数据库中会有多条一样的记录。可按照我们以前的构想,在执行后两条 SQL 时 应该抛出 ‘Duplicate key’ 的异常的。</p>
<p>后来查了一下,才发现其实 MySQL 官方文档上已经明确说了这一点, 唯一性索引是允许多个 NULL 值的存在的:</p>
<blockquote>
<p>A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. For all engines, a UNIQUE index allows multiple NULL values for columns that can contain NULL. </p>
</blockquote>
<p>从下表中也可以看出来不管是采用什么类型的存储引擎,在建立 unique key 的时候都是允许多个 NULL 存在的。。。。<br><br><img src="https://pic.yupoo.com/jiananshi/bfbe439c/2ba53a19.png" alt="index"><br>细想想,其实也蛮合理,毕竟在 MySQL 中认为 NULL 代表着“未知”。 在 SQL 中,任何值与 NULL 的比较返回值都是 NULL 而不是 TRUE, 就算 NULL 与 NULL 的比较也是返回 NULL。</p>
<p>所以只能 fix 了。。。解决办法也蛮简单粗暴的,直接把线上数据刷了一遍,将“1970-01-01 08:00:00”作为默认值,然后把那列改为不允许为 NULL 的了,咳咳。</p>
<p><img src="https://pic.yupoo.com/jiananshi/498d5f2c/8dfdd2be.jpg" alt="wuli 滚滚"></p>
<p>MySQL 官网上也有蛮多人讨论过这个问题,一部分人认为这是 MySQL 的 bug, 另一部分则认为是一个 feature,附上链接。</p>
<p><a href="https://bugs.mysql.com/bug.php?id=8173" target="_blank" rel="external">MySQL Bugs: #8173: unique index allows duplicates with null values</a></p>
</content>
<summary type="html">
<blockquote>
<p>很久之前的一个 bug 了,简单记录下。。。 </p>
</blockquote>
</summary>
<category term="MySQL" scheme="http://yemengying.com/categories/MySQL/"/>
<category term="MySQL" scheme="http://yemengying.com/tags/MySQL/"/>
</entry>
<entry>
<title>【译】Executor, ExecutorService 和 Executors 间的不同</title>
<link href="http://yemengying.com/2017/03/17/difference-between-executor-executorService/"/>
<id>http://yemengying.com/2017/03/17/difference-between-executor-executorService/</id>
<published>2017-03-17T06:07:21.000Z</published>
<updated>2017-06-10T07:56:03.000Z</updated>
<content type="html"><blockquote>
<p>搁了好久没更博客,再不写要被某人 BS 了,咦。。。。。</p>
</blockquote>
<p><a href="http://javarevisited.blogspot.jp/2017/02/difference-between-executor-executorservice-and-executors-in-java.html" target="_blank" rel="external">原文链接</a> </p>
<a id="more"></a>
<p><code>java.util.concurrent.Executor</code>, <code>java.util.concurrent.ExecutorService</code>, <code>java.util.concurrent. Executors</code> 这三者均是 Java Executor 框架的一部分,用来提供线程池的功能。因为创建和管理线程非常心累,并且操作系统通常对线程数有限制,所以建议使用线程池来并发执行任务,而不是每次请求进来时创建一个线程。使用线程池不仅可以提高应用的响应时间,还可以避免<code>&quot;java.lang.OutOfMemoryError: unable to create new native thread&quot;</code> 之类的错误。</p>
<p>在 Java 1.5 时,开发者需要关心线程池的创建和管理,但在 Java 1.5 之后 Executor 框架提供了多种内置的线程池,例如:FixedThreadPool(包含固定数目的线程),CachedThreadPool(可根据需要创建新的线程)等等。</p>
<h2 id="Executor"><a href="#Executor" class="headerlink" title="Executor"></a>Executor</h2><p>Executor, ExecutorService, 和 Executors 最主要的区别是 Executor 是一个抽象层面的核心接口(大致代码如下)。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Executor</span> </span>&#123;</div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable command)</span></span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>不同于 <code>java.lang.Thread</code> 类将任务和执行耦合在一起, Executor 将任务本身和执行任务分离,可以阅读 <a href="http://javarevisited.blogspot.sg/2016/12/difference-between-thread-and-executor.html来看看" target="_blank" rel="external">difference between Thread and Executor</a> 来了解 Thread 和 Executor 间更多的不同。</p>
<h2 id="ExecutorService"><a href="#ExecutorService" class="headerlink" title="ExecutorService"></a>ExecutorService</h2><p>ExecutorService 接口 对 Executor 接口进行了扩展,提供了返回 Future 对象,终止,关闭线程池等方法。当调用 <code>shutDown</code> 方法时,线程池会停止接受新的任务,但会完成正在 pending 中的任务。</p>
<p>Future 对象提供了异步执行,这意味着无需等待任务执行的完成,只要提交需要执行的任务,然后在需要时检查 Future 是否已经有了结果,如果任务已经执行完成,就可以通过 Future.get() 方法获得执行结果。需要注意的是,Future.get() 方法是一个阻塞式的方法,如果调用时任务还没有完成,会等待直到任务执行结束。</p>
<p>通过 ExecutorService.submit() 方法返回的 Future 对象,还可以取消任务的执行。Future 提供了 cancel() 方法用来取消执行 pending 中的任务。</p>
<p>ExecutorService 部分代码如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ExecutorService</span> <span class="keyword">extends</span> <span class="title">Executor</span> </span>&#123;</div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">shutdown</span><span class="params">()</span></span>;</div><div class="line"> &lt;T&gt; <span class="function">Future&lt;T&gt; <span class="title">submit</span><span class="params">(Callable&lt;T&gt; task)</span></span>;</div><div class="line"> &lt;T&gt; <span class="function">Future&lt;T&gt; <span class="title">submit</span><span class="params">(Runnable task, T result)</span></span>;</div><div class="line"> &lt;T&gt; List&lt;Future&lt;T&gt;&gt; invokeAll(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks, <span class="keyword">long</span> timeout, TimeUnit unit) <span class="keyword">throws</span> InterruptedException;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<h2 id="Executors"><a href="#Executors" class="headerlink" title="Executors"></a>Executors</h2><p>Executors 是一个工具类,类似于 Collections。提供工厂方法来创建不同类型的线程池,比如 FixedThreadPool 或 CachedThreadPool。</p>
<p>Executors 部分代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Executors</span> </span>&#123;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newFixedThreadPool</span><span class="params">(<span class="keyword">int</span> nThreads)</span> </span>&#123;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(nThreads, nThreads, <span class="number">0L</span>, TimeUnit.MILLISECONDS,<span class="keyword">new</span> LinkedBlockingQueue&lt;Runnable&gt;());</div><div class="line"> &#125;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newCachedThreadPool</span><span class="params">()</span> </span>&#123;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">60L</span>, TimeUnit.SECONDS, <span class="keyword">new</span> SynchronousQueue&lt;Runnable&gt;());</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>下面详细看一下三者的区别:</p>
<h2 id="Executor-vs-ExecutorService-vs-Executors"><a href="#Executor-vs-ExecutorService-vs-Executors" class="headerlink" title="Executor vs ExecutorService vs Executors"></a>Executor vs ExecutorService vs Executors</h2><p>正如上面所说,这三者均是 Executor 框架中的一部分。Java 开发者很有必要学习和理解他们,以便更高效的使用 Java 提供的不同类型的线程池。总结一下这三者间的区别,以便大家更好的理解:</p>
<ul>
<li>Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口</li>
<li>Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 <code>execute()</code>方法用来接收一个<code>Runnable</code>接口的对象,而 ExecutorService 接口中的 <code>submit()</code>方法可以接受<code>Runnable</code>和<code>Callable</code>接口的对象。</li>
<li>Executor 和 ExecutorService 接口第三个区别是 Executor 中的 <code>execute()</code> 方法不返回任何结果,而 ExecutorService 中的 <code>submit()</code>方法可以通过一个 Future 对象返回运算结果。</li>
<li>Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 <code>shutDown()</code> 方法终止线程池。可以通过 <a href="http://www.amazon.com/dp/0321349601/?tag=javamysqlanta-20" target="_blank" rel="external">《Java Concurrency in Practice》</a> 一书了解更多关于关闭线程池和如何处理 pending 的任务的知识。</li>
<li>Executors 类提供工厂方法用来创建不同类型的线程池。比如: <code>newSingleThreadExecutor()</code> 创建一个只有一个线程的线程池,<code>newFixedThreadPool(int numOfThreads)</code>来创建固定线程数的线程池,<code>newCachedThreadPool()</code>可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>下表列出了 Executor 和 ExecutorService 的区别:</p>
<table>
<thead>
<tr>
<th>Executor</th>
<th>ExecutorService</th>
</tr>
</thead>
<tbody>
<tr>
<td>Executor 是 Java 线程池的核心接口,用来并发执行提交的任务</td>
<td>ExecutorService 是 Executor 接口的扩展,提供了异步执行和关闭线程池的方法</td>
</tr>
<tr>
<td>提供execute()方法用来提交任务</td>
<td>提供submit()方法用来提交任务</td>
</tr>
<tr>
<td>execute()方法无返回值</td>
<td>submit()方法返回Future对象,可用来获取任务执行结果</td>
</tr>
<tr>
<td>不能取消任务</td>
<td>可以通过Future.cancel()取消pending中的任务</td>
</tr>
<tr>
<td>没有提供和关闭线程池有关的方法</td>
<td>提供了关闭线程池的方法</td>
</tr>
</tbody>
</table>
<h2 id="译者注"><a href="#译者注" class="headerlink" title="译者注"></a>译者注</h2><p>个人觉得,利用 Executors 类提供的工厂方法来创建一个线程池是很方便,但对于需要根据实际情况自定义线程池某些参数的场景,就不太适用了。</p>
<p>举个例子:<br>当线程池中的线程均处于工作状态,并且线程数已达线程池允许的最大线程数时,就会采取指定的饱和策略来处理新提交的任务。总共有四种策略:</p>
<ul>
<li>AbortPolicy: 直接抛异常</li>
<li>CallerRunsPolicy: 用调用者的线程来运行任务</li>
<li>DiscardOldestPolicy: 丢弃线程队列里最近的一个任务,执行新提交的任务</li>
<li>DiscardPolicy 直接将新任务丢弃</li>
</ul>
<p>如果使用 Executors 的工厂方法创建的线程池,那么饱和策略都是采用默认的 AbortPolicy,所以如果我们想当线程池已满的情况,使用调用者的线程来运行任务,就要自己创建线程池,指定想要的饱和策略,而不是使用 Executors 了。</p>
<p>所以我们可以根据需要创建 ThreadPoolExecutor(ExecutorService接口的实现类) 对象,自定义一些参数,而不是调用 Executors 的工厂方法创建。</p>
<p>当然,在使用 Spring 框架的项目中,也可以使用 Spring 提供的 ThreadPoolTaskExecutor 类来创建线程池。ThreadPoolTaskExecutor 与 ThreadPoolExecutor 类似,也提供了许多参数用来自定义线程池,比如:核心线程池大小,线程池最大数量,饱和策略,线程活动保持时间等等。</p>
<h2 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h2><ul>
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html" target="_blank" rel="external">Executors java api doc</a></li>
</ul>
</content>
<summary type="html">
<blockquote>
<p>搁了好久没更博客,再不写要被某人 BS 了,咦。。。。。</p>
</blockquote>
<p><a href="http://javarevisited.blogspot.jp/2017/02/difference-between-executor-executorservice-and-executors-in-java.html">原文链接</a> </p>
</summary>
<category term="java" scheme="http://yemengying.com/categories/java/"/>
<category term="java" scheme="http://yemengying.com/tags/java/"/>
</entry>
<entry>
<title>谈谈 NoSuchBeanDefinitionException</title>
<link href="http://yemengying.com/2017/02/18/something-about-noSuchBeanDefinitionException/"/>
<id>http://yemengying.com/2017/02/18/something-about-noSuchBeanDefinitionException/</id>
<published>2017-02-18T07:55:50.000Z</published>
<updated>2018-12-13T04:05:10.000Z</updated>
<content type="html"><blockquote>
<p>这篇博客是来自对两篇文章的翻译,原文链接如下。这两篇文章都总结了在使用 Spring 框架时可能造成 <code>NoSuchBeanDefinitionException</code> 的情况,以及应该如何解决。 </p>
</blockquote>
<a id="more"></a>
<h2 id="原文链接"><a href="#原文链接" class="headerlink" title="原文链接"></a>原文链接</h2><p><a href="http://stackoverflow.com/questions/39173982/what-is-a-nosuchbeandefinitionexception-and-how-do-i-fix-it" target="_blank" rel="external">java - What is a NoSuchBeanDefinitionException and how do I fix it? - Stack Overflow</a><br><a href="http://www.baeldung.com/spring-nosuchbeandefinitionexception" target="_blank" rel="external">Spring NoSuchBeanDefinitionException | Baeldung</a></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p><code>org.springframework.beans.factory.NoSuchBeanDefinitionException</code> 是很常见的异常,可以说绝大多数使用过 Spring 的人都曾遇到过它。本文旨在总结下NoSuchBeanDefinitionException(以下简称 NSBDE)的含义,哪些情况下可能抛出 NSBDE,和如何解决(文中配置均用 JavaConfig)。</p>
<h2 id="什么是-NoSuchBeanDefinitionException"><a href="#什么是-NoSuchBeanDefinitionException" class="headerlink" title="什么是 NoSuchBeanDefinitionException"></a>什么是 NoSuchBeanDefinitionException</h2><p>从字面其实就很好理解,NoSuchBeanDefinitionException 就是没有找到指定 Bean 的 Definition。NoSuchBeanDefinitionException 的 JavaDoc是这样定义的:</p>
<blockquote>
<p>Exception thrown when a BeanFactory is asked for a bean instance for which it cannot find a definition. This may point to a non-existing bean, a non-unique bean, or a manually registered singleton instance without an associated bean definition.</p>
</blockquote>
<p>下面看看可能抛出 NSBDE 的一些情况。</p>
<h2 id="情况1-No-qualifying-bean-of-type-…-found-for-dependency"><a href="#情况1-No-qualifying-bean-of-type-…-found-for-dependency" class="headerlink" title="情况1: No qualifying bean of type […] found for dependency"></a>情况1: No qualifying bean of type […] found for dependency</h2><p>最常见的抛出 NSBDE 的情况就是在一个 BeanA 中注入 BeanB 时找不到 BeanB 的定义。例子如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanA</span> </span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> BeanB dependency;</div><div class="line"> <span class="comment">//...</span></div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>当在 BeanA 中注入 BeanB 时,如果在 Spring 上下文中找不到 BeanB 的定义,就会抛出 NSBDE。异常信息如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">org.springframework.beans.factory.NoSuchBeanDefinitionException: </div><div class="line">No qualifying bean of type [org.baeldung.packageB.BeanB]</div><div class="line"> found for dependency: </div><div class="line">expected at least 1 bean which qualifies as</div><div class="line"> autowire candidate for this dependency. </div><div class="line">Dependency annotations: </div><div class="line"> &#123;@org.springframework.beans.factory.annotation.Autowired(required=true)&#125;</div></pre></td></tr></table></figure></p>
<p>抛异常的原因在异常信息中说的很清楚:<code>expected at least 1 bean which qualifies as autowire candidate for this dependency</code>。所以要么是 BeanB 不存在在 Spring 上下文中(比如没有标注 @ Component,@Repository,@Service, @Controller等注解) ,要么就是 BeanB 所在的包没有被 Spring 扫描到。</p>
<p>解决办法就是先确认 BeanB 有没有被某些注解声明为 Bean:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> org.baeldung.packageB;</div><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanB</span> </span>&#123; ...&#125;</div></pre></td></tr></table></figure></p>
<p>如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="meta">@ComponentScan</span>(<span class="string">"org.baeldung.packageB"</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ContextWithJavaConfig</span> </span>&#123;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<h2 id="情况2-No-qualifying-bean-of-type-…-is-defined"><a href="#情况2-No-qualifying-bean-of-type-…-is-defined" class="headerlink" title="情况2: No qualifying bean of type […] is defined"></a>情况2: No qualifying bean of type […] is defined</h2><p>还有一种可能抛出 NSBDE 的情况是在上下文中存在着两个 Bean,比如有一个接口 IBeanB,它有两个实现类 BeanB1 和 BeanB2。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanB1</span> <span class="keyword">implements</span> <span class="title">IBeanB</span> </span>&#123;</div><div class="line"> <span class="comment">//</span></div><div class="line">&#125;</div><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanB2</span> <span class="keyword">implements</span> <span class="title">IBeanB</span> </span>&#123;</div><div class="line"> <span class="comment">//</span></div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanA</span> </span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> IBeanB dependency;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>异常信息如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: </div><div class="line">No qualifying bean of type</div><div class="line"> [org.baeldung.packageB.IBeanB] is defined: </div><div class="line">expected single matching bean but found 2: beanB1,beanB2</div></pre></td></tr></table></figure></p>
<p>仔细看异常信息会发现,并不是直接抛出 NSBDE,而是它的子类 <code>NoUniqueBeanDefinitionException</code>,这是 Spring 3.2.1 之后引入的新异常,目的就是为了和第一种找不到 Bean Definition 的情况作区分。</p>
<p>解决办法1就是利用 <code>@Qualifier</code> 注解,明确指定要注入的 Bean 的名字(BeanB2 默认的名字就是 beanB2)。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanA</span> </span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="meta">@Qualifier</span>(<span class="string">"beanB2"</span>)</div><div class="line"> <span class="keyword">private</span> IBeanB dependency;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>除了指定名字,我们还可以将其中一个 Bean 加上 <code>@Primary</code>的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="meta">@Primary</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanB1</span> <span class="keyword">implements</span> <span class="title">IBeanB</span> </span>&#123;</div><div class="line"> <span class="comment">//</span></div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>这样 Spring 就能够知道到底应该注入哪个 Bean 了。</p>
<h2 id="情况3-No-Bean-Named-…-is-defined"><a href="#情况3-No-Bean-Named-…-is-defined" class="headerlink" title="情况3: No Bean Named […] is defined"></a>情况3: No Bean Named […] is defined</h2><p>NSBDE 还可能在从 Spring 上下文中通过名字获取一个 Bean 时抛出。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Component</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanA</span> <span class="keyword">implements</span> <span class="title">InitializingBean</span> </span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> ApplicationContext context;</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> </span>&#123;</div><div class="line"> context.getBean(<span class="string">"someBeanName"</span>);</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>在这种情况中,如果找不到指定名字 Bean 的 Definition,就会抛出如下异常:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: </div><div class="line">No bean named &apos;someBeanName&apos; is defined</div></pre></td></tr></table></figure></p>
<h2 id="情况4-代理-Beans"><a href="#情况4-代理-Beans" class="headerlink" title="情况4: 代理 Beans"></a>情况4: 代理 Beans</h2><p>Spring 通过 <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-introduction-proxies" target="_blank" rel="external">AOP 代理</a> 实现了许多高级功能,比如:</p>
<ul>
<li>通过 <a href="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html" target="_blank" rel="external">@Transactional</a>完成 <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html" target="_blank" rel="external">事务管理</a></li>
<li>通过 <a href="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html" target="_blank" rel="external">@Cacheable</a>实现缓存</li>
<li>通过 <a href="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html" target="_blank" rel="external">@Async</a>和 <a href="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html" target="_blank" rel="external">@Scheduled</a>实现任务调度和异步执行</li>
</ul>
<p>Spring 有两种方式实现代理:</p>
<ol>
<li>利用 <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html" target="_blank" rel="external">JDK 动态代理机制</a> ,在运行时为<code>实现了某些接口</code>的类动态创建一个实现了同样接口的代理对象。</li>
<li>使用 <a href="https://github.com/cglib/cglib/wiki" target="_blank" rel="external">CGLIB</a>,CGLIB 可以在运行期扩展Java类与实现Java接口,也就是说当一个类没有实现接口时,必须用 CGLIB 生成代理对象。</li>
</ol>
<p>所以,当 Spring 上下文中的一个实现了某个接口的 Bean 通过JDK 动态代理机制被代理时,代理类并不是继承了目标类,而是实现同样的接口。</p>
<p>也正因为如此,如果一个 Bean 通过接口注入时,可以成功被注入。但如果是通过真正的类注入,那么 Spring 将无法找到匹配这个类的 Definition——因为代理类并没有继承这个类。</p>
<p>以 Spring 中比较常见的事务管理为例,假设 ServiceA 中要注入 ServiceB,两个 Service 均标注了 <code>@Transactional</code>注解来进行事务管理,那么下面的注入方式是不会正常 work 的。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceA</span> <span class="keyword">implements</span> <span class="title">IServiceA</span></span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> ServiceB serviceB;</div><div class="line"> ...</div><div class="line">&#125;</div><div class="line"> </div><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceB</span> <span class="keyword">implements</span> <span class="title">IServiceB</span></span>&#123;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>解决办法就是通过接口来进行注入:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceA</span> <span class="keyword">implements</span> <span class="title">IServiceA</span></span>&#123;</div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> IServiceB serviceB;</div><div class="line">&#125;</div><div class="line"> </div><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServiceB</span> <span class="keyword">implements</span> <span class="title">IServiceB</span></span>&#123;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>今天天气好好啊~<br><img src="https://pic.yupoo.com/jiananshi/8d2d2aba/0fdb443c.jpg" alt="小脑腐"></p>
</content>
<summary type="html">
<blockquote>
<p>这篇博客是来自对两篇文章的翻译,原文链接如下。这两篇文章都总结了在使用 Spring 框架时可能造成 <code>NoSuchBeanDefinitionException</code> 的情况,以及应该如何解决。 </p>
</blockquote>
</summary>
<category term="spring" scheme="http://yemengying.com/categories/spring/"/>
<category term="spring" scheme="http://yemengying.com/tags/spring/"/>
</entry>
<entry>
<title>RabbitMq 如何处理异常</title>
<link href="http://yemengying.com/2017/01/30/how-does-rabbitmq-handle-exception/"/>
<id>http://yemengying.com/2017/01/30/how-does-rabbitmq-handle-exception/</id>
<published>2017-01-30T11:37:21.000Z</published>
<updated>2018-12-13T04:10:36.000Z</updated>
<content type="html"><blockquote>
<p>这应该是过年假期的最后一篇,如果不是,那你一定看到了假博客。(๑•̀ㅂ•́)و✧ </p>
</blockquote>
<a id="more"></a>
<p>在消费 RabbitMq 中的 Message 时,常常会出现异常,可能是 Message 本身格式不对,或者由于某些原因无法被处理。我一般都是 catch 异常然后抛个 <code>AmqpRejectAndDontRequeueException</code> (以下简称 ARADRE ),也出啥问题,不过还是仔细看下,rabbitmq 是如何对待消费消息时出现的异常,是会将消息直接丢弃还是有其他操作。</p>
<p>其实 Spring-amqp <a href="http://docs.spring.io/spring-amqp/reference/html/_reference.html#exception-handling" target="_blank" rel="external">官方文档</a>上对于 RabbitMq 是如何处理异常的说的已经很明白了,不过都是大段的文字可能不太好理解,还是配着代码看一下。</p>
<p>代码版本: 1.6.3.RELEASE</p>
<p>根据官方文档,当 listener 在消费消息时抛出一个异常的时候,该异常会被包装在 <code>ListenerExecutionFailedException</code> 中抛出,并根据 <code>listenerContainer</code> 中 <code>defaultRequeueRejected</code> 设定的值来决定是否将该消息重新加入队列,默认是会重新加入队列。</p>
<p><img src="https://pic.yupoo.com/jiananshi/1ef2b0c3/ae31063d.jpg" alt="ListenerExecutionFailedException结构"></p>
<p>需要注意的是,如果抛出的异常是 ARADRE 或其他被 RabbitMq 认为是致命错误的异常,即便 defaultRequeueRejected 的值为 true , 该消息也不会重新加入队列,而是会被直接丢弃或加入 dead-letter-exchange 中(如果有配置 dead-letter-exchange)。</p>
<p><img src="https://pic.yupoo.com/jiananshi/1a0a19a6/1c5104d9.jpeg" alt="来源https://derickbailey.com/2016/03/28/dealing-with-dead-letters-and-poison-messages-in-rabbitmq/"></p>
<p>在 1.6.3. RELEASE 中被 RabbitMq 认为是致命错误的异常有以下 6 种:</p>
<ul>
<li>o.s.amqp…MessageConversionException</li>
<li>o.s.messaging…MessageConversionException</li>
<li>o.s.messaging…MethodArgumentNotValidException</li>
<li>o.s.messaging…MethodArgumentTypeMismatchException</li>
<li>java.lang.NoSuchMethodException</li>
<li>java.lang.ClassCastException</li>
</ul>
<p>也就是说,当抛出以上异常及 ARADRE 时,该消息一定不会重新入队,即便 defaultRequeueRejected 的值为 true。</p>
<p>下面看看 Spring-RabbitMq 是如何实现的:</p>
<p>在源码中,异常在 <code>AbstractMessageListenerContainer</code> 中被包装在 ListenerExecutionFailedException 中之后还会经由 <code>ErrorHandler</code> 的 <code>handleError</code> 方法处理, 默认的 ErrorHandler 是 <code>ConditionalRejectingErrorHandler</code> 。 </p>
<p>我们也可以实现自己的 ErrorHandler 来控制需要丢弃消息的异常,只要实现 <code>org.springframework.util.ErrorHandler</code> 接口,然后将<code>listenerContainer</code> 中的 <code>errorHandler</code> 参数指定我们自定义的 handler 即可。 </p>
<p>ConditionalRejectingErrorHandler 中配置有 <code>FatalExceptionStrategy</code>,会调用 FatalExceptionStrategy 中的 isFatal 方法来判断异常是不是属于致命异常。</p>
<p>ConditionalRejectingErrorHandler 的具体实现如下:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConditionalRejectingErrorHandler</span> <span class="keyword">implements</span> <span class="title">ErrorHandler</span> </span>&#123;</div><div class="line"></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> FatalExceptionStrategy exceptionStrategy; </div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleError</span><span class="params">(Throwable t)</span> </span>&#123;</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.logger.isWarnEnabled()) &#123;</div><div class="line"> <span class="keyword">this</span>.logger.warn(<span class="string">"Execution of Rabbit message listener failed."</span>, t);</div><div class="line"> &#125;</div><div class="line"> <span class="comment">// 如果是致命异常,则转为 AmqpRejectAndDontRequeueException 抛出</span></div><div class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.causeChainContainsARADRE(t) &amp;&amp; <span class="keyword">this</span>.exceptionStrategy.isFatal(t)) &#123;</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> AmqpRejectAndDontRequeueException(<span class="string">"Error Handler converted exception to fatal"</span>, t);</div><div class="line"> &#125;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * <span class="doctag">@return</span> true if the cause chain already contains an</div><div class="line"> * &#123;<span class="doctag">@link</span> AmqpRejectAndDontRequeueException&#125;.</div><div class="line"> */</div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">causeChainContainsARADRE</span><span class="params">(Throwable t)</span> </span>&#123;</div><div class="line"> Throwable cause = t.getCause();</div><div class="line"> <span class="keyword">while</span> (cause != <span class="keyword">null</span>) &#123;</div><div class="line"> <span class="keyword">if</span> (cause <span class="keyword">instanceof</span> AmqpRejectAndDontRequeueException) &#123;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> &#125;</div><div class="line"> cause = cause.getCause();</div><div class="line"> &#125;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">&#125;</div><div class="line"></div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * Default implementation of &#123;<span class="doctag">@link</span> FatalExceptionStrategy&#125;.</div><div class="line"> * <span class="doctag">@since</span> 1.6.3</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultExceptionStrategy</span> <span class="keyword">implements</span> <span class="title">FatalExceptionStrategy</span> </span>&#123;</div><div class="line"></div><div class="line"> <span class="comment">// 判断传入参数 是不是 致命异常</span></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isFatal</span><span class="params">(Throwable t)</span> </span>&#123;</div><div class="line"> <span class="keyword">if</span> (t <span class="keyword">instanceof</span> ListenerExecutionFailedException</div><div class="line"> &amp;&amp; isCauseFatal(t.getCause())) &#123;</div><div class="line"> <span class="keyword">if</span> (ConditionalRejectingErrorHandler.<span class="keyword">this</span>.logger.isWarnEnabled()) &#123;</div><div class="line"> ConditionalRejectingErrorHandler.<span class="keyword">this</span>.logger.warn(</div><div class="line"> <span class="string">"Fatal message conversion error; message rejected; "</span></div><div class="line"> + <span class="string">"it will be dropped or routed to a dead letter exchange, if so configured: "</span></div><div class="line"> + ((ListenerExecutionFailedException) t).getFailedMessage());</div><div class="line"> &#125;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> &#125;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">isCauseFatal</span><span class="params">(Throwable cause)</span> </span>&#123;</div><div class="line"> <span class="keyword">return</span> cause <span class="keyword">instanceof</span> MessageConversionException</div><div class="line"> || cause <span class="keyword">instanceof</span> org.springframework.messaging.converter.MessageConversionException</div><div class="line"> || cause <span class="keyword">instanceof</span> MethodArgumentNotValidException</div><div class="line"> || cause <span class="keyword">instanceof</span> MethodArgumentTypeMismatchException</div><div class="line"> || cause <span class="keyword">instanceof</span> NoSuchMethodException</div><div class="line"> || cause <span class="keyword">instanceof</span> ClassCastException</div><div class="line"> || isUserCauseFatal(cause);</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"> * 通过重写该方法来添加自定义的异常</div><div class="line"> * Subclasses can override this to add custom exceptions.</div><div class="line"> * <span class="doctag">@param</span> cause the cause</div><div class="line"> * <span class="doctag">@return</span> true if the cause is fatal.</div><div class="line"> */</div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">isUserCauseFatal</span><span class="params">(Throwable cause)</span> </span>&#123;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>代码比较长,简单来说,就是 ConditionalRejectingErrorHandler 的 handleError 会先判断接到的异常中的 cause 是不是 ARADRE,如果不是再调用 FatalExceptionStrategy 的 isFatal 方法,判断是不是致命异常中的一种,如果是,则将异常转为 ARADRE 抛出,该消息也就不会重新入队。</p>
<p>如果想要把自定义的异常加入到 fatalException, 一个简单的办法就是提供新的 FatalExceptionStrategy ,只要继承 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 并重写 isUserCauseFatal(Throwable cause) 方法,在方法里对于需要丢弃消息的异常返回 true即可。</p>
<p>再简单看下,RabbitMq 判断是否需要将消息重入队列的部分逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// We should always requeue if the container was stopping</span></div><div class="line"><span class="keyword">boolean</span> shouldRequeue = <span class="keyword">this</span>.defaultRequeuRejected || ex <span class="keyword">instanceof</span> MessageRejectedWhileStoppingException;</div><div class="line">Throwable t = ex;</div><div class="line"><span class="keyword">while</span> (shouldRequeue &amp;&amp; t != <span class="keyword">null</span>) &#123;</div><div class="line"> <span class="keyword">if</span> (t <span class="keyword">instanceof</span> AmqpRejectAndDontRequeueException) &#123;</div><div class="line"> shouldRequeue = <span class="keyword">false</span>;</div><div class="line"> &#125;</div><div class="line"> t = t.getCause();</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>根据上面的代码,如果处理消息时出现异常,在判断是否需要入队时,会将 shouldRequeue 变量等于 this.defaultRequeuRejected ||ex instanceof MessageRejectedWhileStoppingException 的值,然后如果异常是 ARADRE, 不管之前 shouldRequeue 的值是什么,都会被置为 false。最后根据 shouldRequeue 的值来决定是否需要重新入队。 </p>
<p>可以用下图总结一下:<br><img src="//yemengying.com/qiniu/image/2017-02-05-j%20-1-.png?imageMogr2/thumbnail/667x618!" alt="总结"></p>
<p>最后。。Cookie 宝宝祝大家新春快乐~~<br><img src="https://pic.yupoo.com/jiananshi/6bcb0d8b/1461eb70.jpg" alt="cookie 宝宝"></p>
</content>
<summary type="html">
<blockquote>
<p>这应该是过年假期的最后一篇,如果不是,那你一定看到了假博客。(๑•̀ㅂ•́)و✧ </p>
</blockquote>
</summary>
<category term="rabbitmq" scheme="http://yemengying.com/categories/rabbitmq/"/>
<category term="rabbitmq" scheme="http://yemengying.com/tags/rabbitmq/"/>
<category term="spring" scheme="http://yemengying.com/tags/spring/"/>
</entry>
<entry>
<title>为Rabbitmq中的Jackson2JsonMessageConverter自定义ClassMapper</title>
<link href="http://yemengying.com/2017/01/03/rabbitmq-classMapper/"/>
<id>http://yemengying.com/2017/01/03/rabbitmq-classMapper/</id>
<published>2017-01-03T14:40:30.000Z</published>
<updated>2018-12-13T04:11:34.000Z</updated>
<content type="html"><blockquote>
<p>新年第一篇~~ 🐣🐥🐤🐔</p>
</blockquote>
<a id="more"></a>
<p>消息队列算是各个系统间通信比较常见的方式了。我们公司用的是是基于 AMQP 协议的 <a href="http://www.rabbitmq.com/" target="_blank" rel="external">RabbitMq</a>。在 Spring-AMQP 中比较重要的类就是 <code>Message</code>,因为要发送的消息必须要构造成一个 Message 对象来进行传输。Message 对象包括两部分 Body 和 Properties,Body 就是真正要发送的消息内容,Properties 就是和消息相关的一些属性(消息头,要发送的交换机,routingKey等等),主要结构如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Message</span> </span>&#123;</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> MessageProperties messageProperties;</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">byte</span>[] body;</div><div class="line"></div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>消息生产者构造好 Message 之后,就会将 Message 发送到指定的 Exchange (交换机),再根据 Exchange 的类型及 routing-key 将消息路由到相应的 queue 中,最后被监听该 queue 的消费者消费,大致如下图:</p>
<p><img src="https://pic.yupoo.com/jiananshi/d22db668/11c183f2.jpg" alt="流程1"></p>
<p>不过每次发消息都要自己构造 Message 对象比较麻烦。Spring-AMQP 允许我们直接使用自定义的类,然后会利用指定好的 <code>MessageConverter</code> 将自定义的类转换为 Message 进行发送,在接收时也会利用 MessageConverter 将接收到的 Message 对象转成需要的对象。Spring-AMQP 提供了多种 MessageConverter,比如 SimpleMessageConverter,SerializerMessageConverter,Jackson2JsonMessageConverter,MarshallingMessageConverter等等,如果发送的消息对象不是 Message 实例,并且没有指定 MessageConverter 的话,默认用 SimpleMessageConverter。以上各种 MessageConverter 归根结底都是实现了 MessageConverter 接口,该接口只有两个方法:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MessageConverter</span> </span>&#123;</div><div class="line"> <span class="function">Message <span class="title">toMessage</span><span class="params">(Object object, MessageProperties messageProperties)</span> <span class="keyword">throws</span> MessageConversionException</span>;</div><div class="line"> <span class="function">Object <span class="title">fromMessage</span><span class="params">(Message message)</span> <span class="keyword">throws</span> MessageConversionException</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>这两个方法一个是在发送消息时将我们的消息对象转换成标准的 Message 对象,另一个是在接受消息时将 Message 对象转换为相应的对象。<br>比较常用的 Converter 就是 Jackson2JsonMessageConverter(以下简称 JsonMessageConverter),在发送消息时,它会先将自定义的消息类序列化成json格式,再转成byte构造 Message,在接收消息时,会将接收到的 Message 再反序列化成自定义的类。大致流程如下图:<br><img src="https://pic.yupoo.com/jiananshi/9900549c/d73e4d16.jpg" alt="流程2"></p>
<p>不过使用 JsonMessageConverter 时有一个小问题,在<code>不对它进行任何改造的前提下</code>,发送消息的类和接受消息的类必须是一样的,不仅是要里面的字段一样,类名一样,连类的包路径都要一样。</p>
<p>所以当系统1使用 JsonMessageConverter 发送消息类A给系统2时,系统2可以有如下几种方式来接收:</p>
<ul>
<li>1.依赖系统1的jar包,直接使用类A来接收 </li>
<li>2.不依赖系统1的jar包,自己建一个和A一模一样的类,连名称,包路径都一样</li>
<li>3.负责监听 queue 的类实现 MessageListener 接口,直接接收 Message 类,再自己转换 </li>
</ul>
<p>上面三个方法都不是很好,按照正常的想法,我们肯定是期望系统2直接使用自己的类来接收就可以了,只要与A类的字段名一样即可。那有没有方法可以让系统2既不依赖无用的jar包,也不用建立个与自己命名规范不相符的类, 也无需自己转换呢?</p>
<p>要解决这个问题,就要先看看 JsonMessageConverter 是如何将 Message 进行反序列化的。<br>在 JsonMessageConverter 的 fromMessage 方法中有这么一段:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (getClassMapper() == <span class="keyword">null</span>) &#123;</div><div class="line"> JavaType targetJavaType = getJavaTypeMapper()</div><div class="line"> .toJavaType(message.getMessageProperties());</div><div class="line"> content = convertBytesToObject(message.getBody(), encoding, targetJavaType);</div><div class="line">&#125; <span class="keyword">else</span> &#123;</div><div class="line"> Class&lt;?&gt; targetClass = getClassMapper().toClass(</div><div class="line"> message.getMessageProperties());</div><div class="line"> content = convertBytesToObject(message.getBody(), encoding, targetClass);</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>就是说默认情况下,JsonMessageConverter 使用的 ClassMapper 是 <code>DefaultJackson2JavaTypeMapper</code>,在转换时通过 Message 的 Properties 来获取要转换的目标类的类型。通过 Debug 可以发现,目标类的类型是存储在 Message 的 Proterties 的 一个 headers 的 Map 中,Key 叫“__TypeId__”。所以只要想办法在传输消息时更改__TypeId__的值即可。</p>
<p>下面是解决办法,在消息的生产者端为 JsonMessageConverter, 设置一个自定义的 ClassMapper,重写 fromClass 方法,将 __TypeId__ 的值设为消费端用来接收的类的路径+名称。当然了,也可以在消费者端重写toClass方法,直接返回想要转换的目标类的类类型。两种选一种就可以。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Bean</span></div><div class="line"><span class="function"><span class="keyword">public</span> Jackson2JsonMessageConverter <span class="title">customConverter</span><span class="params">()</span> </span>&#123;</div><div class="line"> Jackson2JsonMessageConverter converter = <span class="keyword">new</span> Jackson2JsonMessageConverter();</div><div class="line"> converter.setClassMapper(<span class="keyword">new</span> ClassMapper() &#123;</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="keyword">public</span> Class&lt;?&gt; toClass(MessageProperties properties) &#123;</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException(<span class="string">"this mapper is only for outbound, do not use for receive message"</span>);</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fromClass</span><span class="params">(Class&lt;?&gt; clazz, MessageProperties properties)</span> </span>&#123;</div><div class="line"> properties.setHeader(<span class="string">"__TypeId__"</span>, <span class="string">"com.xxx.B"</span>);</div><div class="line"> &#125;</div><div class="line"></div><div class="line"> &#125;);</div><div class="line"> <span class="keyword">return</span> converter;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<blockquote>
<p>感觉自己语言组织能力退化了。。。。。</p>
</blockquote>
</content>
<summary type="html">
<blockquote>
<p>新年第一篇~~ 🐣🐥🐤🐔</p>
</blockquote>
</summary>
<category term="rabbitmq" scheme="http://yemengying.com/categories/rabbitmq/"/>
<category term="rabbitmq" scheme="http://yemengying.com/tags/rabbitmq/"/>
<category term="spring" scheme="http://yemengying.com/tags/spring/"/>
</entry>
<entry>
<title>log 不打印异常堆栈</title>
<link href="http://yemengying.com/2016/12/18/log4j-not-printing-stacktrace-for-eception/"/>
<id>http://yemengying.com/2016/12/18/log4j-not-printing-stacktrace-for-eception/</id>
<published>2016-12-18T06:17:33.000Z</published>
<updated>2018-12-13T04:14:48.000Z</updated>
<content type="html"><blockquote>
<p>和上篇内容并不重复 🙃</p>
</blockquote>
<a id="more"></a>
<p>最近由于规则引擎有问题,导致产线上的一个 job 会抛 NullPointerException。本来这是个已知的问题,也没什么,已经联系对应的人去修复了。可由此发现了另外一个问题, fireman 的告警邮件只有异常的名称,而没有异常堆栈。<br><img src="//yemengying.com/qiniu/image/2016-12-18-Screen%20Shot%202016-12-18%20at%204.22.52%20PM.png" alt="fireman"></p>
<p>这就很令人懵圈了,因为不知道是已知的规则引擎的问题还是其他问题。</p>
<p><img src="https://pic.yupoo.com/jiananshi/e0ffe3d5/b2e1a958.jpeg" alt="??"></p>
<p>先看了下对应 job 的代码,确认打印异常的姿势是正确的, 本地也可以正常打印。然后去搜了下对应日期的 log 文件,确实有一堆 NPE 的报错,不过惊喜的发现在一开始的时候其实是有打出堆栈的,只是到后面就没有了。</p>
<p>最后终于在 <a href="http://stackoverflow.com/questions/2295015/log4j-not-printing-the-stacktrace-for-exceptions" target="_blank" rel="external">stackoverflow</a> 上找到了答案。</p>
<blockquote>
<p>The compiler in the server VM now provides correct stack backtraces for all “cold” built-in exceptions. For performance purposes, when such an exception is thrown a few times, the method may be recompiled. After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace. To disable completely the use of preallocated exceptions, use this new flag: -XX:-OmitStackTraceInFastThrow.</p>
</blockquote>
<p>个人理解就是,JVM 为了性能会做优化,如果频繁的抛出某个异常,会重新编译,不再打印异常堆栈。<br>解决这个问题也比较简单,如果不想每次都去查前面的 log 去看堆栈,只要在启动参数加上 <code>-XX:-OmitStackTraceInFastThrow</code>,就可以禁用该优化,强制打印异常堆栈。这样可能会导致,log 文件过大,不过产线上今天之前的 log 文件都会被压缩,所以感觉问题也不大。</p>
<p>Ps:用 iPic 上传图片真是好用到飞起,非常适合我这种喜欢插图星人,多谢洪菊的良心推荐。<br><img src="https://pic.yupoo.com/jiananshi/f74936ec/a339d728.png" alt="敲好用"></p>
</content>
<summary type="html">
<blockquote>
<p>和上篇内容并不重复 🙃</p>
</blockquote>
</summary>
<category term="java" scheme="http://yemengying.com/categories/java/"/>
<category term="java" scheme="http://yemengying.com/tags/java/"/>
</entry>
<entry>
<title>Clean Code, Clean Log</title>
<link href="http://yemengying.com/2016/11/18/clean-code-clean-log/"/>
<id>http://yemengying.com/2016/11/18/clean-code-clean-log/</id>
<published>2016-11-18T03:14:13.000Z</published>
<updated>2018-12-13T04:20:09.000Z</updated>
<content type="html"><blockquote>
<p>最近提的 PR 都有关于 Log 的 comment,不能忍,以下内容总结整理自明佳的 Comment 和网络资料,只是为了以后提 PR 之前过来扫一眼,尽量避免 Log 上的疏忽,不一定适用于所有人。</p>
</blockquote>
<a id="more"></a>
<p>在程序中的适当位置打 Log 的重要性就不用多说了,很多人应该都体会过线上有 Bug 却由于没有打 log 而不好 troubleshooting 的经历。<br><img src="https://pic.yupoo.com/jiananshi/52d605e4/06ead603.gif" alt="T^T"></p>
<h3 id="相关文档(康桑哈密达)"><a href="#相关文档(康桑哈密达)" class="headerlink" title="相关文档(康桑哈密达)"></a>相关文档(康桑哈密达)</h3><ul>
<li><a href="http://www.nurkiewicz.com/2010/05/clean-code-clean-logs-use-appropriate.html" target="_blank" rel="external">Clean code, clean logs</a>(👍很赞)</li>
</ul>
<h3 id="SLF4J-VS-Log4J"><a href="#SLF4J-VS-Log4J" class="headerlink" title="SLF4J VS Log4J"></a>SLF4J VS Log4J</h3><p>有很多关于打 Log 的第三方库,也没有多研究过,接触过的就是 <code>SLF4J</code> 和 <code>Log4J</code> 了,不过墙裂建议用 <a href="http://www.slf4j.org/manual.html" target="_blank" rel="external">SLF4J</a>,使用占位符 {} 真的比加号拼接字符串可读性提高N倍啊!!!</p>
<p>感受一下&lt;( ̄︶ ̄)&gt;<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// SLF4J, good</span></div><div class="line">log.error(<span class="string">"Invest loan failed, loan Id: &#123;&#125;, error: &#123;&#125;"</span>, loanId, errorMsg);</div><div class="line"><span class="comment">// Log4J, bad</span></div><div class="line">log.error(<span class="string">"Invest loan failed, loan Id:"</span> + loanId + <span class="string">" error: "</span> + errorMsg);</div></pre></td></tr></table></figure></p>
<p>当然,SLF4J 还有其他的优点,比如不用字符串拼接,节省了创建 String 对象所耗费的资源之类的。不过我最看重的就是可读性高了。<br><img src="https://pic.yupoo.com/jiananshi/ab4a80e5/6c60836c.gif" alt="喵喵呜"></p>
<h3 id="Logging-Level"><a href="#Logging-Level" class="headerlink" title="Logging Level"></a>Logging Level</h3><ul>
<li>ERROR - 记录一些比较严重的错误,比如一些严重异常,数据库链接不可用等等</li>
<li>WARN - 记录一些系统可以容忍的异常,或者是一些警示信息。比如:”Current data unavailable, using cached values”。</li>
<li>INFO - 记录一些比较重要的操作,能反映程序运行状态的。比如:”[Who] booked ticket from [Where] to [Where]”</li>
<li>DEBUG - 一些帮助调试的信息</li>
<li>TRACE - 嗯,这个级别俺也没用过。</li>
</ul>
<h3 id="Pay-attention"><a href="#Pay-attention" class="headerlink" title="Pay attention"></a>Pay attention</h3><ul>
<li><p>Log 信息首字母大写<br>这点完全是为了看上去舒服,至于到底需不需要大写,见仁见智吧~,不过我还是要注意一下,要大写。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// good</span></div><div class="line">log.error(<span class="string">"Invest loan failed, loan Id: &#123;&#125;, error: &#123;&#125;"</span>, loanId, errorMsg);</div><div class="line"><span class="comment">// bad</span></div><div class="line">log.error(<span class="string">"invest loan failed, loan Id: &#123;&#125;, error: &#123;&#125;"</span>, loanId, errorMsg);</div></pre></td></tr></table></figure>
</li>
<li><p>避免 Log 中的 NullPointerException<br>如果像下面这样记 Log,要注意确保 loan 不会为null, 不然打 Log 时抛个 NPE,想想就蛋疼。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">log.info(<span class="string">"Invest loan : &#123;&#125;"</span>, loan.getId());</div></pre></td></tr></table></figure>
</li>
<li><p>Log 的信息简洁有用<br>Log 的内容一定要是有用的,能反映出程序的运行状态,能帮助定位错误。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// good</span></div><div class="line">log.info(<span class="string">"Invest loan with id:&#123;&#125;"</span>, loanId);</div><div class="line"><span class="comment">// bad</span></div><div class="line">log.info(<span class="string">"Invest loan"</span>);</div></pre></td></tr></table></figure>
</li>
<li><p>记录某些方法的入参和出参<br>记录方法的入参和出参,也可以帮助我们定位问题。特别是调用提供接口给其他系统调用的时候,记录入参可以帮助分辨到底是谁的锅🌚。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">printDocument</span><span class="params">(Document doc, Mode mode)</span> </span>&#123;</div><div class="line"> log.debug(<span class="string">"Entering printDocument(doc=&#123;&#125;, mode=&#123;&#125;)"</span>, doc, mode);</div><div class="line"> String id = <span class="comment">//Lengthy printing operation</span></div><div class="line"> log.debug(<span class="string">"Leaving printDocument(): &#123;&#125;"</span>, id);</div><div class="line"> <span class="keyword">return</span> id;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>合适的记录异常<br>大家都知道要在记录程序运行中抛出的异常,但有的时候方式可能是不对的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">try</span>&#123;</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">"Just for test"</span>);</div><div class="line"> &#125; <span class="keyword">catch</span> (Exception e)&#123;</div><div class="line"> log.error(e); <span class="comment">//A </span></div><div class="line"> log.error(e, e); <span class="comment">//B </span></div><div class="line"> log.error(<span class="string">""</span> + e); <span class="comment">//C </span></div><div class="line"> log.error(e.toString()); <span class="comment">//D </span></div><div class="line"> log.error(e.getMessage()); <span class="comment">//E </span></div><div class="line"> log.error(<span class="keyword">null</span>, e); <span class="comment">//F </span></div><div class="line"> log.error(<span class="string">""</span>, e); <span class="comment">//G</span></div><div class="line"> log.error(<span class="string">"&#123;&#125;"</span>, e); <span class="comment">//H </span></div><div class="line"> log.error(<span class="string">"&#123;&#125;"</span>, e.getMessage()); <span class="comment">//I </span></div><div class="line"> log.error(<span class="string">"Error reading configuration file: "</span> + e); <span class="comment">//J </span></div><div class="line"> log.error(<span class="string">"Error reading configuration file: "</span> + e.getMessage()); <span class="comment">//K </span></div><div class="line"> log.error(<span class="string">"Error reading configuration file"</span>, e); <span class="comment">//L</span></div><div class="line"> &#125;</div></pre></td></tr></table></figure>
<p>在上面 12 种打印异常的方式中,只有 G 和 L是正确的。A 和 B 在使用 SLF4J 时会编译不通过, 其他的几种要么不会打印异常堆栈,要么会打印出不正确的信息。比如,E 的方式只会打印”Just for test”的信息,而不会打印异常类型和异常堆栈。在 SLF4J 中,第一个参数是文本信息,简单描述一下异常;第二个参数要传异常本身,而不是<code>e.getMessage()</code>或<code>e.toString()</code>,这样才能打印出异常堆栈,方便定位问题。</p>
</li>
</ul>
<p>希望可以消灭和 Log 有关的 Comment。<br><img src="https://pic.yupoo.com/jiananshi/872e177c/aa1c06dc.jpeg" alt="biu biu biu~"></p>
<blockquote>
<p>欢迎指正错误,欢迎一起讨论~(≧▽≦)/~。</p>
</blockquote>
</content>
<summary type="html">
<blockquote>
<p>最近提的 PR 都有关于 Log 的 comment,不能忍,以下内容总结整理自明佳的 Comment 和网络资料,只是为了以后提 PR 之前过来扫一眼,尽量避免 Log 上的疏忽,不一定适用于所有人。</p>
</blockquote>
</summary>
<category term="随笔" scheme="http://yemengying.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="http://yemengying.com/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>【转】EVE 早期成员面基</title>
<link href="http://yemengying.com/2016/11/15/i-love-eve/"/>
<id>http://yemengying.com/2016/11/15/i-love-eve/</id>
<published>2016-11-15T05:12:46.000Z</published>
<updated>2017-06-10T07:56:03.000Z</updated>
<content type="html"><blockquote>
<p>第一次转别人的博客,想看原文的请移步<a href="http://kevin.doyeden.com/2016/11/13/with-sunshine-after-one-year/" target="_blank" rel="external">三“观”茅庐</a>,我才不会告诉你们原文有大神高清无码照的!!!</p>
</blockquote>
<a id="more"></a>
<p>先说点自己想说的, EVE 是毕业后第一份工作做的第一个产品(也可以说第一份工作做得唯一的产品,后面的那个我实在不想承认是我写的🙄),做 EVE 的那段日子是到目前为止毕业后最开心的时光,学习到了很多东西也认识了很多很棒的小伙伴,不过也就是因为那段时光太过美好,才导致在部门拆分,EVE 团队大换血之后萌生了离职的念头。唉,没有对比就没有伤害啊。不过还是很感谢前前司,老大还有磊哥收留技艺不精的我。</p>
<p>上周和之前的 EVE 小伙伴搞了次聚餐,见到了一年多没见的 sunshine 大神,之前做 EVE 时,前期一直和大神联调,虽未看过大神的代码,但也在 API 文档评审时深刻感受到了大神的代码洁癖。嗯,有代码洁癖的人代码一定写的好。</p>
<p>最后,希望下次聚餐可以听到大神讲段子😏。</p>
<p>—————————————分割线,以下内容来自hongju’s blog—————————————————</p>
<p>这次面基的成因主要还是因为前两天看微信通讯录,sunshine 大神的头像换成了一只狗。于是就聊了起来,然后就有了今晚的这次聚餐~ sunshine 大神去年离职后,大约在十月一之前聚过一次餐,当时住的也比较近,后来大神搬到浦东,于是一年未见。于是又约到阿姨,卢神,凑了一次烤肉。</p>
<p>未经大神同意,先偷偷的放一张 sunshine 的照片吧~</p>
<p>&lt;此处应该有照片&gt;</p>
<p>大神,阿姨,卢神和我们当时一起做在 * 公司做 EVE 这款产品,阿姨和卢神是EVE的后端主力,sunshine 算是我的 mentor,带着我用 cordova 做客户端,说白了就是用 H5 技术来做客户端。那段日子学到了很多东西,sunshine 大神的代码一直很 clean,给我做了非常好的榜样。以至于这一年多来,没见过比大神更加干净的代码。也就说,当我看那些人代码的时候,抑制不住吐槽的情绪。在 sunshine 的良好影响下,我自己也尽量写最干净的代码。</p>
<p>除了代码之外,sunshine 对技术的追求也给我树立了很好的榜样。其实,在公司就会感觉到很多人对技术仅限于表面,或者能用就行。一点点追求都没有,这点,很致命。</p>
<p>说完技术,再说说 sunshine 大神的 RP,大神不仅技术也好,做朋友也很好。非常踏实,有追求,因此我觉得自己非常幸运,能在自己毕业正式签约的第一家公司就能遇到这么好的 mentor。可惜的是,sunshine 离职,丢下的 EVE 这款美丽的产品于不靠谱的 sjn 之手。这里不再吐槽了。最近得知,EVE 这款产品更名 **。靠!</p>
<p>说完,sunshine , 这篇文章基本就算完事了。</p>
<p>EVE 的早期成员都是非常靠谱,小团队,又能成事,现在很多人离开了公司,一部分人也分布在不同的业务线上,并且都混得很不错。期待着,哪一天能再次聚在一起~也不枉一起为了 EVE 熬了许多夜。</p>
</content>
<summary type="html">
<blockquote>
<p>第一次转别人的博客,想看原文的请移步<a href="http://kevin.doyeden.com/2016/11/13/with-sunshine-after-one-year/">三“观”茅庐</a>,我才不会告诉你们原文有大神高清无码照的!!!</p>
</blockquote>
</summary>
<category term="随笔" scheme="http://yemengying.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="http://yemengying.com/tags/%E9%9A%8F%E7%AC%94/"/>
<category term="转载" scheme="http://yemengying.com/tags/%E8%BD%AC%E8%BD%BD/"/>
</entry>
<entry>
<title>Something about Spring Transaction</title>
<link href="http://yemengying.com/2016/11/14/something-about-spring-transaction/"/>
<id>http://yemengying.com/2016/11/14/something-about-spring-transaction/</id>
<published>2016-11-14T13:30:34.000Z</published>
<updated>2018-12-13T04:23:34.000Z</updated>
<content type="html"><blockquote>
<p>记录一个上周遇到的小问题。</p>
</blockquote>
<a id="more"></a>
<p>后端开发免不了要和事务打交道,比较常用的就是利用 Spring 框架的声明式事务管理,简单的说就是在需要事务管理的类或方法上添加 <code>@Transactional</code> 注解,然后在配置类上添加 <code>@EnableTransactionManagement</code>注解(这里配置采用 JavaConfig 的方式,如果是 XML, 是要在 XML 文件中添加<code>&lt;tx:annotation-driven/&gt;</code>)。然后 Spring 框架会利用 AOP 在相关方法调用的前后进行事务管理。</p>
<p>一直以来也没出什么岔子,直到。。。。。。。。</p>
<p>上周写了段大概长下面样纸的代码。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GiraffeServiceImpl</span> <span class="keyword">implements</span> <span class="title">GiraffeService</span> </span>&#123;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">A</span><span class="params">(List&lt;Giraffe&gt; giraffes)</span> </span>&#123;</div><div class="line"> </div><div class="line"> <span class="keyword">for</span> (Giraffe giraffe : giraffes) &#123;</div><div class="line"> B(giraffe);</div><div class="line"> &#125;</div><div class="line"> &#125;</div><div class="line"> </div><div class="line"> <span class="meta">@Transactional</span>(<span class="string">"transactionManager"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">B</span><span class="params">(Giraffe giraffe)</span> </span>&#123;</div><div class="line"> <span class="comment">// Step 1: update something</span></div><div class="line"> <span class="comment">// Step 2: insert something</span></div><div class="line"> <span class="comment">// Step 3: update something</span></div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。</p>
<p>然鹅,通过下面的 Junit 测试方法 A 的时候发现方法 B 的事务并没有开启, 而直接调用方法 B 事务是正常开启的。 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GiraffeServiceTest</span></span>&#123;</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> GiraffeService giraffeService;</div><div class="line"> </div><div class="line"> <span class="comment">// 没有开启事务</span></div><div class="line"> <span class="meta">@Test</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testA</span><span class="params">()</span> </span>&#123;</div><div class="line"> giraffeService.A();</div><div class="line"> &#125;</div><div class="line"> </div><div class="line"> <span class="comment">// 正常开启事务</span></div><div class="line"> <span class="meta">@Test</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testB</span><span class="params">()</span> </span>&#123;</div><div class="line"> giraffeService.B();</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p><img src="https://pic.yupoo.com/jiananshi/42081bb5/9b2b2888.jpg" alt="T^T"></p>
<p>问了下明佳和超哥之后,终于有点明白了🤔</p>
<p>Spring 在加载目标 Bean 的时候,会为声明了 @Transactional 的 目标 Bean 创造一个代理类,而<code>目标类本身并不能感知到代理类的存在</code>。调用通过 Spring 上下文注入的 Bean 的方法, 并不是直接调用目标类的方法。<br><img src="https://pic.yupoo.com/jiananshi/72b75974/a642fa86.png" alt="不是"><br>而是先调用代理类的方法,再调用目标类的。<br><img src="https://pic.yupoo.com/jiananshi/a08dbbac/a91d9ecf.png" alt="是"></p>
<p>对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器<code>TransactionInterceptor</code>开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图。<br><img src="https://pic.yupoo.com/jiananshi/f445d468/d8b89267.png" alt="transaction manager"></p>
<p>而对于第一段的代码,我在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以。。。事务是不会开启滴。</p>
<p>解决办法也蛮简单,通过实现ApplicationContextAware接口获得 Spring 的上下文,然后获得目标类的代理类,通过代理类的对象,调用方法 B,即可。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GiraffeServiceImpl</span> <span class="keyword">implements</span> <span class="title">GiraffeService</span>,<span class="title">ApplicationContextAware</span></span>&#123;</div><div class="line"></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> ApplicationContext applicationContext;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">A</span><span class="params">(List&lt;Giraffe&gt; giraffes)</span> </span>&#123;</div><div class="line"> </div><div class="line"> GiraffeService service = applicationContext.getBean(GiraffeService.class);</div><div class="line"> <span class="keyword">for</span> (Giraffe giraffe : giraffes) &#123;</div><div class="line"> service.B(giraffe);</div><div class="line"> &#125;</div><div class="line"> &#125;</div><div class="line"> </div><div class="line"> <span class="meta">@Transactional</span>(<span class="string">"transactionManager"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">B</span><span class="params">(Giraffe giraffe)</span> </span>&#123;</div><div class="line"> <span class="comment">// Step 1: update something</span></div><div class="line"> <span class="comment">// Step 2: insert something</span></div><div class="line"> <span class="comment">// Step 3: update something</span></div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure></p>
<p>stackoverflow 上也有相关的问题:<br><a href="http://stackoverflow.com/questions/1099025/spring-transactional-what-happens-in-background" target="_blank" rel="external">@Transactional - What happens in background?</a><br><a href="http://stackoverflow.com/questions/23931698/spring-transactional-annotation-self-invocation" target="_blank" rel="external">@Transactional Annotation : Self Invocation</a></p>
<p>唉,都快写完了,还没等来 wuli 悦儿</p>
<p><img src="https://pic.yupoo.com/jiananshi/95e282f8/8e7068a2.png" alt="wuli 悦儿"></p>
</content>
<summary type="html">
<blockquote>
<p>记录一个上周遇到的小问题。</p>
</blockquote>
</summary>
<category term="随笔" scheme="http://yemengying.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="http://yemengying.com/tags/%E9%9A%8F%E7%AC%94/"/>
<category term="spring" scheme="http://yemengying.com/tags/spring/"/>
</entry>
<entry>
<title>Running Man 7012</title>
<link href="http://yemengying.com/2016/10/27/runningman-7012/"/>
<id>http://yemengying.com/2016/10/27/runningman-7012/</id>
<published>2016-10-27T13:47:17.000Z</published>
<updated>2018-12-13T04:34:05.000Z</updated>
<content type="html"><blockquote>
<p>前两天刷微博时突然看到 Gary 即将从 《Running Man》(以下简称 RM) 下车的消息,有点伤感,却并不惊讶。可能从2016年开始,就隐约感到节目到了瓶颈,收视率也一直上不来,作为一个铁杆粉丝也不得不承认 RM 没有以前好看了,很长时间没有一期看好几遍的情况了。不过追了 RM 四年,看 RM 早已不是为了娱乐搞笑,而变成了自己生活的一部分,只要看到他们七个就开心。也许在不久的将来连 RM 都会停播了,但现在只要 RM 更新一期,就会看一期,哪怕就是他们几个嗑瓜子闲聊天。即便有再火再好看的节目,自己也早没了当初的心境去追了。改编我前女神的一句歌词就是“有的综艺说不清哪里好,但就是谁都替代不了”。</p>
</blockquote>
<a id="more"></a>
<p>好了,结束伤感的话题。</p>
<p>本来想写篇 RM 科普文,但对 RM 不感冒估计怎么说也不会感兴趣吧。所以就简单写点,纪念下我爱的七只和第一次也是最后一次追了四年看过每一期的 RM。部分资料和图片来源于网络,权侵删。</p>
<h3 id="七只"><a href="#七只" class="headerlink" title="七只"></a>七只</h3><p>RM 能收获那么的喜爱和关注,虽然离不开制作组的精心制作和创意,但更重要的是七个 MC 的人格魅力。So…..就从介绍七只开始,聊聊 RM 吧。</p>
<ul>
<li><p><strong>刘在石</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/80534c41/b8231039.jpeg" alt="刘大神"><br> 外号:刘大神、刘鲁斯·威利斯、刘姆斯·邦德、蚂蚱/蚱蜢、刘赫</p>
<p> 国民 MC,也应该是目前韩国地位最高的 MC 了。RM 的灵魂人物,美好的一塌糊涂(我是不是和驴得水里面的教育部长一样不会用词🤓)。会记住工作人员的名字,会在录制中去帮助路边的市民,会隐藏自己努力突出其他人,亲民,谦逊,幽默,温暖(此处省略10万字)。跑步很快,所以有个外号叫刘鲁斯·威利斯。如果硬要说一个缺点的话,那就是。。。。。恩,摘了眼睛颜值骤降。<br> <img src="https://pic.yupoo.com/jiananshi/7957bb61/8f1eef04.png" alt="萌萌哒"></p>
</li>
<li><p><strong>池石镇</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/b2ded4aa/94416e26.jpg" alt="池石镇"><br> 外号:王鼻子、黑斑羚、Race Starter</p>
<p> 王👃大叔,RM 中年龄最大的,擅长身体搞笑。是 RM 中的最弱体, 监狱三人组之一。几乎每次撕名牌都是最先去监狱的,所以也被称为“Race Starter”(比赛开始)。其实鼻子大叔对 RM 的贡献是很大的,在前期所有人的定位还不明确的时候,RM 的笑点都是靠大神和鼻子大叔扛起来的,鼻子叔是那种身体素质一般,但口才超棒的人,很会制造笑料。</p>
</li>
<li><p><strong>宋智孝</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/54e46ffb/63699140.jpg" alt="宋智孝"><br> 外号:懵智、不良智孝、Ace、金智孝、周一情侣</p>
<p> RM 中唯一的女 MC。第一眼感觉就是漂亮,很耐看。但深入了解下去,才发现漂亮在智孝身上已经算不上什么闪光点了。第一次见到刚睡醒顶着一头乱乱的头发就去录影的女演员;第一次见到在车上张着嘴睡的女演员;能摔跤,能劈砖,能下泥潭,能蹦极,完全不输给男生;气急了还会爆两句粗口,瞬间变身不良智孝;很聪明,是 RM 中单独获胜次数最多的;这样的懵智怎么可能不喜欢😍<br> <img src="https://pic.yupoo.com/jiananshi/ca73daa1/f28b5c45.png" alt="宋智孝"></p>
</li>
<li><p><strong>金钟国</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/1edb8f53/9596e577.jpeg" alt="金钟国"><br> 外号:能力者、老虎、钟淑、斯巴达国</p>
<p> 很具有反转魅力的一个人。唱歌时嗓音很细腻温柔,撕名牌时却以一抵十;看身材很剽悍,实际却很爱卖萌。是 RM 中的能力者,撕遍天下无敌手,不过也因为太强,经常在撕名牌时被围剿😭。要说能力者有什么怕的,应该就是“背叛长颈鹿”(李光洙)了,就是本能坑害老虎。<br> <img src="//yemengying.com/qiniu/image/tumblr_mitpqwkvFE1qgnyjgo6_r1_250.gif" alt="︿( ̄︶ ̄)︿"><br> 咳咳,只是为了节目效果,国儿和光洙还是很有爱的🙃。<br> <img src="//yemengying.com/qiniu/image/tumblr_mitpqwkvFE1qgnyjgo2_r2_250.gif" alt="相亲相爱"></p>
</li>
<li><p><strong>姜gary</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/aee80059/8d6c7f46.jpg" alt="姜gary"><br> 外号:狗哥、平和Gary、偶尔能力者、鱿鱼、周一情侣</p>
<p> 本职工作是音乐人,但却因为综艺火了起来。在节目中,总是不经意的爆发一下,所以被称为偶尔能力者,前两期的最强者特辑, gary 都是最后的获胜者。gary 的诸多定位中,最火的应该就是和智孝的周一情侣了。虽然在2016年的节目中,制作组特意淡化了周一cp间的互动,但我一直觉得 gary 就是智孝的“姜盖里”,会偷偷放走她的“姜盖里”。多亏了周一情侣,让我在看综艺的同时,也看了部偶像剧🌚<br> <img src="https://pic.yupoo.com/jiananshi/a335d17a/c569430f.jpg" alt="周一情侣"></p>
</li>
<li><p><strong>哈哈</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/595589e2/3852699b.jpg" alt="哈哈"><br> 外号:Haroro、企鹅、花心哈哈</p>
<p> 哈哈,本名河东勋,不过还是更习惯哈哈这个名字。早期在 RM 的定位是“花心哈哈”,每次一来女嘉宾,就会说“成为我最后的爱吧 (。♥‿♥。)”。不过这些只是为了活跃气氛,结婚之后“花心哈哈”的定位就没有,变成了“顾家好男人”,最近听说哈哈马上要有第二个孩子了,在渡汉江那期说的愿望也要实现了。<br> <img src="https://pic.yupoo.com/jiananshi/fbecf329/cb2884c4.gif" alt="☆´∀`☆"></p>
</li>
<li><p><strong>李光洙</strong>:<br> <img src="https://pic.yupoo.com/jiananshi/8f831e6e/5343a05d.jpg" alt="李光洙"><br> 外号:长颈鹿、亚洲王子、李光子、光凡达、光蟾蜍、情景剧发烧友</p>
<p> 绝对的男神,我博客的标准结尾。其实在一开始,光洙可以说是最不起眼的,作为一个综艺新手,没有大神,国儿,haha从以前节目积累下的观众缘,没有王鼻子老练,没有 gary 有那么多的歌迷,也没有宋仲基那么明显的颜值优势。但在自己的努力和其他人的帮助下,一点点找到了自己的定位(陷害光洙,easy 兄弟,长颈鹿)。在节目中,光洙最明显的特质应该就是“背叛”了,每次都让人觉得好气啊,然后就又笑的肚子疼。。。。不知道为什么七个 MC 中,最最喜欢光洙,连带着把很多网站的用户名都换成了 giraffe,可能天生就对这种又努力又逗比的人没有抵抗力吧。<br> 放一下女装造型<br> <img src="https://pic.yupoo.com/jiananshi/ea305e38/3e5739f2.jpeg" alt="李光子"><br> 不过一开始的胡子造型真是亮瞎了我的卡姿兰大眼睛。<br> <img src="https://pic.yupoo.com/jiananshi/18ff87f0/ad47d124.jpg" alt="😱"><br> 哈哈哈哈,原谅我,爱到深处自然黑啊。</p>
</li>
</ul>
<h3 id="墙裂推荐"><a href="#墙裂推荐" class="headerlink" title="墙裂推荐"></a>墙裂推荐</h3><p>本来想按照好看程度排个最好看的十期,最后发现实在选不出来。每一期都敲好看,都值得看。就随便推几期吧。</p>
<ul>
<li><a href="http://www.tudou.com/albumplay/KwvzbxLDWls/C7eeyc6dRVI.html" target="_blank" rel="external">20130825 </a><br> 按照电影雪国列车为主题拍摄的一期,玩游戏整理排名,国儿和大神互打手掌那段简直笑屎我了。</li>
<li><a href="http://www.tudou.com/albumplay/KwvzbxLDWls/_kGbJeh5ayM.html" target="_blank" rel="external">20130526</a><br> 嘉宾是金秀贤和李玹雨,全集高能,我大神叼着接力棒吃洋葱圈简直萌翻我。</li>
<li><a href="http://www.tudou.com/albumplay/v9hwf_w5zyk/kDnxaOAcKsY.html" target="_blank" rel="external">20141123</a><br> 这集造型极其哇撒,放张图,大家憋说话,用心感受<br> <img src="https://pic.yupoo.com/jiananshi/ad30cb4d/55d3dc2c.png" alt="( ̄▽ ̄)~*"></li>
<li><a href="http://www.tudou.com/albumplay/v9hwf_w5zyk/RzktO2d9L1o.html" target="_blank" rel="external">20141012</a><br> 七个成员都变成了超级英雄,高空测试胆量那段笑的我肚子疼。</li>
<li><a href="http://www.tudou.com/albumplay/v9hwf_w5zyk/AKR3BwkXzV8.html" target="_blank" rel="external">20141109</a><br> 女装特辑,最爱wuli光子和钟淑了。</li>
<li><a href="http://www.tudou.com/albumplay/KwvzbxLDWls/JTRj3UG6Nsg.html" target="_blank" rel="external">20130915</a><br> 很多人应该冲着嘉宾是Bigbang看过这期了,但我推荐这期是因为这期有我周一情侣的荧屏初吻,我的少女心啊😍</li>
<li><a href="http://www.iqiyi.com/v_19rrn6uwpg.html" target="_blank" rel="external">20141214</a><br> 通过一张图就能知道大神为了赢有多拼。<br> <img src="//yemengying.com/qiniu/image/u=288559190,3305279898&amp;fm=21&amp;gp=0.jpg?imageMogr2/thumbnail/300x200!" alt="&lt;( ̄︶ ̄)&gt;"></li>
<li><a href="http://v.youku.com/v_show/id_XMTUyMjI3MTEwNA==.html?from=y1.6-85.3.1.7712090c9d5a11e5a080" target="_blank" rel="external">20160403</a><br> 2016年里的觉得最好看的一期。特别是wuli光洙被haha愚弄的那段。</li>
<li><a href="http://www.iqiyi.com/w_19rr63053t.html" target="_blank" rel="external">20111225</a><br> 第一期最强者特辑。RM 每年都会有一期是最强者特辑,遗憾的是国儿还从来没有赢过,希望下一期最强者战可以是国儿赢。</li>
<li><a href="http://www.iqiyi.com/zongyi/20130130/379d4aa0b603dec5.html" target="_blank" rel="external">20120624</a><br> RM 第一百期特辑,诸神之战, 嘉宾是金喜善, 结局蛮不错的</li>
<li><a href="http://www.tudou.com/albumplay/O8GDpd7v8RA/v3YuwBHAY4M.html" target="_blank" rel="external">20150125</a><br> 刘姆斯邦德 vs 光佛岩,结尾狗哥真是蠢萌的让人心疼</li>
<li><a href="http://www.iqiyi.com/w_19rt38lku1.html" target="_blank" rel="external">20110911</a><br> 狗哥做间谍🕵的一期,不应该说是狗哥以为他做间谍,但是其他成员其实都知道他是间谍,他并不知道其他成员知道他是间谍的一期。。。艾玛啊,说的好绕🙃</li>
</ul>
<h3 id="又到睡觉觉的时候了"><a href="#又到睡觉觉的时候了" class="headerlink" title="又到睡觉觉的时候了"></a>又到睡觉觉的时候了</h3><p>写的差不多了,这么点内容根本表达不出 RM 带给我的快乐。生活大爆炸也开始更新第十季了,很有可能是最后一季,说不定过两天又要写篇文章纪念下贱萌贱萌的 Sheldon, 总被压迫的 Leonard 和女汉纸 Penny 了。</p>
<p><img src="https://pic.yupoo.com/jiananshi/69d7fb06/f3dc92a7.jpg" alt="7012"></p>
</content>
<summary type="html">
<blockquote>
<p>前两天刷微博时突然看到 Gary 即将从 《Running Man》(以下简称 RM) 下车的消息,有点伤感,却并不惊讶。可能从2016年开始,就隐约感到节目到了瓶颈,收视率也一直上不来,作为一个铁杆粉丝也不得不承认 RM 没有以前好看了,很长时间没有一期看好几遍的情况了。不过追了 RM 四年,看 RM 早已不是为了娱乐搞笑,而变成了自己生活的一部分,只要看到他们七个就开心。也许在不久的将来连 RM 都会停播了,但现在只要 RM 更新一期,就会看一期,哪怕就是他们几个嗑瓜子闲聊天。即便有再火再好看的节目,自己也早没了当初的心境去追了。改编我前女神的一句歌词就是“有的综艺说不清哪里好,但就是谁都替代不了”。</p>
</blockquote>
</summary>
<category term="随笔" scheme="http://yemengying.com/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="http://yemengying.com/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>滚蛋吧~ XML 君</title>
<link href="http://yemengying.com/2016/10/15/javaConfig-vs-XML/"/>
<id>http://yemengying.com/2016/10/15/javaConfig-vs-XML/</id>
<published>2016-10-15T07:20:20.000Z</published>
<updated>2018-12-13T07:33:20.000Z</updated>
<content type="html"><blockquote>
<p>Long long time ago, 在《Spring In Action》一书中看到这么一句话<strong> JavaConfig is the preferred option for explicit configuration because it’s more powerful, type-safe, and refactor-friendly </strong>。不过当时并不知道 <code>JavaConfig</code> 是神马东东就选择性忽略了。最近新的项目采用了 Spring Boot + JavaConfig ,接触了一个星期的 JavaConfig,感觉还不错,简单比较下它和 XML。由于接连看了 4 部韩国灾难片心情比较蓝瘦,就暂时不要指正错误了,错就错吧(づ。◕‿‿◕。)づ</p>
</blockquote>
<a id="more"></a>
<p><img src="https://pic.yupoo.com/jiananshi/38ad3eff/2a9a8d29.jpg" alt="lalala"></p>
<h3 id="XML"><a href="#XML" class="headerlink" title="XML"></a>XML</h3><p>虽然早在 Spring 3 中就开始支持以 <code>JavaConfig</code> 的方式配置项目,但感觉目前主流的还是以 <code>XML + Annotation</code> 的方式,很少见到使用 JavaConfig 的。可能有点以偏概全了,但在前司和前前司接触到的项目都是以 XML + Annotation 混合的方式配置的,即在 XML 配置文件中开启注解扫描, 业务 bean 的配置注入采用注解( @Serivce, @Autowire 等等),全局的一些配置(如 MyBatis 的 DataSource,SqlSessionFactory ,web.xml 等等)使用 XML。</p>
<p>虽然 XML + Annotation 的方式比纯用 XML 配置的方式少写了很多 XML,但本质上还是基于 XML 的。</p>
<p>XML 的配置文件比较冗长,不易书写,而且可读性也不高。不知道大家感觉怎么样,反正我看着是挺头疼的╥﹏╥…</p>
<h3 id="JavaConfig"><a href="#JavaConfig" class="headerlink" title="JavaConfig"></a>JavaConfig</h3><p>先简单介绍一下 JavaConfig,JavaConfig即Java Configuration, 即用纯 Java 的方式来配置 Spring<br> IoC 容器,允许开发者用代码来表示配置逻辑,不再需要 XML。粗略翻了两本关于 Spring Boot的书, JavaConfig 都是最推荐的配置方式。</p>
<p>使用 JavaConfig 的好处,Spring 官方文档中说的很清楚:</p>
<ol>
<li>JavaConfig 为依赖注入提供了一个真正面向对象的机制,这意味着开发者可以在配置代码中充分利用 Java 重用、继承和多态性等特性。</li>
<li>开发者可以完全控制实例化和依赖注入,即使是最复杂的对象也可以很优雅地处理。</li>
<li>因为使用 JavaConfig 只需要 Java,可以很容易的 refactor 代码,而无需再 IDE 之外使用特殊的工具或插件。</li>
</ol>
<p>JavaConfig 其实很简单,主要是通过 @Configuration 和 @Bean 来进行配置。@Configuration 注解的作用是声明当前类是一个配置类, 就相当于一个 XML 文件。 @Bean 注解声明当前方法返回的是一个 bean。</p>
<p>可能这样说对于用惯了 XML 的人还是比较抽象的,下面看看这些注解与 XML 配置的对应关系。</p>
<h3 id="JavaConfig-与-XML"><a href="#JavaConfig-与-XML" class="headerlink" title="JavaConfig 与 XML"></a>JavaConfig 与 XML</h3><p>1.<br><strong>JavaConfig</strong>: @Configuration<br><strong>XML</strong>:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></div><div class="line"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></div><div class="line"> <span class="attr">xmlns:tx</span>=<span class="string">"http://www.springframework.org/schema/tx"</span></div><div class="line"> <span class="attr">xmlns:util</span>=<span class="string">"http://www.springframework.org/schema/util"</span></div><div class="line"> <span class="attr">xmlns:p</span>=<span class="string">"http://www.springframework.org/schema/p"</span></div><div class="line"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans</span></div><div class="line"> http://www.springframework.org/schema/beans/spring-beans.xsd</div><div class="line"> http://www.springframework.org/schema/tx</div><div class="line"> http://www.springframework.org/schema/tx/spring-tx.xsd</div><div class="line"> http://www.springframework.org/schema/util</div><div class="line"> http://www.springframework.org/schema/util/spring-util.xsd"&gt; </div><div class="line"> </div><div class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></div></pre></td></tr></table></figure>
<p>2.<br><strong>JavaConfig</strong>: @Bean<br><strong>XML</strong>:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<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>&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"jdbc:mysql://127.0.0.1:3307/giraffe"</span>/&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"ymy"</span>/&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"666666"</span>/&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></div></pre></td></tr></table></figure>
<p>3.<br><strong>JavaConfig</strong>:@ComponentScan<br><strong>XML</strong>:<br> <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">context:component-scan</span>&gt;</span></div></pre></td></tr></table></figure></p>
<p>4.<br><strong>JavaConfig</strong>:@Import<br><strong>XML</strong>:<br> <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">import</span> <span class="attr">resource</span>=<span class="string">"XXX.xml"</span>/&gt;</span></div></pre></td></tr></table></figure></p>
<p>比如要配置一个 dataSource, 在 XML 中通常的做法是这样的:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"drMainDataSource"</span> <span class="attr">class</span>=<span class="string">"com.alibaba.druid.pool.DruidDataSource"</span>&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"$&#123;mysql.datasource.url&#125;"</span>/&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"$&#123;mysql.datasource.username&#125;"</span>/&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"$&#123;mysql.datasource.password&#125;"</span>/&gt;</span></div><div class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxActive"</span> <span class="attr">value</span>=<span class="string">"&#123;mysql.datasource.max-active&#125;"</span>/&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></div></pre></td></tr></table></figure>
<p>对应的 JavaConfig 是酱紫的:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DataAccessConfig</span> <span class="keyword">extends</span> <span class="title">ConfigurationSupport</span> </span>&#123;</div><div class="line"> <span class="meta">@Bean</span>(name = <span class="string">"dataSource"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">mysqlDataSource</span><span class="params">(@Value(<span class="string">"$&#123;mysql.datasource.url&#125;"</span>)</span> String url,</span></div><div class="line"> @<span class="title">Value</span><span class="params">(<span class="string">"$&#123;mysql.datasource.username&#125;"</span>)</span> String username,</div><div class="line"> @<span class="title">Value</span><span class="params">(<span class="string">"$&#123;mysql.datasource.password&#125;"</span>)</span> String password,</div><div class="line"> @<span class="title">Value</span><span class="params">(<span class="string">"$&#123;mysql.datasource.driverClass&#125;"</span>)</span> String driver,</div><div class="line"> @<span class="title">Value</span><span class="params">(<span class="string">"$&#123;mysql.datasource.max-active&#125;"</span>)</span> <span class="keyword">int</span> maxActive) &#123;</div><div class="line"></div><div class="line"> DruidAbstractDataSource dataSource = <span class="keyword">new</span> DruidDataSource();</div><div class="line"> dataSource.setDriverClassName(driver);</div><div class="line"> dataSource.setUrl(url);</div><div class="line"> dataSource.setUsername(username);</div><div class="line"> dataSource.setPassword(password);</div><div class="line"> dataSource.setMaxActive(maxActive);</div><div class="line"> <span class="keyword">return</span> dataSource;</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>虽然才接触了一周的 JavaConfig, 但是相见恨晚啊, 个人还是更喜欢 JavaConfig 的配置方式的。JavaConfig 的配置文件可读性更高也更容易学习,记住简单的几个注解即可;借助 IDE 的力量,更不容易出错;而且脑袋再也不用在 Java 和 XML 间来回切换了,在搭配上 Gradle 简直破费科特!!!!!</p>
<p><img src="https://pic.yupoo.com/jiananshi/5417f84e/5cd518b1.png" alt="开森"></p>
<h3 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h3><ul>
<li><a href="http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/pr01.html" target="_blank" rel="external">Spring JavaConfig Document</a></li>
<li>《Spring Boot 揭秘》</li>
</ul>
<p>——————————————-片 尾 彩 蛋 🎉🎉🎉—————————————————<br>咳咳,预警预警!!!下面的部分与本文主旨无关。</p>
<p>首先,换了个新锅,可以预约煮粥,再也不用早起煮粥了,幸福感提升 200%。<br><img src="https://pic.yupoo.com/jiananshi/e5c5a4fc/83a101e1.jpg" alt="我是锅"><br>其次,在匿名人士的帮助下上线了新版未翻墙模式下的评论,优化了样式。唉,这是谁的男朋友这么有才华,好羡慕她哦︿( ̄︶ ̄)︿<br><img src="https://pic.yupoo.com/jiananshi/9eeb1071/d6b98de8.png" alt="&lt;( ̄︶ ̄)&gt;"></p>
<p>最后,嘿嘿,关注个公众号再走吧(<em>^__^</em>)<br><img src="https://pic.yupoo.com/jiananshi/3302bb60/7b8457af.jpg" alt="Running Geek"></p>
</content>
<summary type="html">
<blockquote>
<p>Long long time ago, 在《Spring In Action》一书中看到这么一句话<strong> JavaConfig is the preferred option for explicit configuration because it’s more powerful, type-safe, and refactor-friendly </strong>。不过当时并不知道 <code>JavaConfig</code> 是神马东东就选择性忽略了。最近新的项目采用了 Spring Boot + JavaConfig ,接触了一个星期的 JavaConfig,感觉还不错,简单比较下它和 XML。由于接连看了 4 部韩国灾难片心情比较蓝瘦,就暂时不要指正错误了,错就错吧(づ。◕‿‿◕。)づ</p>
</blockquote>