-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
2946 lines (2689 loc) · 766 KB
/
search.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"?>
<search>
<entry>
<title>Docker Swarm 入门</title>
<url>/posts/18246ef3/</url>
<content><![CDATA[<blockquote>
<p>Swarm 在 Docker 1.12 版本之前属于一个独立的项目,在 Docker 1.12 版本发布之后,该项目合并到了 Docker 中,成为 Docker 的一个子命令。目前,Swarm 是 Docker 社区提供的唯一一个原生支持 Docker 集群管理的工具。它可以把多个 Docker 主机组成的系统转换为单一的虚拟 Docker 主机,使得容器可以组成跨主机的子网网络。</p>
</blockquote>
<h2 id="Swarm-认识"><a href="#Swarm-认识" class="headerlink" title="Swarm 认识"></a>Swarm 认识</h2><p>Swarm 是目前 Docker 官方唯一指定(绑定)的集群管理工具。Docker 1.12 内嵌了 swarm mode 集群管理模式。</p>
<p>为了方便演示跨主机网络,我们需要用到一个工具——Docker Machine,这个工具与 Docker Compose、Docker Swarm 并称 Docker 三剑客,下面我们来看看如何安装 Docker Machine:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ curl -L https://github.com/docker/machine/releases/download/v0.9.0-rc2/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine &&</span><br><span class="line"> chmod +x /tmp/docker-machine &&</span><br><span class="line"> sudo cp /tmp/docker-machine /usr/<span class="built_in">local</span>/bin/docker-machine</span><br></pre></td></tr></table></figure>
<p>安装过程和 Docker Compose 非常类似。现在 Docker 三剑客已经全部到齐了。<br>在开始之前,我们需要了解一些基本概念,有关集群的 Docker 命令如下:</p>
<ul>
<li>docker swarm:集群管理,子命令有 init, join,join-token, leave, update</li>
<li>docker node:节点管理,子命令有 demote, inspect,ls, promote, rm, ps, update</li>
<li>docker service:服务管理,子命令有 create, inspect, ps, ls ,rm , scale, update</li>
<li>docker stack/deploy:试验特性,用于多应用部署,等正式版加进来再说。</li>
</ul>
<h2 id="创建集群"><a href="#创建集群" class="headerlink" title="创建集群"></a>创建集群</h2><p>首先使用 Docker Machine 创建一个虚拟机作为 manger 节点。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine create --driver virtualbox manager1</span><br><span class="line">Running pre-create checks...</span><br><span class="line">(manager1) Unable to get the latest Boot2Docker ISO release version: Get https://api.github.com/repos/boot2docker/boot2docker/releases/latest: dial tcp: lookup api.github.com on [::1]:53: server misbehaving</span><br><span class="line">Creating machine...</span><br><span class="line">(manager1) Unable to get the latest Boot2Docker ISO release version: Get https://api.github.com/repos/boot2docker/boot2docker/releases/latest: dial tcp: lookup api.github.com on [::1]:53: server misbehaving</span><br><span class="line">(manager1) Copying /home/zuolan/.docker/machine/cache/boot2docker.iso to /home/zuolan/.docker/machine/machines/manager1/boot2docker.iso...</span><br><span class="line">(manager1) Creating VirtualBox VM...</span><br><span class="line">(manager1) Creating SSH key...</span><br><span class="line">(manager1) Starting the VM...</span><br><span class="line">(manager1) Check network to re-create <span class="keyword">if</span> needed...</span><br><span class="line">(manager1) Found a new host-only adapter: <span class="string">"vboxnet0"</span></span><br><span class="line">(manager1) Waiting <span class="keyword">for</span> an IP...</span><br><span class="line">Waiting <span class="keyword">for</span> machine to be running, this may take a few minutes...</span><br><span class="line">Detecting operating system of created instance...</span><br><span class="line">Waiting <span class="keyword">for</span> SSH to be available...</span><br><span class="line">Detecting the provisioner...</span><br><span class="line">Provisioning with boot2docker...</span><br><span class="line">Copying certs to the <span class="built_in">local</span> machine directory...</span><br><span class="line">Copying certs to the remote machine...</span><br><span class="line">Setting Docker configuration on the remote daemon...</span><br><span class="line">Checking connection to Docker...</span><br><span class="line">Docker is up and running!</span><br><span class="line">To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env manager1</span><br></pre></td></tr></table></figure>
<p>查看虚拟机的环境变量等信息,包括虚拟机的 IP 地址:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine env manager1</span><br><span class="line"><span class="built_in">export</span> DOCKER_TLS_VERIFY=<span class="string">"1"</span></span><br><span class="line"><span class="built_in">export</span> DOCKER_HOST=<span class="string">"tcp://192.168.99.100:2376"</span></span><br><span class="line"><span class="built_in">export</span> DOCKER_CERT_PATH=<span class="string">"/home/zuolan/.docker/machine/machines/manager1"</span></span><br><span class="line"><span class="built_in">export</span> DOCKER_MACHINE_NAME=<span class="string">"manager1"</span></span><br><span class="line"><span class="comment"># Run this command to configure your shell: </span></span><br><span class="line"><span class="comment"># eval $(docker-machine env manager1)</span></span><br></pre></td></tr></table></figure>
<p>然后再创建一个节点作为 work 节点。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine create --driver virtualbox worker1</span><br></pre></td></tr></table></figure>
<p>现在我们有了两个虚拟主机,使用 Machine 的命令可以查看:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ls</span><br><span class="line">NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS</span><br><span class="line">manager1 - virtualbox Running tcp://192.168.99.100:2376 v1.12.3</span><br><span class="line">worker1 - virtualbox Running tcp://192.168.99.101:2376 v1.12.3</span><br></pre></td></tr></table></figure>
<p>但是目前这两台虚拟主机并没有什么联系,为了把它们联系起来,我们需要 Swarm 登场了。<br>因为我们使用的是 Docker Machine 创建的虚拟机,因此可以使用 docker-machine ssh 命令来操作虚拟机,在实际生产环境中,并不需要像下面那样操作,只需要执行 docker swarm 即可。</p>
<p>把 manager1 加入集群:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker swarm init --listen-addr 192.168.99.100:2377 --advertise-addr 192.168.99.100</span><br><span class="line">Swarm initialized: current node (23lkbq7uovqsg550qfzup59t6) is now a manager.</span><br><span class="line"></span><br><span class="line">To add a worker to this swarm, run the following <span class="built_in">command</span>:</span><br><span class="line"></span><br><span class="line"> docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-c036gwrakjejql06klrfc585r \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line"></span><br><span class="line">To add a manager to this swarm, run <span class="string">'docker swarm join-token manager'</span> and follow the instructions.</span><br></pre></td></tr></table></figure>
<p>用 –listen-addr 指定监听的 ip 与端口,实际的 Swarm 命令格式如下,本例使用 Docker Machine 来连接虚拟机而已:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker swarm init --listen-addr <MANAGER-IP>:<PORT></span><br></pre></td></tr></table></figure>
<p>接下来,再把 work1 加入集群中:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker1 docker swarm join --token \</span><br><span class="line"> SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-c036gwrakjejql06klrfc585r \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line">This node joined a swarm as a worker.</span><br></pre></td></tr></table></figure>
<p>上面 join 命令中可以添加 –listen-addr $WORKER1_IP:2377 作为监听准备,因为有时候可能会遇到把一个 work 节点提升为 manger 节点的可能,当然本例子没有这个打算就不添加这个参数了。</p>
<blockquote>
<p>注意:如果你在新建集群时遇到双网卡情况,可以指定使用哪个 IP,例如上面的例子会有可能遇到下面的错误。</p>
</blockquote>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker swarm init --listen-addr <span class="variable">$MANAGER1_IP</span>:2377</span><br><span class="line">Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (10.0.2.15 on eth0 and 192.168.99.100 on eth1) - specify one with --advertise-addr</span><br><span class="line"><span class="built_in">exit</span> status 1</span><br></pre></td></tr></table></figure>
<p>发生错误的原因是因为有两个 IP 地址,而 Swarm 不知道用户想使用哪个,因此要指定 IP。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker swarm init --advertise-addr 192.168.99.100 --listen-addr 192.168.99.100:2377 </span><br><span class="line">Swarm initialized: current node (ahvwxicunjd0z8g0eeosjztjx) is now a manager.</span><br><span class="line"></span><br><span class="line">To add a worker to this swarm, run the following <span class="built_in">command</span>:</span><br><span class="line"></span><br><span class="line"> docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-c036gwrakjejql06klrfc585r \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line"></span><br><span class="line">To add a manager to this swarm, run <span class="string">'docker swarm join-token manager'</span> and follow the instructions.</span><br></pre></td></tr></table></figure>
<p>集群初始化成功。</p>
<p>现在我们新建了一个有两个节点的“集群”,现在进入其中一个管理节点使用 docker node 命令来查看节点信息:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker node ls</span><br><span class="line">ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS</span><br><span class="line">23lkbq7uovqsg550qfzup59t6 * manager1 Ready Active Leader</span><br><span class="line">dqb3fim8zvcob8sycri3hy98a worker1 Ready Active</span><br></pre></td></tr></table></figure>
<p>现在每个节点都归属于 Swarm,并都处在了待机状态。Manager1 是领导者,work1 是工人。</p>
<p>现在,我们继续新建虚拟机 manger2、worker2、worker3,现在已经有五个虚拟机了,使用 docker-machine ls 来查看虚拟机:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS</span><br><span class="line">manager1 - virtualbox Running tcp://192.168.99.100:2376 v1.12.3 </span><br><span class="line">manager2 - virtualbox Running tcp://192.168.99.105:2376 v1.12.3 </span><br><span class="line">worker1 - virtualbox Running tcp://192.168.99.102:2376 v1.12.3 </span><br><span class="line">worker2 - virtualbox Running tcp://192.168.99.103:2376 v1.12.3 </span><br><span class="line">worker3 - virtualbox Running tcp://192.168.99.104:2376 v1.12.3</span><br></pre></td></tr></table></figure>
<p>然后我们把剩余的虚拟机也加到集群中。</p>
<p>添加 worker2 到集群中:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker2 docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-c036gwrakjejql06klrfc585r \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line">This node joined a swarm as a worker.</span><br></pre></td></tr></table></figure>
<p>添加 worker3 到集群中:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker3 docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-c036gwrakjejql06klrfc585r \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line">This node joined a swarm as a worker.</span><br></pre></td></tr></table></figure>
<p>添加 manager2 到集群中:<br>先从 manager1 中获取 manager 的 token:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker swarm join-token manager</span><br><span class="line">To add a manager to this swarm, run the following <span class="built_in">command</span>:</span><br><span class="line"></span><br><span class="line"> docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-8tn855hkjdb6usrblo9iu700o \</span><br><span class="line">192.168.99.100:2377</span><br></pre></td></tr></table></figure>
<p>然后添加 manager2 到集群中:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager2 docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-8tn855hkjdb6usrblo9iu700o \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line">This node joined a swarm as a manager.</span><br></pre></td></tr></table></figure>
<p>现在再来查看集群信息:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager2 docker node ls</span><br><span class="line">ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS</span><br><span class="line">16w80jnqy2k30yez4wbbaz1l8 worker1 Ready Active</span><br><span class="line">2gkwhzakejj72n5xoxruet71z worker2 Ready Active</span><br><span class="line">35kutfyn1ratch55fn7j3fs4x worker3 Ready Active</span><br><span class="line">a9r21g5iq1u6h31myprfwl8ln * manager2 Ready Active Reachable</span><br><span class="line">dpo7snxbz2a0dxvx6mf19p35z manager1 Ready Active Leader</span><br></pre></td></tr></table></figure>
<h2 id="建立跨主机网络"><a href="#建立跨主机网络" class="headerlink" title="建立跨主机网络"></a>建立跨主机网络</h2><p>为了演示更清晰,下面我们把宿主机也加入到集群之中,这样我们使用 Docker 命令操作会清晰很多。<br>直接在本地执行加入集群命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker swarm join \</span><br><span class="line"> --token SWMTKN-1-3z5rzoey0u6onkvvm58f7vgkser5d7z8sfshlu7s4oz2gztlvj-8tn855hkjdb6usrblo9iu700o \</span><br><span class="line"> 192.168.99.100:2377</span><br><span class="line">This node joined a swarm as a manager.</span><br></pre></td></tr></table></figure>
<p>现在我们有三台 manager,三台 worker。其中一台是宿主机,五台虚拟机。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker node ls</span><br><span class="line">ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS</span><br><span class="line">6z2rpk1t4xucffzlr2rpqb8u3 worker3 Ready Active</span><br><span class="line">7qbr0xd747qena4awx8bx101s * user-pc Ready Active Reachable</span><br><span class="line">9v93sav79jqrg0c7051rcxxev manager2 Ready Active Reachable</span><br><span class="line">a1ner3zxj3ubsiw4l3p28wrkj worker1 Ready Active</span><br><span class="line">a5w7h8j83i11qqi4vlu948mad worker2 Ready Active</span><br><span class="line">d4h7vuekklpd6189fcudpfy18 manager1 Ready Active Leader</span><br></pre></td></tr></table></figure>
<p>查看网络状态:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker network ls</span><br><span class="line">NETWORK ID NAME DRIVER SCOPE</span><br><span class="line">764ff31881e5 bridge bridge <span class="built_in">local</span></span><br><span class="line">fbd9a977aa03 host host <span class="built_in">local</span></span><br><span class="line">6p6xlousvsy2 ingress overlay swarm</span><br><span class="line">e81af24d643d none null <span class="built_in">local</span></span><br></pre></td></tr></table></figure>
<p>可以看到在 swarm 上默认已有一个名为 ingress 的 overlay 网络, 默认在 swarm 里使用,本例子中会创建一个新的 overlay 网络。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker network create --driver overlay swarm_test</span><br><span class="line">4dm8cy9y5delvs5vd0ghdd89s</span><br><span class="line">$ docker network ls</span><br><span class="line">NETWORK ID NAME DRIVER SCOPE</span><br><span class="line">764ff31881e5 bridge bridge <span class="built_in">local</span></span><br><span class="line">fbd9a977aa03 host host <span class="built_in">local</span></span><br><span class="line">6p6xlousvsy2 ingress overlay swarm</span><br><span class="line">e81af24d643d none null <span class="built_in">local</span></span><br><span class="line">4dm8cy9y5del swarm_test overlay swarm</span><br></pre></td></tr></table></figure>
<p>这样一个跨主机网络就搭建好了,但是现在这个网络只是处于待机状态,下一小节我们会在这个网络上部署应用。</p>
<h2 id="在跨主机网络上部署应用"><a href="#在跨主机网络上部署应用" class="headerlink" title="在跨主机网络上部署应用"></a>在跨主机网络上部署应用</h2><p>首先我们上面创建的节点都是没有镜像的,因此我们要逐一 pull 镜像到节点中,这里我们使用前面搭建的私有仓库。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker pull reg.example.com/library/nginx:alpine</span><br><span class="line">alpine: Pulling from library/nginx</span><br><span class="line">e110a4a17941: Pulling fs layer</span><br><span class="line">... ...</span><br><span class="line">7648f5d87006: Pull complete</span><br><span class="line">Digest: sha256:65063cb82bf508fd5a731318e795b2abbfb0c22222f02ff5c6b30df7f23292fe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> reg.example.com/library/nginx:alpine</span><br><span class="line">$ docker-machine ssh manager2 docker pull reg.example.com/library/nginx:alpine</span><br><span class="line">alpine: Pulling from library/nginx</span><br><span class="line">e110a4a17941: Pulling fs layer</span><br><span class="line">... ...</span><br><span class="line">7648f5d87006: Pull complete</span><br><span class="line">Digest: sha256:65063cb82bf508fd5a731318e795b2abbfb0c22222f02ff5c6b30df7f23292fe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> reg.example.com/library/nginx:alpine</span><br><span class="line">$ docker-machine ssh worker1 docker pull reg.example.com/library/nginx:alpine</span><br><span class="line">alpine: Pulling from library/nginx</span><br><span class="line">e110a4a17941: Pulling fs layer</span><br><span class="line">... ...</span><br><span class="line">7648f5d87006: Pull complete</span><br><span class="line">Digest: sha256:65063cb82bf508fd5a731318e795b2abbfb0c22222f02ff5c6b30df7f23292fe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> reg.example.com/library/nginx:alpine</span><br><span class="line">$ docker-machine ssh worker2 docker pull reg.example.com/library/nginx:alpine</span><br><span class="line">alpine: Pulling from library/nginx</span><br><span class="line">e110a4a17941: Pulling fs layer</span><br><span class="line">... ...</span><br><span class="line">7648f5d87006: Pull complete</span><br><span class="line">Digest: sha256:65063cb82bf508fd5a731318e795b2abbfb0c22222f02ff5c6b30df7f23292fe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> reg.example.com/library/nginx:alpine</span><br><span class="line">$ docker-machine ssh worker3 docker pull reg.example.com/library/nginx:alpine</span><br><span class="line">alpine: Pulling from library/nginx</span><br><span class="line">e110a4a17941: Pulling fs layer</span><br><span class="line">... ...</span><br><span class="line">7648f5d87006: Pull complete</span><br><span class="line">Digest: sha256:65063cb82bf508fd5a731318e795b2abbfb0c22222f02ff5c6b30df7f23292fe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> reg.example.com/library/nginx:alpine</span><br></pre></td></tr></table></figure>
<p>上面使用 docker pull 分别在五个虚拟机节点拉取 nginx:alpine 镜像。接下来我们要在五个节点部署一组 Nginx 服务。</p>
<p>部署的服务使用 swarm_test 跨主机网络。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service create --replicas 2 --name helloworld --network=swarm_test nginx:alpine</span><br><span class="line">5gz0h2s5agh2d2libvzq6bhgs</span><br></pre></td></tr></table></figure>
<p>查看服务状态:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ls</span><br><span class="line">ID NAME REPLICAS IMAGE COMMAND</span><br><span class="line">5gz0h2s5agh2 helloworld 0/2 nginx:alpine</span><br></pre></td></tr></table></figure>
<p>查看 helloworld 服务详情(为了方便阅读,已调整输出内容):</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ps helloworld</span><br><span class="line">ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR</span><br><span class="line">ay081uome3 helloworld.1 nginx:alpine manager1 Running Preparing 2 seconds ago </span><br><span class="line">16cvore0c96 helloworld.2 nginx:alpine worker2 Running Preparing 2 seconds ago</span><br></pre></td></tr></table></figure>
<p>可以看到两个实例分别运行在两个节点上。</p>
<p>进入两个节点,查看服务状态(为了方便阅读,已调整输出内容):</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker ps -a</span><br><span class="line">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span><br><span class="line">119f787622c2 nginx:alpine <span class="string">"nginx -g ..."</span> 4 minutes ago Up 4 minutes 80/tcp, 443/tcp hello ...</span><br><span class="line">$ docker-machine ssh worker2 docker ps -a</span><br><span class="line">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</span><br><span class="line">5db707401a06 nginx:alpine <span class="string">"nginx -g ..."</span> 4 minutes ago Up 4 minutes 80/tcp, 443/tcp hello ...</span><br></pre></td></tr></table></figure>
<p>上面输出做了调整,实际的 NAMES 值为:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">helloworld.1.ay081uome3eejeg4mspa8pdlx</span><br><span class="line">helloworld.2.16cvore0c96rby1vp0sny3mvt</span><br></pre></td></tr></table></figure>
<p>记住上面这两个实例的名称。现在我们来看这两个跨主机的容器是否能互通:<br>首先使用 Machine 进入 manager1 节点,然后使用 docker exec -i 命令进入 helloworld.1 容器中 ping 运行在 worker2 节点的 helloworld.2 容器。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh manager1 docker <span class="built_in">exec</span> -i helloworld.1.ay081uome3eejeg4mspa8pdlx \</span><br><span class="line"> ping helloworld.2.16cvore0c96rby1vp0sny3mvt</span><br><span class="line">PING helloworld.2.16cvore0c96rby1vp0sny3mvt (10.0.0.4): 56 data bytes</span><br><span class="line">64 bytes from 10.0.0.4: seq=0 ttl=64 time=0.591 ms</span><br><span class="line">64 bytes from 10.0.0.4: seq=1 ttl=64 time=0.594 ms</span><br><span class="line">64 bytes from 10.0.0.4: seq=2 ttl=64 time=0.624 ms</span><br><span class="line">64 bytes from 10.0.0.4: seq=3 ttl=64 time=0.612 ms</span><br><span class="line">^C</span><br></pre></td></tr></table></figure>
<p>然后使用 Machine 进入 worker2 节点,然后使用 docker exec -i 命令进入 helloworld.2 容器中 ping 运行在 manager1 节点的 helloworld.1 容器。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker2 docker <span class="built_in">exec</span> -i helloworld.2.16cvore0c96rby1vp0sny3mvt \</span><br><span class="line"> ping helloworld.1.ay081uome3eejeg4mspa8pdlx </span><br><span class="line">PING helloworld.1.ay081uome3eejeg4mspa8pdlx (10.0.0.3): 56 data bytes</span><br><span class="line">64 bytes from 10.0.0.3: seq=0 ttl=64 time=0.466 ms</span><br><span class="line">64 bytes from 10.0.0.3: seq=1 ttl=64 time=0.465 ms</span><br><span class="line">64 bytes from 10.0.0.3: seq=2 ttl=64 time=0.548 ms</span><br><span class="line">64 bytes from 10.0.0.3: seq=3 ttl=64 time=0.689 ms</span><br><span class="line">^C</span><br></pre></td></tr></table></figure>
<p>可以看到这两个跨主机的服务集群里面各个容器是可以互相连接的。</p>
<p>为了体现 Swarm 集群的优势,我们可以使用虚拟机的 ping 命令来测试对方虚拟机内的容器。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker2 ping helloworld.1.ay081uome3eejeg4mspa8pdlx</span><br><span class="line">PING helloworld.1.ay081uome3eejeg4mspa8pdlx (221.179.46.190): 56 data bytes</span><br><span class="line">64 bytes from 221.179.46.190: seq=0 ttl=63 time=48.651 ms</span><br><span class="line">64 bytes from 221.179.46.190: seq=1 ttl=63 time=63.239 ms</span><br><span class="line">64 bytes from 221.179.46.190: seq=2 ttl=63 time=47.686 ms</span><br><span class="line">64 bytes from 221.179.46.190: seq=3 ttl=63 time=61.232 ms</span><br><span class="line">^C</span><br><span class="line">$ docker-machine ssh manager1 ping helloworld.2.16cvore0c96rby1vp0sny3mvt</span><br><span class="line">PING helloworld.2.16cvore0c96rby1vp0sny3mvt (221.179.46.194): 56 data bytes</span><br><span class="line">64 bytes from 221.179.46.194: seq=0 ttl=63 time=30.150 ms</span><br><span class="line">64 bytes from 221.179.46.194: seq=1 ttl=63 time=54.455 ms</span><br><span class="line">64 bytes from 221.179.46.194: seq=2 ttl=63 time=73.862 ms</span><br><span class="line">64 bytes from 221.179.46.194: seq=3 ttl=63 time=53.171 ms</span><br><span class="line">^C</span><br></pre></td></tr></table></figure>
<p>上面我们使用了虚拟机内部的 ping 去测试容器的延迟,可以看到延迟明显比集群内部的 ping 值要高。</p>
<h2 id="Swarm-集群负载"><a href="#Swarm-集群负载" class="headerlink" title="Swarm 集群负载"></a>Swarm 集群负载</h2><p>现在我们已经学会了 Swarm 集群的部署方法,现在来搭建一个可访问的 Nginx 集群吧。体验最新版的 Swarm 所提供的自动服务发现与集群负载功能。<br>首先删掉上一节我们启动的 helloworld 服务:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service rm helloworld</span><br><span class="line">helloworld</span><br></pre></td></tr></table></figure>
<p>然后在新建一个服务,提供端口映射参数,使得外界可以访问这些 Nginx 服务:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service create --replicas 2 --name helloworld -p 7080:80 --network=swarm_test nginx:alpine</span><br><span class="line">9gfziifbii7a6zdqt56kocyun</span><br></pre></td></tr></table></figure>
<p>查看服务运行状态:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ls</span><br><span class="line">ID NAME REPLICAS IMAGE COMMAND</span><br><span class="line">9gfziifbii7a helloworld 2/2 nginx:alpine</span><br></pre></td></tr></table></figure>
<p>不知你有没有发现,虽然我们使用 –replicas 参数的值都是一样的,但是上一节中获取服务状态时,REPLICAS 返回的是 0/2,现在的 REPLICAS 返回的是 2/2。<br>同样使用 docker service ps 查看服务详细状态时(下面输出已经手动调整为更易读的格式),可以看到实例的 CURRENT STATE 中是 Running 状态的,而上一节中的 CURRENT STATE 中全部是处于 Preparing 状态。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ps helloworld</span><br><span class="line">ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR</span><br><span class="line">9ikr3agyi... helloworld.1 nginx:alpine user-pc Running Running 13 seconds ago </span><br><span class="line">7acmhj0u... helloworld.2 nginx:alpine worker2 Running Running 6 seconds ago</span><br></pre></td></tr></table></figure>
<p>这就涉及到 Swarm 内置的发现机制了,目前 Docker 1.12 中 Swarm 已经内置了服务发现工具,我们不再需要像以前使用 Etcd 或者 Consul 这些工具来配置服务发现。对于一个容器来说如果没有外部通信但又是运行中的状态会被服务发现工具认为是 Preparing 状态,本小节例子中因为映射了端口,因此有了 Running 状态。<br>现在我们来看 Swarm 另一个有趣的功能,当我们杀死其中一个节点时,会发生什么。<br>首先 kill 掉 worker2 的实例:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker-machine ssh worker2 docker <span class="built_in">kill</span> helloworld.2.7acmhj0udzusv1d7lu2tbuhu4</span><br><span class="line">helloworld.2.7acmhj0udzusv1d7lu2tbuhu4</span><br></pre></td></tr></table></figure>
<p>稍等几秒,再来看服务状态:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ps helloworld</span><br><span class="line">ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR</span><br><span class="line">9ikr3agyi... helloworld.1 nginx:alpine zuolan-pc Running Running 19 minutes ago </span><br><span class="line">8f866igpl... helloworld.2 nginx:alpine manager1 Running Running 4 seconds ago</span><br><span class="line">7acmhj0u... \_ helloworld.2 nginx:alpine worker2 Shutdown Failed 11 seconds ago ...<span class="built_in">exit</span>...</span><br><span class="line">$ docker service ls</span><br><span class="line">ID NAME REPLICAS IMAGE COMMAND</span><br><span class="line">9gfziifbii7a helloworld 2/2 nginx:alpine</span><br></pre></td></tr></table></figure>
<p>可以看到即使我们 kill 掉其中一个实例,Swarm 也会迅速把停止的容器撤下来,同时在节点中启动一个新的实例顶上来。这样服务依旧还是两个实例在运行。<br>此时如果你想添加更多实例可以使用 scale 命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service scale helloworld=3</span><br><span class="line">helloworld scaled to 3</span><br></pre></td></tr></table></figure>
<p>查看服务详情,可以看到有三个实例启动了:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service ps helloworld</span><br><span class="line">ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR</span><br><span class="line">9ikr3agyi... helloworld.1 nginx:alpine user-pc Running Running 30 minutes ago 8f866igpl... helloworld.2 nginx:alpine manager1 Running Running 11 minutes ago 7acmhj0u... \_ helloworld.2 nginx:alpine worker2 Shutdown Failed 11 minutes ago exit137</span><br><span class="line">1vexr1jm... helloworld.3 nginx:alpine worker2 Running Running 4 seconds ago</span><br></pre></td></tr></table></figure>
<p>现在如果想减少实例数量,一样可以使用 scale 命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ docker service scale helloworld=2</span><br><span class="line">helloworld scaled to 2</span><br></pre></td></tr></table></figure>
<p>至此,Swarm的主要用法都已经介绍完了,主要讲述了 Swarm 集群网络的创建与部署。介绍了 Swarm 的常规应用,包括 Swarm 的服务发现、负载均衡等,然后使用 Swarm 来配置跨主机容器网络,并在上面部署应用。</p>
<blockquote>
<p>转自: <a href="http://www.jianshu.com/p/9eb9995884a5" target="_blank" rel="noopener">http://www.jianshu.com/p/9eb9995884a5</a></p>
</blockquote>
]]></content>
<categories>
<category>容器技术</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>Docker Swarm</tag>
</tags>
</entry>
<entry>
<title>Docker可视化管理工具Shipyard安装与配置</title>
<url>/posts/2a39a568/</url>
<content><![CDATA[<blockquote>
<p><strong>文章来源: <a href="https://shipyard-project.com/docs/deploy/automated/" target="_blank" rel="noopener">Shipyard Automated Deployment</a></strong></p>
</blockquote>
<hr>
<h2 id="Shipyard简介"><a href="#Shipyard简介" class="headerlink" title="Shipyard简介"></a>Shipyard简介</h2><p>Shipyard是一个集成管理docker容器、镜像、Registries的系统,它具有以下特点:</p>
<ul>
<li>支持多节点的集成管理</li>
<li>可动态加载节点</li>
<li>可托管node下的容器</li>
</ul>
<h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><h3 id="下载镜像"><a href="#下载镜像" class="headerlink" title="下载镜像"></a>下载镜像</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker pull rethinkdb</span><br><span class="line">docker pull microbox/etcd</span><br><span class="line">docker pull shipyard/docker-proxy</span><br><span class="line">docker pull swarm</span><br><span class="line">docker pull shipyard/shipyard</span><br></pre></td></tr></table></figure>
<h3 id="自动安装"><a href="#自动安装" class="headerlink" title="自动安装"></a>自动安装</h3><p>注意:这将会暴露Docker Engine的管理端口2375。如果此节点在安全网络外部可以访问,建议使用TLS。</p>
<h4 id="下载自动部署Shell脚本"><a href="#下载自动部署Shell脚本" class="headerlink" title="下载自动部署Shell脚本"></a>下载自动部署Shell脚本</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | bash -s</span><br></pre></td></tr></table></figure>
<p>自动部署脚本中, 包括以下参数:</p>
<ul>
<li>ACTION: 表示可以使用的指令,它包括以下选项。</li>
<li>deploy, 默认值, 表示自动安装部署Shipyard管理工具及相关应用</li>
<li>upgrade,更新已存在的实例(注意:你要保持相同的系统环境、变量来部署同样的配置)</li>
<li>node, 部署Swarm的一个新节点</li>
<li>remove, 已存在的shipyard实例</li>
<li>DISCOVERY: 集群系统采用Swarm进行采集和管理(在节点管理中可以使用‘node’)</li>
<li>IMAGE: 镜像,默认使用shipyard的镜像</li>
<li>PREFIX: 容器名字的前缀</li>
<li>SHIPYARD_ARGS: 容器的常用参数</li>
<li>TLS_CERT_PATH: TLS证书路径</li>
<li>PORT: 主程序监听端口 (默认端口: 8080)</li>
<li>PROXY_PORT: 代理端口 (默认: 2375)</li>
</ul>
<h4 id="使用镜像"><a href="#使用镜像" class="headerlink" title="使用镜像"></a>使用镜像</h4><p>Shipyard允许您采取指定的镜像来部署实例,比如以下的测试版本,你也已这样做:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | IMAGE=shipyard/shipyard:<span class="built_in">test</span> bash -s</span><br></pre></td></tr></table></figure>
<h4 id="使用前缀"><a href="#使用前缀" class="headerlink" title="使用前缀"></a>使用前缀</h4><p>你可以在部署Shipyard管理工具时,自定义你想要的前缀,比如</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | PREFIX=shipyard-test bash -s</span><br></pre></td></tr></table></figure>
<h4 id="使用运行参数"><a href="#使用运行参数" class="headerlink" title="使用运行参数"></a>使用运行参数</h4><p>这里增加一些shipyard运行参数,你可以像这样进行调整:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | SHIPYARD_ARGS=<span class="string">"--ldap-server=ldap.example.com --ldap-autocreate-users"</span> bash -s</span><br></pre></td></tr></table></figure>
<h4 id="使用安全认证-TLS证书"><a href="#使用安全认证-TLS证书" class="headerlink" title="使用安全认证(TLS证书)"></a>使用安全认证(TLS证书)</h4><p>启用安全加密通讯协议(TLS)对Shipyard进行部署,包括代理(docker-proxy)、swarm集群、shipyard管理平台的配置,这是一个配置规范。证书必须采用以下命名规范:</p>
<ul>
<li>ca.pem: 安全认证证书</li>
<li>server.pem: 服务器证书</li>
<li>server-key.pem: 服务器私有证书</li>
<li>cert.pem: 客户端证书</li>
<li>key.pem: 客户端证书的key</li>
</ul>
<p>注意:证书将被放置在一个单独的安全认证docker容器中,并在各个组成部分之间共享。如果需要调试,可以将此容器连接到调试容器。数据容器名称为$PREFIX-certs。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run --rm \</span><br><span class="line"> -v $(<span class="built_in">pwd</span>)/certs:/certs \</span><br><span class="line"> ehazlett/certm \ -d /certs \</span><br><span class="line"> bundle \</span><br><span class="line"> generate \</span><br><span class="line"> -o shipyard \</span><br><span class="line"> --host proxy \</span><br><span class="line"> --host 127.0.0.1</span><br></pre></td></tr></table></figure>
<p>你也可以在部署时,指定TLS_CERT_PATH参数:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | TLS_CERT_PATH=$(<span class="built_in">pwd</span>)/certs bash -s</span><br></pre></td></tr></table></figure>
<h4 id="增加Swarm节点"><a href="#增加Swarm节点" class="headerlink" title="增加Swarm节点"></a>增加Swarm节点</h4><p>Shipyard管理的Swarm节点部署脚本将自动的安装key/value存储系统(etcd系统),用于进行服务发现, 相关的工具还有Consul、Zookeeper。增加一个节点到swarm集群,你可以通过以下的节点部署脚本:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | ACTION=node DISCOVERY=etcd://10.0.1.10:4001 bash -s</span><br></pre></td></tr></table></figure>
<p>注意:10.0.1.10该ip地址为部署Ectd系统所在主机的IP地址,你需要根据你的部署位置进行修改。</p>
<h4 id="删除Shipyard管理工具"><a href="#删除Shipyard管理工具" class="headerlink" title="删除Shipyard管理工具"></a>删除Shipyard管理工具</h4><p>如果你要删除Shipyard部署的容器,你可以使用以下脚本进行删除。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sSL https://shipyard-project.com/deploy | ACTION=remove bash -s</span><br></pre></td></tr></table></figure>
<h3 id="手动安装"><a href="#手动安装" class="headerlink" title="手动安装"></a>手动安装</h3><h4 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h4><p>Shipyard使用RethinkDB做为数据存储工具, 我们需要先运行RethinkDB容器。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-rethinkdb \</span><br><span class="line"> rethinkdb</span><br></pre></td></tr></table></figure>
<h4 id="服务发现"><a href="#服务发现" class="headerlink" title="服务发现"></a>服务发现</h4><p>为了启用Swarm leader选择,我们必须使用来自Swarm容器的外部键值存储。此处,我们使用Etcd作为服务发现工具。可以选用的服务发现工具还有Consul、Zookeeper等。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> -p 4001:4001 \</span><br><span class="line"> -p 7001:7001 \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-discovery \</span><br><span class="line"> microbox/etcd:latest \</span><br><span class="line"> -name discovery</span><br></pre></td></tr></table></figure>
<h4 id="Docker代理服务"><a href="#Docker代理服务" class="headerlink" title="Docker代理服务"></a>Docker代理服务</h4><p>默认情况下,Docker引擎只侦听套接字。 我们可以重新配置引擎以使用TLS,或者您可以使用代理容器。 这是一个非常轻量级的容器,它只是将请求从TCP转发到Docker监听的Unix套接字。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> -p 2375:2375 \</span><br><span class="line"> --hostname=<span class="variable">$HOSTNAME</span> \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-proxy \</span><br><span class="line"> -v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line"> -e PORT=2375 \</span><br><span class="line"> shipyard/docker-proxy:latest</span><br></pre></td></tr></table></figure>
<h4 id="Swarm管理节点"><a href="#Swarm管理节点" class="headerlink" title="Swarm管理节点"></a>Swarm管理节点</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-swarm-manager \</span><br><span class="line"> swarm:latest \</span><br><span class="line"> manage --host tcp://0.0.0.0:3375 etcd://<IP-OF-HOST>:4001</span><br></pre></td></tr></table></figure>
<h4 id="Swarm-Agent节点"><a href="#Swarm-Agent节点" class="headerlink" title="Swarm Agent节点"></a>Swarm Agent节点</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-swarm-agent \</span><br><span class="line"> swarm:latest \</span><br><span class="line"> join --addr <ip-of-host>:2375 etcd://<ip-of-host>:4001</span><br></pre></td></tr></table></figure>
<h4 id="Shipyard管理工具"><a href="#Shipyard管理工具" class="headerlink" title="Shipyard管理工具"></a>Shipyard管理工具</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line"> -ti \</span><br><span class="line"> -d \</span><br><span class="line"> --restart=always \</span><br><span class="line"> --name shipyard-controller \</span><br><span class="line"> --link shipyard-rethinkdb:rethinkdb \</span><br><span class="line"> --link shipyard-swarm-manager:swarm \</span><br><span class="line"> -p 8080:8080 \</span><br><span class="line"> shipyard/shipyard:latest \</span><br><span class="line"> server \</span><br><span class="line"> -d tcp://swarm:3375</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>容器技术</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>Tool</tag>
</tags>
</entry>
<entry>
<title>Docker的Secrets管理</title>
<url>/posts/b8062d72/</url>
<content><![CDATA[<p>我相信当我们意识到重要且敏感的访问信息已经暴露到公共网络上,并可能使您的微服务无条件被访问。随着我们依赖于的开发出来的服务化的量不断增加, 这时跟踪敏感细节的数量也有所增加。为了应对这个问题,在“secrets managemen”领域出现了工具。</p>
<p>在这篇文章中,我们将看Docker Secrets,要求在Docker 1.13及更高版本的新秘密管理功能。</p>
<p>从Docker的角度来看,该功能不需要太多的工作,但是您可能需要重构应用程序以利用它。我们将介绍如何做到这一点的想法,但不是详细的。</p>
<p>Docker的 Secrets只适用于Docker群集,主要是因为这是秘密管理最有意义的领域。毕竟,Swarm是针对多个Docker实例需要在他们之间共享访问细节的生产用途。如果要在独立容器中使用秘密管理,则需要运行</p>
<p>scale值设置为1 的容器。适用于Mac和Windows的Docker不支持多节点群集模式,但您可以使用它们使用Docker Machine创建多节点群集。</p>
<p>创建两个机器,然后创建一个两个节点,并从该组中的一个swarm环境中运行本文中的案例。</p>
<h3 id="获得Secrets"><a href="#获得Secrets" class="headerlink" title="获得Secrets"></a>获得Secrets</h3><p>当您从命令行创建Secrets时,您可以使用所有可用的工具来创建随机密码和管道输出。例如,为数据库用户创建一个随机密码:</p>
<p>opensslrand-base6420|dockersecretcreatemariadb_password-</p>
<p>这将返回一个秘密的ID。</p>
<p>您需要再次发出此命令以生成MariaDB root用户的密码。您将需要这样才能开始使用,但您不需要为每项服务。</p>
<p>opensslrand-base6420|dockersecretcreatemariadb_root_password-</p>
<p>如果你已经忘记了你创建的秘密, 可以用ls查看,也可以用以下命令查看docker secret ls</p>
<h3 id="替换secrets"><a href="#替换secrets" class="headerlink" title="替换secrets"></a>替换secrets</h3><p>为了保持秘密,良好的秘密,服务之间的通信发生在您定义的覆盖网络中。它们只能通过调用其ID来在该覆盖网络中使用。</p>
<p>dockernetworkcreate-doverlaymariadb_private</p>
<p>这也将返回该网络的ID。再次,你可以docker network ls查看相关网络</p>
<h3 id="创建服务"><a href="#创建服务" class="headerlink" title="创建服务"></a>创建服务</h3><p>这个例子将有一个Docker节点运行MariaDB,一个运行Python的节点。在最终的应用程序中,Python应用程序将读取和写入数据库。</p>
<p>首先,添加一个MariaDB服务。此服务使用您创建的网络进行通信,之前创建的秘密保存为两个文件:一个用于根密码,一个用于默认用户密码。然后将所需的所有变量作为环境变量传递给服务。</p>
<p>dockerservicecreate\ –namemariadb\ –replicas1\ –networkmariadb_private\ –mounttype=volume,source=mydata,destination=/var/lib/mariadb\ –secretsource=mariadb_root_password,target=mariadb_root_password\ –secretsource=mariadb_password,target=mariadb_password\ -eMARIADB_ROOT_PASSWORD_FILE=”/run/secrets/mariadb_root_password”\ -eMARIADB_PASSWORD_FILE=”/run/secrets/mariadb_password”\ -eMARIADB_USER=”python”\ -eMARIADB_DATABASE=”python”\</p>
<p>Python实例再次使用您创建的专用网络,并复制网络中可访问的秘密。一个更好的(生产就绪的)选项将是创建您的应用程序在管理程序中需要的数据库,而不会给应用程序访问根密码,但这仅仅是一个例子。</p>
<p>dockerservicecreate\ –namecspython\ –replicas1\ –networkmariadb_private\ –publish50000:5000\ –mounttype=volume,source=pydata,destination=/var/www/html\ –secretsource=mariadb_root_password,target=python_root_password,mode=0400\ –secretsource=mariadb_password,target=python_password,mode=0400\ -ePYTHON_DB_USER=”python”\ -ePYTHON_DB_ROOT_PASSWORD_FILE=”/run/secrets/python_root_password”\ -ePYTHON_DB_PASSWORD_FILE=”/run/secrets/python_password”\ -ePYTHON_DB_HOST=”mariadb:3306”\ -ePYTHON_DB_NAME=”python”\</p>
<p>上面的示例使用我创建的一个简单的Docker映像,它设置用于使用Flask创建Web应用程序的软件包,用于提供Web页面和PyMySQL来进行数据库访问。代码没有做太多,但显示了如何从Docker容器访问环境变量。</p>
<p>例如,要连接到没有指定数据库的数据库服务器:</p>
<p>importos importMySQLdb db=MySQLdb.connect(host=os.environ[‘PYTHON_DB_HOST’], user=os.environ[‘PYTHON_DB_ROOT_USER’], passwd=os.environ[‘PYTHON_DB_PASSWORD_FILE’]) cur=db.cursor() print(db) db.close()</p>
<p>更新secrets</p>
<p>频繁更改敏感信息是个好习惯。但是,您可能知道,在应用程序中更新这些细节是一个沉闷的过程,最不愿意避免。通过服务,Docker Secrets管理允许您更改值,而无需更改代码。</p>
<p>创建一个新秘密:</p>
<p>opensslrand-base6420|dockersecretcreatemariadb_password_march-</p>
<p>从MariaDB服务中删除当前密码的访问权限:</p>
<p>dockerserviceupdate\ –secret-rmmariadb_password\</p>
<p>并让它访问新的秘密,将目标指向新的值:</p>
<p>dockerserviceupdate\ –secret-addsource=mariadb_password_march,target=mysql_password\</p>
<p>更新Python服务:</p>
<p>dockerserviceupdate\ –secret-rmmariadb_password\ –secret-addsource=mariadb_password_march,target=python_password,mode=0400\</p>
<p>并删除旧秘密:</p>
<p>dockersecretrmmariadb_password</p>
<h3 id="扩展说明"><a href="#扩展说明" class="headerlink" title="扩展说明"></a>扩展说明</h3><p>Docker Secrets是一个新功能,但Docker鼓励镜像维护人员尽快为Docker用户提供更好的安全性。这需要允许与上述示例类似的过程,其中容器可以从通过生成秘密而不是硬编码到应用中创建的文件来读取其需要的每个参数。这可以强制实施集装箱应用程序,因为容器可以来回走动,但是始终可以访问您的应用程序运行所需的重要信息。</p>
]]></content>
<categories>
<category>容器技术</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>Secret</tag>
</tags>
</entry>
<entry>
<title>EL表达式fn:endsWith函数的bug</title>
<url>/posts/73c8edbb/</url>
<content><![CDATA[<p>jstl-1.2.jar</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">endsWith</span><span class="params">(String input, String substring)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (input == <span class="keyword">null</span>)</span><br><span class="line"> input = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">if</span> (substring == <span class="keyword">null</span>)</span><br><span class="line"> substring = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">int</span> index = input.indexOf(substring); <span class="comment">// should be indexOf应该是lastIndexOf 才对</span></span><br><span class="line"> <span class="keyword">if</span> (index == -<span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> ((index == <span class="number">0</span>) && (substring.length() == <span class="number">0</span>))</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">return</span> index == input.length() - substring.length();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Jstl</tag>
</tags>
</entry>
<entry>
<title>Git Commit message 的写法规范之《Angular 规范》</title>
<url>/posts/72195e4f/</url>
<content><![CDATA[<blockquote>
<p>目前,社区有多种 Commit message 的写法规范。本文介绍<a href="https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.greljkmo14y0" target="_blank" rel="noopener">《Angular 规范》</a>,这是目前使用最广的写法,比较合理和系统化,并且有配套的工具。</p>
</blockquote>
<h2 id="一、Commit-message-的作用"><a href="#一、Commit-message-的作用" class="headerlink" title="一、Commit message 的作用"></a>一、Commit message 的作用</h2><p>格式化的Commit message,有几个好处。</p>
<h3 id="(1)提供更多的历史信息,方便快速浏览。"><a href="#(1)提供更多的历史信息,方便快速浏览。" class="headerlink" title="(1)提供更多的历史信息,方便快速浏览。"></a>(1)提供更多的历史信息,方便快速浏览。</h3><p>比如,下面的命令显示上次发布后的变动,每个commit占据一行。你只看行首,就知道某次 commit 的目的。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ git log <last tag> HEAD --pretty=format:%s</span><br></pre></td></tr></table></figure>
<h3 id="(2)可以过滤某些commit(比如文档改动),便于快速查找信息。"><a href="#(2)可以过滤某些commit(比如文档改动),便于快速查找信息。" class="headerlink" title="(2)可以过滤某些commit(比如文档改动),便于快速查找信息。"></a>(2)可以过滤某些commit(比如文档改动),便于快速查找信息。</h3><p>比如,下面的命令仅仅显示本次发布新增加的功能。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ git log <last release> HEAD --grep feature</span><br></pre></td></tr></table></figure>
<h3 id="(3)可以直接从commit生成Change-log。"><a href="#(3)可以直接从commit生成Change-log。" class="headerlink" title="(3)可以直接从commit生成Change log。"></a>(3)可以直接从commit生成Change log。</h3><p>Change Log 是发布新版本时,用来说明与上一个版本差异的文档,详见后文。</p>
<h2 id="二、Commit-message-的格式"><a href="#二、Commit-message-的格式" class="headerlink" title="二、Commit message 的格式"></a>二、Commit message 的格式</h2><p>每次提交,Commit message 都包括三个部分:Header,Body 和 Footer。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><type>(<scope>): <subject></span><br><span class="line">// 空一行</span><br><span class="line"><body></span><br><span class="line">// 空一行</span><br><span class="line"><footer></span><br></pre></td></tr></table></figure>
<p>其中,Header 是必需的,Body 和 Footer 可以省略。<br>不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。</p>
<h3 id="2-1-Header"><a href="#2-1-Header" class="headerlink" title="2.1 Header"></a>2.1 Header</h3><p>Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。</p>
<h4 id="(1)type"><a href="#(1)type" class="headerlink" title="(1)type"></a>(1)type</h4><ul>
<li>type用于说明 commit 的类别,只允许使用下面7个标识。</li>
<li>feat:新功能(feature)</li>
<li>fix:修补bug</li>
<li>docs:文档(documentation)</li>
<li>style: 格式(不影响代码运行的变动)</li>
<li>refactor:重构(即不是新增功能,也不是修改bug的代码变动)</li>
<li>test:增加测试</li>
<li>chore:构建过程或辅助工具的变动</li>
</ul>
<p>如果type为feat和fix,则该 commit 将肯定出现在 Change log 之中。其他情况(docs、chore、style、refactor、test)由你决定,要不要放入 Change log,建议是不要。</p>
<h4 id="(2)scope"><a href="#(2)scope" class="headerlink" title="(2)scope"></a>(2)scope</h4><p>scope用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。</p>
<h4 id="(3)subject"><a href="#(3)subject" class="headerlink" title="(3)subject"></a>(3)subject</h4><p>subject是 commit 目的的简短描述,不超过50个字符。<br>以动词开头,使用第一人称现在时,比如change,而不是changed或changes<br>第一个字母小写<br>结尾不加句号(.)</p>
<h3 id="2-2-Body"><a href="#2-2-Body" class="headerlink" title="2.2 Body"></a>2.2 Body</h3><p>Body 部分是对本次 commit 的详细描述,可以分成多行。下面是一个范例。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">More detailed explanatory text, if necessary. Wrap it to</span><br><span class="line">about 72 characters or so.</span><br><span class="line"></span><br><span class="line">Further paragraphs come after blank lines.</span><br><span class="line"></span><br><span class="line">- Bullet points are okay, too</span><br><span class="line">- Use a hanging indent</span><br></pre></td></tr></table></figure>
<p>有两个注意点。<br>(1)使用第一人称现在时,比如使用change而不是changed或changes。<br>(2)应该说明代码变动的动机,以及与以前行为的对比。</p>
<h3 id="2-3-Footer"><a href="#2-3-Footer" class="headerlink" title="2.3 Footer"></a>2.3 Footer</h3><p>Footer 部分只用于两种情况。</p>
<h4 id="(1)不兼容变动"><a href="#(1)不兼容变动" class="headerlink" title="(1)不兼容变动"></a>(1)不兼容变动</h4><p>如果当前代码与上一个版本不兼容,则 Footer 部分以BREAKING CHANGE开头,后面是对变动的描述、以及变动理由和迁移方法。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">BREAKING CHANGE: isolate scope bindings definition has changed.</span><br><span class="line"></span><br><span class="line"> To migrate the code follow the example below:</span><br><span class="line"></span><br><span class="line"> Before:</span><br><span class="line"></span><br><span class="line"> scope: {</span><br><span class="line"> myAttr: 'attribute',</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> After:</span><br><span class="line"></span><br><span class="line"> scope: {</span><br><span class="line"> myAttr: '@',</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> The removed `inject` wasn't generaly useful for directives so there should be no code using it.</span><br></pre></td></tr></table></figure>
<h4 id="(2)关闭-Issue"><a href="#(2)关闭-Issue" class="headerlink" title="(2)关闭 Issue"></a>(2)关闭 Issue</h4><p>如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue 。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Closes #234</span><br></pre></td></tr></table></figure>
<p>也可以一次关闭多个 issue 。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Closes #123, #245, #992</span><br></pre></td></tr></table></figure>
<h3 id="2-4-Revert"><a href="#2-4-Revert" class="headerlink" title="2.4 Revert"></a>2.4 Revert</h3><p>还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">revert: feat(pencil): add 'graphiteWidth' option</span><br><span class="line"></span><br><span class="line">This reverts commit 667ecc1654a317a13331b17617d973392f415f02.</span><br></pre></td></tr></table></figure>
<p>Body部分的格式是固定的,必须写成This reverts commit <hash>.,其中的hash是被撤销 commit 的 SHA 标识符。<br>如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。</p>
<h2 id="三、Commitizen"><a href="#三、Commitizen" class="headerlink" title="三、Commitizen"></a>三、Commitizen</h2><p>Commitizen是一个撰写合格 Commit message 的工具。<br>安装命令如下。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g commitizen</span><br></pre></td></tr></table></figure>
<p>然后,在项目目录里,运行下面的命令,使其支持 Angular 的 Commit message 格式。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">commitizen init cz-conventional-changelog --save --save-exact</span><br></pre></td></tr></table></figure>
<p>以后,凡是用到git commit命令,一律改为使用git cz。这时,就会出现选项,用来生成符合格式的 Commit message。</p>
<h2 id="四、validate-commit-msg"><a href="#四、validate-commit-msg" class="headerlink" title="四、validate-commit-msg"></a>四、validate-commit-msg</h2><p>validate-commit-msg 用于检查 Node 项目的 Commit message 是否符合格式。<br>它的安装是手动的。首先,拷贝下面这个JS文件,放入你的代码库。文件名可以取为validate-commit-msg.js。<br>接着,把这个脚本加入 Git 的 hook。下面是在package.json里面使用 ghooks,把这个脚本加为commit-msg时运行。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">"config": {</span><br><span class="line"> "ghooks": {</span><br><span class="line"> "commit-msg": "./validate-commit-msg.js"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后,每次git commit的时候,这个脚本就会自动检查 Commit message 是否合格。如果不合格,就会报错。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add -A</span><br><span class="line">git commit -m <span class="string">"edit markdown"</span></span><br><span class="line">INVALID COMMIT MSG: does not match <span class="string">"<type>(<scope>): <subject>"</span> ! was: edit markdown</span><br></pre></td></tr></table></figure>
<h2 id="五、生成-Change-log"><a href="#五、生成-Change-log" class="headerlink" title="五、生成 Change log"></a>五、生成 Change log</h2><p>如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成(例1,例2,例3)。<br>生成的文档包括以下三个部分。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">New features</span><br><span class="line">Bug fixes</span><br><span class="line">Breaking changes.</span><br></pre></td></tr></table></figure>
<p>每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。<br>conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g conventional-changelog</span><br><span class="line"><span class="built_in">cd</span> my-project</span><br><span class="line">conventional-changelog -p angular -i CHANGELOG.md -w</span><br></pre></td></tr></table></figure>
<p>上面命令不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动。<br>如果你想生成所有发布的 Change log,要改为运行下面的命令。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">conventional-changelog -p angular -i CHANGELOG.md -w -r 0</span><br></pre></td></tr></table></figure>
<p>为了方便使用,可以将其写入package.json的scripts字段。</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"changelog"</span>: <span class="string">"conventional-changelog -p angular -i CHANGELOG.md -w -r 0"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以后,直接运行下面的命令即可。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm run changelog</span><br></pre></td></tr></table></figure>
<blockquote>
<p>转自<a href="http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html" target="_blank" rel="noopener">http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html</a></p>
</blockquote>
]]></content>
<categories>
<category>工具</category>
</categories>
<tags>
<tag>Git</tag>
<tag>Changelog</tag>
<tag>Commitizen</tag>
</tags>
</entry>
<entry>
<title>JAVA不借助中间变量交换2个变量的值</title>
<url>/posts/f07e5cea/</url>
<content><![CDATA[<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">/*方法一*/</span></span><br><span class="line"> <span class="keyword">int</span> a = <span class="number">3</span>;</span><br><span class="line"> <span class="keyword">int</span> b = <span class="number">4</span>;</span><br><span class="line"> a=a+b;</span><br><span class="line"> b=a-b;</span><br><span class="line"> a=a-b;</span><br><span class="line"> System.out.println(<span class="string">"a="</span>+a+<span class="string">",b="</span>+b);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*方法二利用位运算交换,效率很高*/</span></span><br><span class="line"> <span class="keyword">int</span> aa=<span class="number">3</span>;</span><br><span class="line"> <span class="keyword">int</span> bb=<span class="number">4</span>;</span><br><span class="line"> aa=aa^bb;</span><br><span class="line"> bb=bb^aa;</span><br><span class="line"> aa=aa^bb;</span><br><span class="line"> System.out.println(<span class="string">"aa="</span>+aa+<span class="string">",bb="</span>+bb);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Java 9 中的 9 个新特性</title>
<url>/posts/d1d33620/</url>
<content><![CDATA[<h2 id="Java-9-中的-9-个新特性"><a href="#Java-9-中的-9-个新特性" class="headerlink" title="Java 9 中的 9 个新特性"></a>Java 9 中的 9 个新特性</h2><p><img data-src="/images/pasted-0.png" alt="upload successful"></p>
<p>Java 8 发布三年多之后,即将快到2017年7月下一个版本发布的日期了。 你可能已经听说过 Java 9 的模块系统,但是这个新版本还有许多其它的更新。 这里有九个令人兴奋的新功能将与 Java 9 一起发布。</p>
<h3 id="Java-平台级模块系统"><a href="#Java-平台级模块系统" class="headerlink" title="Java 平台级模块系统"></a>Java 平台级模块系统</h3><p>Java 9 的定义功能是一套全新的模块系统。当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到, 这样就会导致无意中使用了并不想被公开访问的 API。此外,类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了, 或者是不是会有重复的项呢? 模块系统把这俩个问题都给解决了。</p>
<p>模块化的 JAR 文件都包含一个额外的模块描述器。在这个模块描述器中, 对其它模块的依赖是通过 “requires” 来表示的。另外, “exports” 语句控制着哪些包是可以被其它模块访问到的。所有不被导出的包默认都封装在模块的里面。如下是一个模块描述器的示例,存在于 “module-info.java” 文件中:</p>
<p>module blog {<br>我们可以如下展示模块:</p>
<p><img data-src="/images/pasted-1.png" alt="upload successful"></p>
<p>请注意,两个模块都包含封装的包,因为它们没有被导出(使用橙色盾牌可视化)。 没有人会偶然地使用来自这些包中的类。Java 平台本身也使用自己的模块系统进行了模块化。通过封装 JDK 的内部类,平台更安全,持续改进也更容易。</p>
<p>当启动一个模块化应用时, JVM 会验证是否所有的模块都能使用,这基于 <code>requires</code> 语句——比脆弱的类路径迈进了一大步。模块允许你更好地强制结构化封装你的应用并明确依赖。你可以在这个课程中学习更多关于 Java 9 中模块工作的信息 。</p>
<h3 id="Linking"><a href="#Linking" class="headerlink" title="Linking"></a>Linking</h3><p>当你使用具有显式依赖关系的模块和模块化的 JDK 时,新的可能性出现了。你的应用程序模块现在将声明其对其他应用程序模块的依赖以及对其所使用的 JDK 模块的依赖。为什么不使用这些信息创建一个最小的运行时环境,其中只包含运行应用程序所需的那些模块呢? 这可以通过 Java 9 中的新的 jlink 工具实现。你可以创建针对应用程序进行优化的最小运行时映像而不需要使用完全加载 JDK 安装版本。</p>
<h3 id="JShell-交互式-Java-REPL"><a href="#JShell-交互式-Java-REPL" class="headerlink" title="JShell: 交互式 Java REPL"></a>JShell: 交互式 Java REPL</h3><p>许多语言已经具有交互式编程环境,Java 现在加入了这个俱乐部。您可以从控制台启动 jshell ,并直接启动输入和执行 Java 代码。 jshell 的即时反馈使它成为探索 API 和尝试语言特性的好工具。</p>
<p><img data-src="/images/pasted-2.png" alt="upload successful"></p>
<p>测试一个 Java 正则表达式是一个很好的说明 jshell 如何使您的生活更轻松的例子。 交互式 shell 还可以提供良好的教学环境以及提高生产力,您可以在此了解更多信息。在教人们如何编写 Java 的过程中,不再需要解释 “public static void main(String [] args)” 这句废话。</p>
<h3 id="改进的-Javadoc"><a href="#改进的-Javadoc" class="headerlink" title="改进的 Javadoc"></a>改进的 Javadoc</h3><p>有时一些小事情可以带来很大的不同。你是否就像我一样在一直使用 Google 来查找正确的 Javadoc 页面呢? 这不再需要了。Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。此外,你会注意到,每个 Javadoc 页面都包含有关 JDK 模块类或接口来源的信息。</p>
<p><img data-src="/images/pasted-3.png" alt="upload successful"></p>
<h3 id="集合工厂方法"><a href="#集合工厂方法" class="headerlink" title="集合工厂方法"></a>集合工厂方法</h3><p>通常,您希望在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:</p>
<p>Set<Integer> ints = Set.of(1, 2, 3);List<String> strings = List.of(“first”, “second”);<br>除了更短和更好阅读之外,这些方法也可以避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,因为它们是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。</p>
<h3 id="改进的-Stream-API"><a href="#改进的-Stream-API" class="headerlink" title="改进的 Stream API"></a>改进的 Stream API</h3><p>长期以来,Stream API 都是 Java 标准库最好的改进之一。通过这套 API 可以在集合上建立用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法:dropWhile, takeWhile, ofNullable。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:</p>
<p>IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);<br>第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。</p>
<p>除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 <code>stram</code> 将一个 Optional 对象转换为一个(可能是空的) Stream 对象:</p>
<p>Stream<Integer> s = Optional.of(1).stream();<br>在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 非常有用。</p>
<h3 id="私有接口方法"><a href="#私有接口方法" class="headerlink" title="私有接口方法"></a>私有接口方法</h3><p>Java 8 为我们带来了接口的默认方法。 接口现在也可以包含行为,而不仅仅是方法签名。 但是,如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。 使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:</p>
<p>public interface MyInterface {<br>如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。</p>
<h3 id="HTTP-2"><a href="#HTTP-2" class="headerlink" title="HTTP/2"></a>HTTP/2</h3><p>Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 <code>HttpURLConnection</code> API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。不过你可以在 Java 9 中开始使用这套 API:</p>
<p>HttpClient client = HttpClient.newHttpClient();HttpRequest req =<br>除了这个简单的请求/响应模型之外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,比如流和服务端推送。</p>
<h3 id="多版本兼容-JAR"><a href="#多版本兼容-JAR" class="headerlink" title="多版本兼容 JAR"></a>多版本兼容 JAR</h3><p>我们最后要来着重介绍的这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多情况下就是 Java 6 或者 7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:</p>
<p>multirelease.jar<br>在上述场景中, multirelease.jar 可以在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为较老版本的 Java 只会看到顶层的这个 Helper 类。</p>
]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Java7和Java8中的ConcurrentHashMap原理解析</title>
<url>/posts/cd7a25a4/</url>
<content><![CDATA[<h2 id="1-Java7中ConcurrentHashMap"><a href="#1-Java7中ConcurrentHashMap" class="headerlink" title="1. Java7中ConcurrentHashMap"></a>1. Java7中ConcurrentHashMap</h2><p>ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。<br>整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为<strong>分段锁</strong>。注意,行文中,我很多地方用了<strong>“槽”</strong>来代表一个 segment。<br>简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。</p>
<p><img data-src="/images/pasted-61.png" alt="upload successful"></p>
<p><strong>concurrencyLevel</strong>:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。<br>再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。</p>
<h3 id="1-1-初始化"><a href="#1-1-初始化" class="headerlink" title="1.1. 初始化"></a>1.1. 初始化</h3><p><strong>initialCapacity</strong>:初始容量,这个值指的是整个 ConcurrentHashMap 的初始容量,实际操作的时候需要平均分给每个 Segment。<br><strong>loadFactor</strong>:负载因子,之前我们说了,Segment 数组不可以扩容,所以这个负载因子是给每个 Segment 内部使用的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">float</span> loadFactor, <span class="keyword">int</span> concurrencyLevel)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!(loadFactor > <span class="number">0</span>) || initialCapacity < <span class="number">0</span> || concurrencyLevel <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line"> <span class="keyword">if</span> (concurrencyLevel > MAX_SEGMENTS)</span><br><span class="line"> concurrencyLevel = MAX_SEGMENTS;</span><br><span class="line"> <span class="comment">// Find power-of-two sizes best matching arguments</span></span><br><span class="line"> <span class="keyword">int</span> sshift = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> ssize = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 计算并行级别 ssize,因为要保持并行级别是 2 的 n 次方</span></span><br><span class="line"> <span class="keyword">while</span> (ssize < concurrencyLevel) {</span><br><span class="line"> ++sshift;</span><br><span class="line"> ssize <<= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 我们这里先不要那么烧脑,用默认值,concurrencyLevel 为 16,sshift 为 4</span></span><br><span class="line"> <span class="comment">// 那么计算出 segmentShift 为 28,segmentMask 为 15,后面会用到这两个值</span></span><br><span class="line"> <span class="keyword">this</span>.segmentShift = <span class="number">32</span> - sshift;</span><br><span class="line"> <span class="keyword">this</span>.segmentMask = ssize - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity > MAXIMUM_CAPACITY)</span><br><span class="line"> initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"> <span class="comment">// initialCapacity 是设置整个 map 初始的大小,</span></span><br><span class="line"> <span class="comment">// 这里根据 initialCapacity 计算 Segment 数组中每个位置可以分到的大小</span></span><br><span class="line"> <span class="comment">// 如 initialCapacity 为 64,那么每个 Segment 或称之为"槽"可以分到 4 个</span></span><br><span class="line"> <span class="keyword">int</span> c = initialCapacity / ssize;</span><br><span class="line"> <span class="keyword">if</span> (c * ssize < initialCapacity)</span><br><span class="line"> ++c;</span><br><span class="line"> <span class="comment">// 默认 MIN_SEGMENT_TABLE_CAPACITY 是 2,这个值也是有讲究的,因为这样的话,对于具体的槽上,</span></span><br><span class="line"> <span class="comment">// 插入一个元素不至于扩容,插入第二个的时候才会扩容</span></span><br><span class="line"> <span class="keyword">int</span> cap = MIN_SEGMENT_TABLE_CAPACITY; </span><br><span class="line"> <span class="keyword">while</span> (cap < c)</span><br><span class="line"> cap <<= <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 创建 Segment 数组,</span></span><br><span class="line"> <span class="comment">// 并创建数组的第一个元素 segment[0]</span></span><br><span class="line"> Segment<K,V> s0 =</span><br><span class="line"> <span class="keyword">new</span> Segment<K,V>(loadFactor, (<span class="keyword">int</span>)(cap * loadFactor),</span><br><span class="line"> (HashEntry<K,V>[])<span class="keyword">new</span> HashEntry[cap]);</span><br><span class="line"> Segment<K,V>[] ss = (Segment<K,V>[])<span class="keyword">new</span> Segment[ssize];</span><br><span class="line"> <span class="comment">// 往数组写入 segment[0]</span></span><br><span class="line"> UNSAFE.putOrderedObject(ss, SBASE, s0); <span class="comment">// ordered write of segments[0]</span></span><br><span class="line"> <span class="keyword">this</span>.segments = ss;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>初始化完成,我们得到了一个 Segment 数组。<br>我们就当是用 new ConcurrentHashMap() 无参构造函数进行初始化的,那么初始化完成后:</p>
<ul>
<li>Segment 数组长度为 16,不可以扩容</li>
<li>Segment[i] 的默认大小为 2,负载因子是 0.75,得出初始阈值为 1.5,也就是以后插入第一个元素不会触发扩容,插入第二个会进行第一次扩容</li>
<li>这里初始化了 segment[0],其他位置还是 null,至于为什么要初始化 segment[0],后面的代码会介绍</li>
<li>当前 segmentShift 的值为 32 - 4 = 28,segmentMask 为 16 - 1 = 15,姑且把它们简单翻译为移位数和掩码,这两个值马上就会用到</li>
</ul>
<h3 id="1-2-put-过程分析"><a href="#1-2-put-过程分析" class="headerlink" title="1.2. put 过程分析"></a>1.2. put 过程分析</h3><p>我们先看 put 的主流程,对于其中的一些关键细节操作,后面会进行详细介绍。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> Segment<K,V> s;</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="comment">// 1. 计算 key 的 hash 值</span></span><br><span class="line"> <span class="keyword">int</span> hash = hash(key);</span><br><span class="line"> <span class="comment">// 2. 根据 hash 值找到 Segment 数组中的位置 j</span></span><br><span class="line"> <span class="comment">// hash 是 32 位,无符号右移 segmentShift(28) 位,剩下高 4 位,</span></span><br><span class="line"> <span class="comment">// 然后和 segmentMask(15) 做一次与操作,也就是说 j 是 hash 值的高 4 位,也就是槽的数组下标</span></span><br><span class="line"> <span class="keyword">int</span> j = (hash >>> segmentShift) & segmentMask;</span><br><span class="line"> <span class="comment">// 刚刚说了,初始化的时候初始化了 segment[0],但是其他位置还是 null,</span></span><br><span class="line"> <span class="comment">// ensureSegment(j) 对 segment[j] 进行初始化</span></span><br><span class="line"> <span class="keyword">if</span> ((s = (Segment<K,V>)UNSAFE.getObject <span class="comment">// nonvolatile; recheck</span></span><br><span class="line"> (segments, (j << SSHIFT) + SBASE)) == <span class="keyword">null</span>) <span class="comment">// in ensureSegment</span></span><br><span class="line"> s = ensureSegment(j);</span><br><span class="line"> <span class="comment">// 3. 插入新值到 槽 s 中</span></span><br><span class="line"> <span class="keyword">return</span> s.put(key, hash, value, <span class="keyword">false</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>第一层皮很简单,根据 hash 值很快就能找到相应的 Segment,之后就是 Segment 内部的 put 操作了。<br>Segment 内部是由 <code>数组+链表</code> 组成的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">put</span><span class="params">(K key, <span class="keyword">int</span> hash, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>{</span><br><span class="line"> <span class="comment">// 在往该 segment 写入前,需要先获取该 segment 的独占锁</span></span><br><span class="line"> <span class="comment">// 先看主流程,后面还会具体介绍这部分内容</span></span><br><span class="line"> HashEntry<K,V> node = tryLock() ? <span class="keyword">null</span> :</span><br><span class="line"> scanAndLockForPut(key, hash, value);</span><br><span class="line"> V oldValue;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 这个是 segment 内部的数组</span></span><br><span class="line"> HashEntry<K,V>[] tab = table;</span><br><span class="line"> <span class="comment">// 再利用 hash 值,求应该放置的数组下标</span></span><br><span class="line"> <span class="keyword">int</span> index = (tab.length - <span class="number">1</span>) & hash;</span><br><span class="line"> <span class="comment">// first 是数组该位置处的链表的表头</span></span><br><span class="line"> HashEntry<K,V> first = entryAt(tab, index);</span><br><span class="line"> <span class="comment">// 下面这串 for 循环虽然很长,不过也很好理解,想想该位置没有任何元素和已经存在一个链表这两种情况</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> e = first;;) {</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> K k;</span><br><span class="line"> <span class="keyword">if</span> ((k = e.key) == key ||</span><br><span class="line"> (e.hash == hash && key.equals(k))) {</span><br><span class="line"> oldValue = e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent) {</span><br><span class="line"> <span class="comment">// 覆盖旧值</span></span><br><span class="line"> e.value = value;</span><br><span class="line"> ++modCount;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 继续顺着链表走</span></span><br><span class="line"> e = e.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// node 到底是不是 null,这个要看获取锁的过程,不过和这里都没有关系。</span></span><br><span class="line"> <span class="comment">// 如果不为 null,那就直接将它设置为链表表头;如果是null,初始化并设置为链表表头。</span></span><br><span class="line"> <span class="keyword">if</span> (node != <span class="keyword">null</span>)</span><br><span class="line"> node.setNext(first);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> node = <span class="keyword">new</span> HashEntry<K,V>(hash, key, value, first);</span><br><span class="line"> <span class="keyword">int</span> c = count + <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 如果超过了该 segment 的阈值,这个 segment 需要扩容</span></span><br><span class="line"> <span class="keyword">if</span> (c > threshold && tab.length < MAXIMUM_CAPACITY)</span><br><span class="line"> rehash(node); <span class="comment">// 扩容后面也会具体分析</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 没有达到阈值,将 node 放到数组 tab 的 index 位置,</span></span><br><span class="line"> <span class="comment">// 其实就是将新的节点设置成原链表的表头</span></span><br><span class="line"> setEntryAt(tab, index, node);</span><br><span class="line"> ++modCount;</span><br><span class="line"> count = c;</span><br><span class="line"> oldValue = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 解锁</span></span><br><span class="line"> unlock();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>整体流程还是比较简单的,由于有独占锁的保护,所以 segment 内部的操作并不复杂。至于这里面的并发问题,我们稍后再进行介绍。<br>到这里 put 操作就结束了,接下来,我们说一说其中几步关键的操作。</p>
<h3 id="1-3-初始化槽-ensureSegment"><a href="#1-3-初始化槽-ensureSegment" class="headerlink" title="1.3. 初始化槽: ensureSegment"></a>1.3. 初始化槽: ensureSegment</h3><p>ConcurrentHashMap 初始化的时候会初始化第一个槽 segment[0],对于其他槽来说,在插入第一个值的时候进行初始化。<br>这里需要考虑并发,因为很可能会有多个线程同时进来初始化同一个槽 segment[k],不过只要有一个成功了就可以。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Segment<K,V> <span class="title">ensureSegment</span><span class="params">(<span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Segment<K,V>[] ss = <span class="keyword">this</span>.segments;</span><br><span class="line"> <span class="keyword">long</span> u = (k << SSHIFT) + SBASE; <span class="comment">// raw offset</span></span><br><span class="line"> Segment<K,V> seg;</span><br><span class="line"> <span class="keyword">if</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 这里看到为什么之前要初始化 segment[0] 了,</span></span><br><span class="line"> <span class="comment">// 使用当前 segment[0] 处的数组长度和负载因子来初始化 segment[k]</span></span><br><span class="line"> <span class="comment">// 为什么要用“当前”,因为 segment[0] 可能早就扩容过了</span></span><br><span class="line"> Segment<K,V> proto = ss[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">int</span> cap = proto.table.length;</span><br><span class="line"> <span class="keyword">float</span> lf = proto.loadFactor;</span><br><span class="line"> <span class="keyword">int</span> threshold = (<span class="keyword">int</span>)(cap * lf);</span><br><span class="line"> <span class="comment">// 初始化 segment[k] 内部的数组</span></span><br><span class="line"> HashEntry<K,V>[] tab = (HashEntry<K,V>[])<span class="keyword">new</span> HashEntry[cap];</span><br><span class="line"> <span class="keyword">if</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))</span><br><span class="line"> == <span class="keyword">null</span>) { <span class="comment">// 再次检查一遍该槽是否被其他线程初始化了。</span></span><br><span class="line"> Segment<K,V> s = <span class="keyword">new</span> Segment<K,V>(lf, threshold, tab);</span><br><span class="line"> <span class="comment">// 使用 while 循环,内部用 CAS,当前线程成功设值或其他线程成功设值后,退出</span></span><br><span class="line"> <span class="keyword">while</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))</span><br><span class="line"> == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (UNSAFE.compareAndSwapObject(ss, u, <span class="keyword">null</span>, seg = s))</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> seg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>总的来说,ensureSegment(int k) 比较简单,对于并发操作使用 CAS 进行控制。</p>
<blockquote>
<p>我没搞懂这里为什么要搞一个 while 循环,CAS 失败不就代表有其他线程成功了吗,为什么要再进行判断?<br>感谢评论区的李子木,如果当前线程 CAS 失败,这里的 while 循环是为了将 seg 赋值返回。</p>
</blockquote>
<h3 id="1-4-获取写入锁-scanAndLockForPut"><a href="#1-4-获取写入锁-scanAndLockForPut" class="headerlink" title="1.4. 获取写入锁: scanAndLockForPut"></a>1.4. 获取写入锁: scanAndLockForPut</h3><p>前面我们看到,在往某个 segment 中 put 的时候,首先会调用 node = tryLock() ? null : scanAndLockForPut(key, hash, value),也就是说先进行一次 tryLock() 快速获取该 segment 的独占锁,如果失败,那么进入到 scanAndLockForPut 这个方法来获取锁。<br>下面我们来具体分析这个方法中是怎么控制加锁的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> HashEntry<K,V> <span class="title">scanAndLockForPut</span><span class="params">(K key, <span class="keyword">int</span> hash, V value)</span> </span>{</span><br><span class="line"> HashEntry<K,V> first = entryForHash(<span class="keyword">this</span>, hash);</span><br><span class="line"> HashEntry<K,V> e = first;</span><br><span class="line"> HashEntry<K,V> node = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> retries = -<span class="number">1</span>; <span class="comment">// negative while locating node</span></span><br><span class="line"> <span class="comment">// 循环获取锁</span></span><br><span class="line"> <span class="keyword">while</span> (!tryLock()) {</span><br><span class="line"> HashEntry<K,V> f; <span class="comment">// to recheck first below</span></span><br><span class="line"> <span class="keyword">if</span> (retries < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span>) <span class="comment">// speculatively create node</span></span><br><span class="line"> <span class="comment">// 进到这里说明数组该位置的链表是空的,没有任何元素</span></span><br><span class="line"> <span class="comment">// 当然,进到这里的另一个原因是 tryLock() 失败,所以该槽存在并发,不一定是该位置</span></span><br><span class="line"> node = <span class="keyword">new</span> HashEntry<K,V>(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> retries = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (key.equals(e.key))</span><br><span class="line"> retries = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 顺着链表往下走</span></span><br><span class="line"> e = e.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 重试次数如果超过 MAX_SCAN_RETRIES(单核1多核64),那么不抢了,进入到阻塞队列等待锁</span></span><br><span class="line"> <span class="comment">// lock() 是阻塞方法,直到获取锁后返回</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (++retries > MAX_SCAN_RETRIES) {</span><br><span class="line"> lock();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((retries & <span class="number">1</span>) == <span class="number">0</span> &&</span><br><span class="line"> <span class="comment">// 这个时候是有大问题了,那就是有新的元素进到了链表,成为了新的表头</span></span><br><span class="line"> <span class="comment">// 所以这边的策略是,相当于重新走一遍这个 scanAndLockForPut 方法</span></span><br><span class="line"> (f = entryForHash(<span class="keyword">this</span>, hash)) != first) {</span><br><span class="line"> e = first = f; <span class="comment">// re-traverse if entry changed</span></span><br><span class="line"> retries = -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法有两个出口,一个是 tryLock() 成功了,循环终止,另一个就是重试次数超过了 MAX_SCAN_RETRIES,进到 lock() 方法,此方法会阻塞等待,直到成功拿到独占锁。<br>这个方法就是看似复杂,但是其实就是做了一件事,那就是获取该 segment 的独占锁,如果需要的话顺便实例化了一下 node。</p>
<h3 id="1-5-扩容-rehash"><a href="#1-5-扩容-rehash" class="headerlink" title="1.5. 扩容: rehash"></a>1.5. 扩容: rehash</h3><p>重复一下,segment 数组不能扩容,扩容是 segment 数组某个位置内部的数组 HashEntry[] 进行扩容,扩容后,容量为原来的 2 倍。<br>首先,我们要回顾一下触发扩容的地方,put 的时候,如果判断该值的插入会导致该 segment 的元素个数超过阈值,那么先进行扩容,再插值,读者这个时候可以回去 put 方法看一眼。<br>该方法不需要考虑并发,因为到这里的时候,是持有该 segment 的独占锁的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方法参数上的 node 是这次扩容后,需要添加到新的数组中的数据。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">rehash</span><span class="params">(HashEntry<K,V> node)</span> </span>{</span><br><span class="line"> HashEntry<K,V>[] oldTable = table;</span><br><span class="line"> <span class="keyword">int</span> oldCapacity = oldTable.length;</span><br><span class="line"> <span class="comment">// 2 倍</span></span><br><span class="line"> <span class="keyword">int</span> newCapacity = oldCapacity << <span class="number">1</span>;</span><br><span class="line"> threshold = (<span class="keyword">int</span>)(newCapacity * loadFactor);</span><br><span class="line"> <span class="comment">// 创建新数组</span></span><br><span class="line"> HashEntry<K,V>[] newTable =</span><br><span class="line"> (HashEntry<K,V>[]) <span class="keyword">new</span> HashEntry[newCapacity];</span><br><span class="line"> <span class="comment">// 新的掩码,如从 16 扩容到 32,那么 sizeMask 为 31,对应二进制 ‘000...00011111’</span></span><br><span class="line"> <span class="keyword">int</span> sizeMask = newCapacity - <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 遍历原数组,老套路,将原数组位置 i 处的链表拆分到 新数组位置 i 和 i+oldCap 两个位置</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < oldCapacity ; i++) {</span><br><span class="line"> <span class="comment">// e 是链表的第一个元素</span></span><br><span class="line"> HashEntry<K,V> e = oldTable[i];</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> HashEntry<K,V> next = e.next;</span><br><span class="line"> <span class="comment">// 计算应该放置在新数组中的位置,</span></span><br><span class="line"> <span class="comment">// 假设原数组长度为 16,e 在 oldTable[3] 处,那么 idx 只可能是 3 或者是 3 + 16 = 19</span></span><br><span class="line"> <span class="keyword">int</span> idx = e.hash & sizeMask;</span><br><span class="line"> <span class="keyword">if</span> (next == <span class="keyword">null</span>) <span class="comment">// 该位置处只有一个元素,那比较好办</span></span><br><span class="line"> newTable[idx] = e;</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// Reuse consecutive sequence at same slot</span></span><br><span class="line"> <span class="comment">// e 是链表表头</span></span><br><span class="line"> HashEntry<K,V> lastRun = e;</span><br><span class="line"> <span class="comment">// idx 是当前链表的头结点 e 的新位置</span></span><br><span class="line"> <span class="keyword">int</span> lastIdx = idx;</span><br><span class="line"> <span class="comment">// 下面这个 for 循环会找到一个 lastRun 节点,这个节点之后的所有元素是将要放到一起的</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> last = next;</span><br><span class="line"> last != <span class="keyword">null</span>;</span><br><span class="line"> last = last.next) {</span><br><span class="line"> <span class="keyword">int</span> k = last.hash & sizeMask;</span><br><span class="line"> <span class="keyword">if</span> (k != lastIdx) {</span><br><span class="line"> lastIdx = k;</span><br><span class="line"> lastRun = last;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将 lastRun 及其之后的所有节点组成的这个链表放到 lastIdx 这个位置</span></span><br><span class="line"> newTable[lastIdx] = lastRun;</span><br><span class="line"> <span class="comment">// 下面的操作是处理 lastRun 之前的节点,</span></span><br><span class="line"> <span class="comment">// 这些节点可能分配在另一个链表中,也可能分配到上面的那个链表中</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> p = e; p != lastRun; p = p.next) {</span><br><span class="line"> V v = p.value;</span><br><span class="line"> <span class="keyword">int</span> h = p.hash;</span><br><span class="line"> <span class="keyword">int</span> k = h & sizeMask;</span><br><span class="line"> HashEntry<K,V> n = newTable[k];</span><br><span class="line"> newTable[k] = <span class="keyword">new</span> HashEntry<K,V>(h, p.key, v, n);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将新来的 node 放到新数组中刚刚的 两个链表之一 的 头部</span></span><br><span class="line"> <span class="keyword">int</span> nodeIndex = node.hash & sizeMask; <span class="comment">// add the new node</span></span><br><span class="line"> node.setNext(newTable[nodeIndex]);</span><br><span class="line"> newTable[nodeIndex] = node;</span><br><span class="line"> table = newTable;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里的扩容比之前的 HashMap 要复杂一些,代码难懂一点。上面有两个挨着的 for 循环,第一个 for 有什么用呢?<br>仔细一看发现,如果没有第一个 for 循环,也是可以工作的,但是,这个 for 循环下来,如果 lastRun 的后面还有比较多的节点,那么这次就是值得的。因为我们只需要克隆 lastRun 前面的节点,后面的一串节点跟着 lastRun 走就是了,不需要做任何操作。<br>我觉得 Doug Lea 的这个想法也是挺有意思的,不过比较坏的情况就是每次 lastRun 都是链表的最后一个元素或者很靠后的元素,那么这次遍历就有点浪费了。不过 Doug Lea 也说了,根据统计,如果使用默认的阈值,大约只有 1/6 的节点需要克隆。</p>
<h3 id="1-6-get过程分析"><a href="#1-6-get过程分析" class="headerlink" title="1.6. get过程分析"></a>1.6. get过程分析</h3><p>相对于 put 来说,get 真的不要太简单。</p>
<ul>
<li>计算 hash 值,找到 segment 数组中的具体位置,或我们前面用的“槽”</li>
<li>槽中也是一个数组,根据 hash 找到数组中具体的位置</li>
<li>到这里是链表了,顺着链表进行查找即可</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Segment<K,V> s; <span class="comment">// manually integrate access methods to reduce overhead</span></span><br><span class="line"> HashEntry<K,V>[] tab;</span><br><span class="line"> <span class="comment">// 1. hash 值</span></span><br><span class="line"> <span class="keyword">int</span> h = hash(key);</span><br><span class="line"> <span class="keyword">long</span> u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;</span><br><span class="line"> <span class="comment">// 2. 根据 hash 找到对应的 segment</span></span><br><span class="line"> <span class="keyword">if</span> ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != <span class="keyword">null</span> &&</span><br><span class="line"> (tab = s.table) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 3. 找到segment 内部数组相应位置的链表,遍历</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile</span><br><span class="line"> (tab, ((<span class="keyword">long</span>)(((tab.length - <span class="number">1</span>) & h)) << TSHIFT) + TBASE);</span><br><span class="line"> e != <span class="keyword">null</span>; e = e.next) {</span><br><span class="line"> K k;</span><br><span class="line"> <span class="keyword">if</span> ((k = e.key) == key || (e.hash == h && key.equals(k)))</span><br><span class="line"> <span class="keyword">return</span> e.value;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="1-7-并发问题分析"><a href="#1-7-并发问题分析" class="headerlink" title="1.7. 并发问题分析"></a>1.7. 并发问题分析</h3><p>现在我们已经说完了 put 过程和 get 过程,我们可以看到 get 过程中是没有加锁的,那自然我们就需要去考虑并发问题。<br>添加节点的操作 put 和删除节点的操作 remove 都是要加 segment 上的独占锁的,所以它们之间自然不会有问题,我们需要考虑的问题就是 get 的时候在同一个 segment 中发生了 put 或 remove 操作。</p>
<ul>
<li>put 操作的线程安全性。<ol>
<li>初始化槽,这个我们之前就说过了,使用了 CAS 来初始化 Segment 中的数组。</li>
<li>添加节点到链表的操作是插入到表头的,所以,如果这个时候 get 操作在链表遍历的过程已经到了中间,是不会影响的。当然,另一个并发问题就是 get 操作在 put 之后,需要保证刚刚插入表头的节点被读取,这个依赖于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject。</li>
<li>扩容。扩容是新创建了数组,然后进行迁移数据,最后面将 newTable 设置给属性 table。所以,如果 get 操作此时也在进行,那么也没关系,如果 get 先行,那么就是在旧的 table 上做查询操作;而 put 先行,那么 put 操作的可见性保证就是 table 使用了 volatile 关键字。</li>
</ol>
</li>
<li>remove 操作的线程安全性。</li>
</ul>
<p>remove 操作我们没有分析源码,所以这里说的读者感兴趣的话还是需要到源码中去求实一下的。<br>get 操作需要遍历链表,但是 remove 操作会”破坏”链表。<br>如果 remove 破坏的节点 get 操作已经过去了,那么这里不存在任何问题。<br>如果 remove 先破坏了一个节点,分两种情况考虑。 1、如果此节点是头结点,那么需要将头结点的 next 设置为数组该位置的元素,table 虽然使用了 volatile 修饰,但是 volatile 并不能提供数组内部操作的可见性保证,所以源码中使用了 UNSAFE 来操作数组,请看方法 setEntryAt。2、如果要删除的节点不是头结点,它会将要删除节点的后继节点接到前驱节点中,这里的并发保证就是 next 属性是 volatile 的。</p>
<h2 id="2-Java8-ConcurrentHashMap"><a href="#2-Java8-ConcurrentHashMap" class="headerlink" title="2. Java8 ConcurrentHashMap"></a>2. Java8 ConcurrentHashMap</h2><p>Java7 中实现的 ConcurrentHashMap 说实话还是比较复杂的,Java8 对 ConcurrentHashMap 进行了比较大的改动。建议读者可以参考 Java8 中 HashMap 相对于 Java7 HashMap 的改动,对于 ConcurrentHashMap,Java8 也引入了红黑树。<br>说实话,Java8 ConcurrentHashMap 源码真心不简单,最难的在于扩容,数据迁移操作不容易看懂。<br>我们先用一个示意图来描述下其结构:</p>
<p><img data-src="/images/pasted-62.png" alt="upload successful"></p>
<p>结构上和 Java8 的 HashMap 基本上一样,不过它要保证线程安全性,所以在源码上确实要复杂一些。</p>
<h3 id="2-1-初始化"><a href="#2-1-初始化" class="headerlink" title="2.1. 初始化"></a>2.1. 初始化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 这构造函数里,什么都不干</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">()</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity < <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line"> <span class="keyword">int</span> cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> <span class="number">1</span>)) ?</span><br><span class="line"> MAXIMUM_CAPACITY :</span><br><span class="line"> tableSizeFor(initialCapacity + (initialCapacity >>> <span class="number">1</span>) + <span class="number">1</span>));</span><br><span class="line"> <span class="keyword">this</span>.sizeCtl = cap;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个初始化方法有点意思,通过提供初始容量,计算了 sizeCtl,sizeCtl = 【 (1.5 * initialCapacity + 1),然后向上取最近的 2 的 n 次方】。如 initialCapacity 为 10,那么得到 sizeCtl 为 16,如果 initialCapacity 为 11,得到 sizeCtl 为 32。<br>sizeCtl 这个属性使用的场景很多,不过只要跟着文章的思路来,就不会被它搞晕了。<br>如果你爱折腾,也可以看下另一个有三个参数的构造方法,这里我就不说了,大部分时候,我们会使用无参构造函数进行实例化,我们也按照这个思路来进行源码分析吧。</p>
<h3 id="2-2-put过程分析"><a href="#2-2-put过程分析" class="headerlink" title="2.2. put过程分析"></a>2.2. put过程分析</h3><p>仔细地一行一行代码看下去:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> putVal(key, value, <span class="keyword">false</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (key == <span class="keyword">null</span> || value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="comment">// 得到 hash 值</span></span><br><span class="line"> <span class="keyword">int</span> hash = spread(key.hashCode());</span><br><span class="line"> <span class="comment">// 用于记录相应链表的长度</span></span><br><span class="line"> <span class="keyword">int</span> binCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V>[] tab = table;;) {</span><br><span class="line"> Node<K,V> f; <span class="keyword">int</span> n, i, fh;</span><br><span class="line"> <span class="comment">// 如果数组"空",进行数组初始化</span></span><br><span class="line"> <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 初始化数组,后面会详细介绍</span></span><br><span class="line"> tab = initTable();</span><br><span class="line"> <span class="comment">// 找该 hash 值对应的数组下标,得到第一个节点 f</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) & hash)) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 如果数组该位置为空,</span></span><br><span class="line"> <span class="comment">// 用一次 CAS 操作将这个新值放入其中即可,这个 put 操作差不多就结束了,可以拉到最后面了</span></span><br><span class="line"> <span class="comment">// 如果 CAS 失败,那就是有并发操作,进到下一个循环就好了</span></span><br><span class="line"> <span class="keyword">if</span> (casTabAt(tab, i, <span class="keyword">null</span>,</span><br><span class="line"> <span class="keyword">new</span> Node<K,V>(hash, key, value, <span class="keyword">null</span>)))</span><br><span class="line"> <span class="keyword">break</span>; <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// hash 居然可以等于 MOVED,这个需要到后面才能看明白,不过从名字上也能猜到,肯定是因为在扩容</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line"> <span class="comment">// 帮助数据迁移,这个等到看完数据迁移部分的介绍后,再理解这个就很简单了</span></span><br><span class="line"> tab = helpTransfer(tab, f);</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// 到这里就是说,f 是该位置的头结点,而且不为空</span></span><br><span class="line"> V oldVal = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 获取数组该位置的头结点的监视器锁</span></span><br><span class="line"> <span class="keyword">synchronized</span> (f) {</span><br><span class="line"> <span class="keyword">if</span> (tabAt(tab, i) == f) {</span><br><span class="line"> <span class="keyword">if</span> (fh >= <span class="number">0</span>) { <span class="comment">// 头结点的 hash 值大于 0,说明是链表</span></span><br><span class="line"> <span class="comment">// 用于累加,记录链表的长度</span></span><br><span class="line"> binCount = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 遍历链表</span></span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> e = f;; ++binCount) {</span><br><span class="line"> K ek;</span><br><span class="line"> <span class="comment">// 如果发现了"相等"的 key,判断是否要进行值覆盖,然后也就可以 break 了</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((ek = e.key) == key ||</span><br><span class="line"> (ek != <span class="keyword">null</span> && key.equals(ek)))) {</span><br><span class="line"> oldVal = e.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> e.val = value;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 到了链表的最末端,将这个新值放到链表的最后面</span></span><br><span class="line"> Node<K,V> pred = e;</span><br><span class="line"> <span class="keyword">if</span> ((e = e.next) == <span class="keyword">null</span>) {</span><br><span class="line"> pred.next = <span class="keyword">new</span> Node<K,V>(hash, key,</span><br><span class="line"> value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) { <span class="comment">// 红黑树</span></span><br><span class="line"> Node<K,V> p;</span><br><span class="line"> binCount = <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// 调用红黑树的插值方法插入新节点</span></span><br><span class="line"> <span class="keyword">if</span> ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,</span><br><span class="line"> value)) != <span class="keyword">null</span>) {</span><br><span class="line"> oldVal = p.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> p.val = value;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (binCount != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 判断是否要将链表转换为红黑树,临界值和 HashMap 一样,也是 8</span></span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD)</span><br><span class="line"> <span class="comment">// 这个方法和 HashMap 中稍微有一点点不同,那就是它不是一定会进行红黑树转换,</span></span><br><span class="line"> <span class="comment">// 如果当前数组的长度小于 64,那么会选择进行数组扩容,而不是转换为红黑树</span></span><br><span class="line"> <span class="comment">// 具体源码我们就不看了,扩容部分后面说</span></span><br><span class="line"> treeifyBin(tab, i);</span><br><span class="line"> <span class="keyword">if</span> (oldVal != <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> oldVal;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> addCount(<span class="number">1L</span>, binCount);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>put 的主流程看完了,但是至少留下了几个问题,第一个是初始化,第二个是扩容,第三个是帮助数据迁移,这些我们都会在后面进行一一介绍。</p>
<h3 id="2-3-初始化数组:initTable"><a href="#2-3-初始化数组:initTable" class="headerlink" title="2.3. 初始化数组:initTable"></a>2.3. 初始化数组:initTable</h3><p>这个比较简单,主要就是初始化一个合适大小的数组,然后会设置 sizeCtl。<br>初始化方法中的并发问题是通过对 sizeCtl 进行一个 CAS 操作来控制的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node<K,V>[] initTable() {</span><br><span class="line"> Node<K,V>[] tab; <span class="keyword">int</span> sc;</span><br><span class="line"> <span class="keyword">while</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 初始化的"功劳"被其他线程"抢去"了</span></span><br><span class="line"> <span class="keyword">if</span> ((sc = sizeCtl) < <span class="number">0</span>)</span><br><span class="line"> Thread.yield(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line"> <span class="comment">// CAS 一下,将 sizeCtl 设置为 -1,代表抢到了锁</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// DEFAULT_CAPACITY 默认初始容量是 16</span></span><br><span class="line"> <span class="keyword">int</span> n = (sc > <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line"> <span class="comment">// 初始化数组,长度为 16 或初始化时提供的长度</span></span><br><span class="line"> Node<K,V>[] nt = (Node<K,V>[])<span class="keyword">new</span> Node<?,?>[n];</span><br><span class="line"> <span class="comment">// 将这个数组赋值给 table,table 是 volatile 的</span></span><br><span class="line"> table = tab = nt;</span><br><span class="line"> <span class="comment">// 如果 n 为 16 的话,那么这里 sc = 12</span></span><br><span class="line"> <span class="comment">// 其实就是 0.75 * n</span></span><br><span class="line"> sc = n - (n >>> <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 设置 sizeCtl 为 sc,我们就当是 12 吧</span></span><br><span class="line"> sizeCtl = sc;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> tab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-4-链表转红黑树-treeifyBin"><a href="#2-4-链表转红黑树-treeifyBin" class="headerlink" title="2.4. 链表转红黑树: treeifyBin"></a>2.4. 链表转红黑树: treeifyBin</h3><p>前面我们在 put 源码分析也说过,treeifyBin 不一定就会进行红黑树转换,也可能是仅仅做数组扩容。我们还是进行源码分析吧。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">treeifyBin</span><span class="params">(Node<K,V>[] tab, <span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> Node<K,V> b; <span class="keyword">int</span> n, sc;</span><br><span class="line"> <span class="keyword">if</span> (tab != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// MIN_TREEIFY_CAPACITY 为 64</span></span><br><span class="line"> <span class="comment">// 所以,如果数组长度小于 64 的时候,其实也就是 32 或者 16 或者更小的时候,会进行数组扩容</span></span><br><span class="line"> <span class="keyword">if</span> ((n = tab.length) < MIN_TREEIFY_CAPACITY)</span><br><span class="line"> <span class="comment">// 后面我们再详细分析这个方法</span></span><br><span class="line"> tryPresize(n << <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// b 是头结点</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((b = tabAt(tab, index)) != <span class="keyword">null</span> && b.hash >= <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 加锁</span></span><br><span class="line"> <span class="keyword">synchronized</span> (b) {</span><br><span class="line"> <span class="keyword">if</span> (tabAt(tab, index) == b) {</span><br><span class="line"> <span class="comment">// 下面就是遍历链表,建立一颗红黑树</span></span><br><span class="line"> TreeNode<K,V> hd = <span class="keyword">null</span>, tl = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> e = b; e != <span class="keyword">null</span>; e = e.next) {</span><br><span class="line"> TreeNode<K,V> p =</span><br><span class="line"> <span class="keyword">new</span> TreeNode<K,V>(e.hash, e.key, e.val,</span><br><span class="line"> <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">if</span> ((p.prev = tl) == <span class="keyword">null</span>)</span><br><span class="line"> hd = p;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tl.next = p;</span><br><span class="line"> tl = p;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将红黑树设置到数组相应位置中</span></span><br><span class="line"> setTabAt(tab, index, <span class="keyword">new</span> TreeBin<K,V>(hd));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-5-扩容:tryPresize"><a href="#2-5-扩容:tryPresize" class="headerlink" title="2.5. 扩容:tryPresize"></a>2.5. 扩容:tryPresize</h3><p>如果说 Java8 ConcurrentHashMap 的源码不简单,那么说的就是扩容操作和迁移操作。<br>这个方法要完完全全看懂还需要看之后的 transfer 方法,读者应该提前知道这点。<br>这里的扩容也是做翻倍扩容的,扩容后数组容量为原来的 2 倍。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 首先要说明的是,方法参数 size 传进来的时候就已经翻了倍了</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">tryPresize</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> <span class="comment">// c:size 的 1.5 倍,再加 1,再往上取最近的 2 的 n 次方。</span></span><br><span class="line"> <span class="keyword">int</span> c = (size >= (MAXIMUM_CAPACITY >>> <span class="number">1</span>)) ? MAXIMUM_CAPACITY :</span><br><span class="line"> tableSizeFor(size + (size >>> <span class="number">1</span>) + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">int</span> sc;</span><br><span class="line"> <span class="keyword">while</span> ((sc = sizeCtl) >= <span class="number">0</span>) {</span><br><span class="line"> Node<K,V>[] tab = table; <span class="keyword">int</span> n;</span><br><span class="line"> <span class="comment">// 这个 if 分支和之前说的初始化数组的代码基本上是一样的,在这里,我们可以不用管这块代码</span></span><br><span class="line"> <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>) {</span><br><span class="line"> n = (sc > c) ? sc : c;</span><br><span class="line"> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (table == tab) {</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> Node<K,V>[] nt = (Node<K,V>[])<span class="keyword">new</span> Node<?,?>[n];</span><br><span class="line"> table = nt;</span><br><span class="line"> sc = n - (n >>> <span class="number">2</span>); <span class="comment">// 0.75 * n</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> sizeCtl = sc;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (c <= sc || n >= MAXIMUM_CAPACITY)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (tab == table) {</span><br><span class="line"> <span class="comment">// 我没看懂 rs 的真正含义是什么,不过也关系不大</span></span><br><span class="line"> <span class="keyword">int</span> rs = resizeStamp(n);</span><br><span class="line"> <span class="keyword">if</span> (sc < <span class="number">0</span>) {</span><br><span class="line"> Node<K,V>[] nt;</span><br><span class="line"> <span class="keyword">if</span> ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="number">1</span> ||</span><br><span class="line"> sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="keyword">null</span> ||</span><br><span class="line"> transferIndex <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">// 2. 用 CAS 将 sizeCtl 加 1,然后执行 transfer 方法</span></span><br><span class="line"> <span class="comment">// 此时 nextTab 不为 null</span></span><br><span class="line"> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, sc + <span class="number">1</span>))</span><br><span class="line"> transfer(tab, nt);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 1. 将 sizeCtl 设置为 (rs << RESIZE_STAMP_SHIFT) + 2)</span></span><br><span class="line"> <span class="comment">// 我是没看懂这个值真正的意义是什么?不过可以计算出来的是,结果是一个比较大的负数</span></span><br><span class="line"> <span class="comment">// 调用 transfer 方法,此时 nextTab 参数为 null</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc,</span><br><span class="line"> (rs << RESIZE_STAMP_SHIFT) + <span class="number">2</span>))</span><br><span class="line"> transfer(tab, <span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法的核心在于 sizeCtl 值的操作,首先将其设置为一个负数,然后执行 transfer(tab, null),再下一个循环将 sizeCtl 加 1,并执行 transfer(tab, nt),之后可能是继续 sizeCtl 加 1,并执行 transfer(tab, nt)。<br>所以,可能的操作就是执行 1 次 transfer(tab, null) + 多次 transfer(tab, nt),这里怎么结束循环的需要看完 transfer 源码才清楚。</p>
<h3 id="2-6-数据迁移:transfer"><a href="#2-6-数据迁移:transfer" class="headerlink" title="2.6. 数据迁移:transfer"></a>2.6. 数据迁移:transfer</h3><p>下面这个方法很点长,将原来的 tab 数组的元素迁移到新的 nextTab 数组中。<br>虽然我们之前说的 tryPresize 方法中多次调用 transfer 不涉及多线程,但是这个 transfer 方法可以在其他地方被调用,典型地,我们之前在说 put 方法的时候就说过了,请往上看 put 方法,是不是有个地方调用了 helpTransfer 方法,helpTransfer 方法会调用 transfer 方法的。<br>此方法支持多线程执行,外围调用此方法的时候,会保证第一个发起数据迁移的线程,nextTab 参数为 null,之后再调用此方法的时候,nextTab 不会为 null。<br>阅读源码之前,先要理解并发操作的机制。原数组长度为 n,所以我们有 n 个迁移任务,让每个线程每次负责一个小任务是最简单的,每做完一个任务再检测是否有其他没做完的任务,帮助迁移就可以了,而 Doug Lea 使用了一个 stride,简单理解就是步长,每个线程每次负责迁移其中的一部分,如每次迁移 16 个小任务。所以,我们就需要一个全局的调度者来安排哪个线程执行哪几个任务,这个就是属性 transferIndex 的作用。<br>第一个发起数据迁移的线程会将 transferIndex 指向原数组最后的位置,然后从后往前的 stride 个任务属于第一个线程,然后将 transferIndex 指向新的位置,再往前的 stride 个任务属于第二个线程,依此类推。当然,这里说的第二个线程不是真的一定指代了第二个线程,也可以是同一个线程,这个读者应该能理解吧。其实就是将一个大的迁移任务分为了一个个任务包。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Node<K,V>[] tab, Node<K,V>[] nextTab)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> n = tab.length, stride;</span><br><span class="line"> <span class="comment">// stride 在单核下直接等于 n,多核模式下为 (n>>>3)/NCPU,最小值是 16</span></span><br><span class="line"> <span class="comment">// stride 可以理解为”步长“,有 n 个位置是需要进行迁移的,</span></span><br><span class="line"> <span class="comment">// 将这 n 个任务分为多个任务包,每个任务包有 stride 个任务</span></span><br><span class="line"> <span class="keyword">if</span> ((stride = (NCPU > <span class="number">1</span>) ? (n >>> <span class="number">3</span>) / NCPU : n) < MIN_TRANSFER_STRIDE)</span><br><span class="line"> stride = MIN_TRANSFER_STRIDE; <span class="comment">// subdivide range</span></span><br><span class="line"> <span class="comment">// 如果 nextTab 为 null,先进行一次初始化</span></span><br><span class="line"> <span class="comment">// 前面我们说了,外围会保证第一个发起迁移的线程调用此方法时,参数 nextTab 为 null</span></span><br><span class="line"> <span class="comment">// 之后参与迁移的线程调用此方法时,nextTab 不会为 null</span></span><br><span class="line"> <span class="keyword">if</span> (nextTab == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 容量翻倍</span></span><br><span class="line"> Node<K,V>[] nt = (Node<K,V>[])<span class="keyword">new</span> Node<?,?>[n << <span class="number">1</span>];</span><br><span class="line"> nextTab = nt;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable ex) { <span class="comment">// try to cope with OOME</span></span><br><span class="line"> sizeCtl = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// nextTable 是 ConcurrentHashMap 中的属性</span></span><br><span class="line"> nextTable = nextTab;</span><br><span class="line"> <span class="comment">// transferIndex 也是 ConcurrentHashMap 的属性,用于控制迁移的位置</span></span><br><span class="line"> transferIndex = n;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">int</span> nextn = nextTab.length;</span><br><span class="line"> <span class="comment">// ForwardingNode 翻译过来就是正在被迁移的 Node</span></span><br><span class="line"> <span class="comment">// 这个构造方法会生成一个Node,key、value 和 next 都为 null,关键是 hash 为 MOVED</span></span><br><span class="line"> <span class="comment">// 后面我们会看到,原数组中位置 i 处的节点完成迁移工作后,</span></span><br><span class="line"> <span class="comment">// 就会将位置 i 处设置为这个 ForwardingNode,用来告诉其他线程该位置已经处理过了</span></span><br><span class="line"> <span class="comment">// 所以它其实相当于是一个标志。</span></span><br><span class="line"> ForwardingNode<K,V> fwd = <span class="keyword">new</span> ForwardingNode<K,V>(nextTab);</span><br><span class="line"> <span class="comment">// advance 指的是做完了一个位置的迁移工作,可以准备做下一个位置的了</span></span><br><span class="line"> <span class="keyword">boolean</span> advance = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">boolean</span> finishing = <span class="keyword">false</span>; <span class="comment">// to ensure sweep before committing nextTab</span></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 下面这个 for 循环,最难理解的在前面,而要看懂它们,应该先看懂后面的,然后再倒回来看</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="comment">// i 是位置索引,bound 是边界,注意是从后往前</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, bound = <span class="number">0</span>;;) {</span><br><span class="line"> Node<K,V> f; <span class="keyword">int</span> fh;</span><br><span class="line"> <span class="comment">// 下面这个 while 真的是不好理解</span></span><br><span class="line"> <span class="comment">// advance 为 true 表示可以进行下一个位置的迁移了</span></span><br><span class="line"> <span class="comment">// 简单理解结局:i 指向了 transferIndex,bound 指向了 transferIndex-stride</span></span><br><span class="line"> <span class="keyword">while</span> (advance) {</span><br><span class="line"> <span class="keyword">int</span> nextIndex, nextBound;</span><br><span class="line"> <span class="keyword">if</span> (--i >= bound || finishing)</span><br><span class="line"> advance = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// 将 transferIndex 值赋给 nextIndex</span></span><br><span class="line"> <span class="comment">// 这里 transferIndex 一旦小于等于 0,说明原数组的所有位置都有相应的线程去处理了</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((nextIndex = transferIndex) <= <span class="number">0</span>) {</span><br><span class="line"> i = -<span class="number">1</span>;</span><br><span class="line"> advance = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt</span><br><span class="line"> (<span class="keyword">this</span>, TRANSFERINDEX, nextIndex,</span><br><span class="line"> nextBound = (nextIndex > stride ?</span><br><span class="line"> nextIndex - stride : <span class="number">0</span>))) {</span><br><span class="line"> <span class="comment">// 看括号中的代码,nextBound 是这次迁移任务的边界,注意,是从后往前</span></span><br><span class="line"> bound = nextBound;</span><br><span class="line"> i = nextIndex - <span class="number">1</span>;</span><br><span class="line"> advance = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (i < <span class="number">0</span> || i >= n || i + n >= nextn) {</span><br><span class="line"> <span class="keyword">int</span> sc;</span><br><span class="line"> <span class="keyword">if</span> (finishing) {</span><br><span class="line"> <span class="comment">// 所有的迁移操作已经完成</span></span><br><span class="line"> nextTable = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 将新的 nextTab 赋值给 table 属性,完成迁移</span></span><br><span class="line"> table = nextTab;</span><br><span class="line"> <span class="comment">// 重新计算 sizeCtl:n 是原数组长度,所以 sizeCtl 得出的值将是新数组长度的 0.75 倍</span></span><br><span class="line"> sizeCtl = (n << <span class="number">1</span>) - (n >>> <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 之前我们说过,sizeCtl 在迁移前会设置为 (rs << RESIZE_STAMP_SHIFT) + 2</span></span><br><span class="line"> <span class="comment">// 然后,每有一个线程参与迁移就会将 sizeCtl 加 1,</span></span><br><span class="line"> <span class="comment">// 这里使用 CAS 操作对 sizeCtl 进行减 1,代表做完了属于自己的任务</span></span><br><span class="line"> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc = sizeCtl, sc - <span class="number">1</span>)) {</span><br><span class="line"> <span class="comment">// 任务结束,方法退出</span></span><br><span class="line"> <span class="keyword">if</span> ((sc - <span class="number">2</span>) != resizeStamp(n) << RESIZE_STAMP_SHIFT)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> <span class="comment">// 到这里,说明 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT,</span></span><br><span class="line"> <span class="comment">// 也就是说,所有的迁移任务都做完了,也就会进入到上面的 if(finishing){} 分支了</span></span><br><span class="line"> finishing = advance = <span class="keyword">true</span>;</span><br><span class="line"> i = n; <span class="comment">// recheck before commit</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果位置 i 处是空的,没有任何节点,那么放入刚刚初始化的 ForwardingNode ”空节点“</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i)) == <span class="keyword">null</span>)</span><br><span class="line"> advance = casTabAt(tab, i, <span class="keyword">null</span>, fwd);</span><br><span class="line"> <span class="comment">// 该位置处是一个 ForwardingNode,代表该位置已经迁移过了</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line"> advance = <span class="keyword">true</span>; <span class="comment">// already processed</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 对数组该位置处的结点加锁,开始处理数组该位置处的迁移工作</span></span><br><span class="line"> <span class="keyword">synchronized</span> (f) {</span><br><span class="line"> <span class="keyword">if</span> (tabAt(tab, i) == f) {</span><br><span class="line"> Node<K,V> ln, hn;</span><br><span class="line"> <span class="comment">// 头结点的 hash 大于 0,说明是链表的 Node 节点</span></span><br><span class="line"> <span class="keyword">if</span> (fh >= <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 下面这一块和 Java7 中的 ConcurrentHashMap 迁移是差不多的,</span></span><br><span class="line"> <span class="comment">// 需要将链表一分为二,</span></span><br><span class="line"> <span class="comment">// 找到原链表中的 lastRun,然后 lastRun 及其之后的节点是一起进行迁移的</span></span><br><span class="line"> <span class="comment">// lastRun 之前的节点需要进行克隆,然后分到两个链表中</span></span><br><span class="line"> <span class="keyword">int</span> runBit = fh & n;</span><br><span class="line"> Node<K,V> lastRun = f;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> p = f.next; p != <span class="keyword">null</span>; p = p.next) {</span><br><span class="line"> <span class="keyword">int</span> b = p.hash & n;</span><br><span class="line"> <span class="keyword">if</span> (b != runBit) {</span><br><span class="line"> runBit = b;</span><br><span class="line"> lastRun = p;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (runBit == <span class="number">0</span>) {</span><br><span class="line"> ln = lastRun;</span><br><span class="line"> hn = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> hn = lastRun;</span><br><span class="line"> ln = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> p = f; p != lastRun; p = p.next) {</span><br><span class="line"> <span class="keyword">int</span> ph = p.hash; K pk = p.key; V pv = p.val;</span><br><span class="line"> <span class="keyword">if</span> ((ph & n) == <span class="number">0</span>)</span><br><span class="line"> ln = <span class="keyword">new</span> Node<K,V>(ph, pk, pv, ln);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hn = <span class="keyword">new</span> Node<K,V>(ph, pk, pv, hn);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 其中的一个链表放在新数组的位置 i</span></span><br><span class="line"> setTabAt(nextTab, i, ln);</span><br><span class="line"> <span class="comment">// 另一个链表放在新数组的位置 i+n</span></span><br><span class="line"> setTabAt(nextTab, i + n, hn);</span><br><span class="line"> <span class="comment">// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,</span></span><br><span class="line"> <span class="comment">// 其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了</span></span><br><span class="line"> setTabAt(tab, i, fwd);</span><br><span class="line"> <span class="comment">// advance 设置为 true,代表该位置已经迁移完毕</span></span><br><span class="line"> advance = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) {</span><br><span class="line"> <span class="comment">// 红黑树的迁移</span></span><br><span class="line"> TreeBin<K,V> t = (TreeBin<K,V>)f;</span><br><span class="line"> TreeNode<K,V> lo = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line"> TreeNode<K,V> hi = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> lc = <span class="number">0</span>, hc = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> e = t.first; e != <span class="keyword">null</span>; e = e.next) {</span><br><span class="line"> <span class="keyword">int</span> h = e.hash;</span><br><span class="line"> TreeNode<K,V> p = <span class="keyword">new</span> TreeNode<K,V></span><br><span class="line"> (h, e.key, e.val, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">if</span> ((h & n) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> ((p.prev = loTail) == <span class="keyword">null</span>)</span><br><span class="line"> lo = p;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> loTail.next = p;</span><br><span class="line"> loTail = p;</span><br><span class="line"> ++lc;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> ((p.prev = hiTail) == <span class="keyword">null</span>)</span><br><span class="line"> hi = p;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hiTail.next = p;</span><br><span class="line"> hiTail = p;</span><br><span class="line"> ++hc;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果一分为二后,节点数少于 8,那么将红黑树转换回链表</span></span><br><span class="line"> ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :</span><br><span class="line"> (hc != <span class="number">0</span>) ? <span class="keyword">new</span> TreeBin<K,V>(lo) : t;</span><br><span class="line"> hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :</span><br><span class="line"> (lc != <span class="number">0</span>) ? <span class="keyword">new</span> TreeBin<K,V>(hi) : t;</span><br><span class="line"> <span class="comment">// 将 ln 放置在新数组的位置 i</span></span><br><span class="line"> setTabAt(nextTab, i, ln);</span><br><span class="line"> <span class="comment">// 将 hn 放置在新数组的位置 i+n</span></span><br><span class="line"> setTabAt(nextTab, i + n, hn);</span><br><span class="line"> <span class="comment">// 将原数组该位置处设置为 fwd,代表该位置已经处理完毕,</span></span><br><span class="line"> <span class="comment">// 其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了</span></span><br><span class="line"> setTabAt(tab, i, fwd);</span><br><span class="line"> <span class="comment">// advance 设置为 true,代表该位置已经迁移完毕</span></span><br><span class="line"> advance = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>说到底,transfer 这个方法并没有实现所有的迁移任务,每次调用这个方法只实现了 transferIndex 往前 stride 个位置的迁移工作,其他的需要由外围来控制。<br>这个时候,再回去仔细看 tryPresize 方法可能就会更加清晰一些了。</p>
<h3 id="2-7-get过程分析"><a href="#2-7-get过程分析" class="headerlink" title="2.7. get过程分析"></a>2.7. get过程分析</h3><p>get 方法从来都是最简单的,这里也不例外:</p>
<ol>
<li>计算 hash 值</li>
<li>根据 hash 值找到数组对应位置: (n - 1) & h</li>
<li>根据该位置处结点性质进行相应查找<ul>
<li>如果该位置为 null,那么直接返回 null 就可以了</li>
<li>如果该位置处的节点刚好就是我们需要的,返回该节点的值即可</li>
<li>如果该位置节点的 hash 值小于 0,说明正在扩容,或者是红黑树,后面我们再介绍 find 方法</li>
<li>如果以上 3 条都不满足,那就是链表,进行遍历比对即可</li>
</ul>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> e, p; <span class="keyword">int</span> n, eh; K ek;</span><br><span class="line"> <span class="keyword">int</span> h = spread(key.hashCode());</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (e = tabAt(tab, (n - <span class="number">1</span>) & h)) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 判断头结点是否就是我们需要的节点</span></span><br><span class="line"> <span class="keyword">if</span> ((eh = e.hash) == h) {</span><br><span class="line"> <span class="keyword">if</span> ((ek = e.key) == key || (ek != <span class="keyword">null</span> && key.equals(ek)))</span><br><span class="line"> <span class="keyword">return</span> e.val;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果头结点的 hash 小于 0,说明 正在扩容,或者该位置是红黑树</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (eh < <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 参考 ForwardingNode.find(int h, Object k) 和 TreeBin.find(int h, Object k)</span></span><br><span class="line"> <span class="keyword">return</span> (p = e.find(h, key)) != <span class="keyword">null</span> ? p.val : <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 遍历链表</span></span><br><span class="line"> <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e.hash == h &&</span><br><span class="line"> ((ek = e.key) == key || (ek != <span class="keyword">null</span> && key.equals(ek))))</span><br><span class="line"> <span class="keyword">return</span> e.val;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>简单说一句,此方法的大部分内容都很简单,只有正好碰到扩容的情况,ForwardingNode.find(int h, Object k) 稍微复杂一些,不过在了解了数据迁移的过程后,这个也就不难了,所以限于篇幅这里也不展开说了。</p>
<blockquote>
<p><strong>原文:</strong><a href="https://www.cnblogs.com/jajian/p/10385377.html" target="_blank" rel="noopener">https://www.cnblogs.com/jajian/p/10385377.html</a></p>
</blockquote>
]]></content>
<categories>
<category>源码分析</category>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Map</tag>
<tag>Concurrency</tag>
</tags>
</entry>
<entry>
<title>Java7和Java8中的HashMap原理解析</title>
<url>/posts/9cb319e/</url>
<content><![CDATA[<p>HashMap可能是面试的时候必问的题目了,面试官为什么都偏爱拿这个问应聘者?因为HashMap它的设计结构和原理比较有意思,它既可以考初学者对Java集合的了解又可以深度的发现应聘者的数据结构功底。<br>阅读前提:本文分析的是源码,所以至少读者要熟悉它们的接口使用,同时,对于并发,读者至少要知道CAS、ReentrantLock、Unsafe操作这几个基本的知识,文中不会对这些知识进行介绍。Java8用到了红黑树,不过本文不会进行展开,感兴趣的读者请自行查找相关资料。</p>
<h2 id="1-Java7中HashMap"><a href="#1-Java7中HashMap" class="headerlink" title="1. Java7中HashMap"></a>1. Java7中HashMap</h2><p>HashMap是最简单的,一来我们非常熟悉,二来就是它不支持并发操作,所以源码也非常简单。<br>首先,我们用下面这张图来介绍HashMap的结构。</p>
<p><img data-src="/images/pasted-63.png" alt="upload successful"></p>
<p>大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。为什么是这种的结构,这涉及到数据结构方面的知识了。</p>
<h3 id="1-1-HashMap的数据结构"><a href="#1-1-HashMap的数据结构" class="headerlink" title="1.1. HashMap的数据结构"></a>1.1. HashMap的数据结构</h3><p>数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。<br><strong>数组</strong><br>数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;<br><strong>链表</strong><br>链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,为O(N)。<br>链表的特点是:寻址困难,插入和删除容易。<br><strong>哈希表</strong><br>那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表(Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。<br>哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— <strong>拉链法</strong>,我们可以理解为<strong>“链表的数组”</strong>,如图:</p>
<p><img data-src="/images/pasted-64.png" alt="upload successful"></p>
<p>当添加数据的时候,整个结构大致如下:</p>
<p><img data-src="/images/pasted-65.png" alt="upload successful"></p>
<p>从上图我们可以发现哈希表是由<code>数组+链表</code>组成的,一个长度为16的数组中,每个数组中元素存储的是一个链表的头结点。<br>那么这些元素是按照什么样的规则存储到数组中呢。一般情况我们首先想到的就是元素的 key 的哈希值对数组长度取模得到( <code>hash(key)%(length -1)</code>),这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式那?Java中是这样做的:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexFor</span><span class="params">(<span class="keyword">int</span> h, <span class="keyword">int</span> length)</span> </span>{ </span><br><span class="line"> <span class="keyword">return</span> h & (length-<span class="number">1</span>); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们知道每个数据对象的hash对应唯一一个值,但是一个hash值不一定对应唯一的数据对象。如果两个不同对象的 hashCode 相同,此情况即称为<strong>哈希冲突</strong>。<br>比如上述HashMap中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置,然后依次放在数组中该位置的链表上。</p>
<blockquote>
<p><strong>注意:</strong><br>对于那些hash冲突的数据,最新(最后)put的值放在链表的头部,为什么这样做呢?因为我们程序设计中认为最新放进去的值它的使用率会更高些,放在链表头比较容易查询获取到。</p>
</blockquote>
<p>HashMap里面实现一个静态内部类Entry,Entry包含四个属性:key,value,hash值和用于单向链表的 next。从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。上图中,每个绿色的实体是嵌套类Entry的实例。</p>
<ul>
<li><strong>capacity</strong>:当前数组容量,始终保持 <code>2^n</code>,可以扩容,扩容后数组大小为当前的2倍。</li>
<li><strong>loadFactor</strong>:负载因子,默认为<code>0.75</code>。</li>
<li><strong>threshold</strong>:扩容的阈值,等于 <code>capacity * loadFactor</code>。</li>
</ul>
<blockquote>
<p><strong>注意问题:</strong><br>1、<strong>扩容的数组的长度为什么保持 2^n?</strong><br>其实这是为了保证通过hash方式获取下标的时候分布均匀。数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。<br>2、<strong>为什么负载因子的值默认为 0.75?</strong><br>加载因子是表示Hash表中元素的填满的程度。<br>加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。<br>反之,加载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了。<br>冲突的机会越大,则查找的成本越高。反之,查找的成本越小。<br>因此,必须在 “冲突的机会”与”空间利用率”之间寻找一种平衡与折衷。</p>
</blockquote>
<h3 id="1-2-put过程分析"><a href="#1-2-put过程分析" class="headerlink" title="1.2. put过程分析"></a>1.2. put过程分析</h3><p>还是比较简单的,跟着代码走一遍吧。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="comment">// 当插入第一个元素的时候,需要先初始化数组大小</span></span><br><span class="line"> <span class="keyword">if</span> (table == EMPTY_TABLE) {</span><br><span class="line"> inflateTable(threshold);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果 key 为 null,感兴趣的可以往里看,最终会将这个 entry 放到 table[0] 中</span></span><br><span class="line"> <span class="keyword">if</span> (key == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> putForNullKey(value);</span><br><span class="line"> <span class="comment">// 1. 求 key 的 hash 值</span></span><br><span class="line"> <span class="keyword">int</span> hash = hash(key);</span><br><span class="line"> <span class="comment">// 2. 找到对应的数组下标</span></span><br><span class="line"> <span class="keyword">int</span> i = indexFor(hash, table.length);</span><br><span class="line"> <span class="comment">// 3. 遍历一下对应下标处的链表,看是否有重复的 key 已经存在,</span></span><br><span class="line"> <span class="comment">// 如果有,直接覆盖,put 方法返回旧值就结束了</span></span><br><span class="line"> <span class="keyword">for</span> (Entry<K,V> e = table[i]; e != <span class="keyword">null</span>; e = e.next) {</span><br><span class="line"> Object k;</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash && ((k = e.key) == key || key.equals(k))) {</span><br><span class="line"> V oldValue = e.value;</span><br><span class="line"> e.value = value;</span><br><span class="line"> e.recordAccess(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="comment">// 4. 不存在重复的 key,将此 entry 添加到链表中,细节后面说</span></span><br><span class="line"> addEntry(hash, key, value, i);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="1-3-数组初始化-inflateTable"><a href="#1-3-数组初始化-inflateTable" class="headerlink" title="1.3. 数组初始化(inflateTable)"></a>1.3. 数组初始化(inflateTable)</h3><p>在第一个元素插入HashMap的时候做一次数组的初始化,就是先确定初始的数组大小,并计算数组扩容的阈值。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">inflateTable</span><span class="params">(<span class="keyword">int</span> toSize)</span> </span>{</span><br><span class="line"> <span class="comment">// 保证数组大小一定是 2 的 n 次方。</span></span><br><span class="line"> <span class="comment">// 比如这样初始化:new HashMap(20),那么处理成初始数组大小是 32</span></span><br><span class="line"> <span class="keyword">int</span> capacity = roundUpToPowerOf2(toSize);</span><br><span class="line"> <span class="comment">// 计算扩容阈值:capacity * loadFactor</span></span><br><span class="line"> threshold = (<span class="keyword">int</span>) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 算是初始化数组吧</span></span><br><span class="line"> table = <span class="keyword">new</span> Entry[capacity];</span><br><span class="line"> initHashSeedAsNeeded(capacity); <span class="comment">//ignore</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里有一个将数组大小保持为2的n次方的做法,Java7和Java8的HashMap和ConcurrentHashMap都有相应的要求,只不过实现的代码稍微有些不同,后面再看到的时候就知道了。</p>
<h3 id="1-4-计算具体数组位置-indexFor"><a href="#1-4-计算具体数组位置-indexFor" class="headerlink" title="1.4. 计算具体数组位置(indexFor)"></a>1.4. 计算具体数组位置(indexFor)</h3><p>这个简单,我们自己也能YY一个:使用key的hash值对数组长度进行取模就可以了。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexFor</span><span class="params">(<span class="keyword">int</span> hash, <span class="keyword">int</span> length)</span> </span>{</span><br><span class="line"> <span class="comment">// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";</span></span><br><span class="line"> <span class="keyword">return</span> hash & (length-<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法很简单,简单说就是取hash值的低n位。如在数组长度为32的时候,其实取的就是key的hash值的低5位,作为它在数组中的下标位置。</p>
<h3 id="1-5-添加节点到链表中-addEntry"><a href="#1-5-添加节点到链表中-addEntry" class="headerlink" title="1.5. 添加节点到链表中(addEntry)"></a>1.5. 添加节点到链表中(addEntry)</h3><p>找到数组下标后,会先进行key判重,如果没有重复,就准备将新值放入到链表的表头。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果当前 HashMap 大小已经达到了阈值,并且新值要插入的数组位置已经有元素了,那么要扩容</span></span><br><span class="line"> <span class="keyword">if</span> ((size >= threshold) && (<span class="keyword">null</span> != table[bucketIndex])) {</span><br><span class="line"> <span class="comment">// 扩容,后面会介绍一下</span></span><br><span class="line"> resize(<span class="number">2</span> * table.length);</span><br><span class="line"> <span class="comment">// 扩容以后,重新计算 hash 值</span></span><br><span class="line"> hash = (<span class="keyword">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 重新计算扩容后的新的下标</span></span><br><span class="line"> bucketIndex = indexFor(hash, table.length);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 往下看</span></span><br><span class="line"> createEntry(hash, key, value, bucketIndex);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 这个很简单,其实就是将新值放到链表的表头,然后 size++</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">createEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>{</span><br><span class="line"> Entry<K,V> e = table[bucketIndex];</span><br><span class="line"> table[bucketIndex] = <span class="keyword">new</span> Entry<>(hash, key, value, e);</span><br><span class="line"> size++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个方法的主要逻辑就是先判断是否需要扩容,需要的话先扩容,然后再将这个新的数据插入到扩容后的数组的相应位置处的链表的表头。</p>
<h3 id="1-6-数组扩容-resize"><a href="#1-6-数组扩容-resize" class="headerlink" title="1.6. 数组扩容(resize)"></a>1.6. 数组扩容(resize)</h3><p>前面我们看到,在插入新值的时候,如果当前的size已经达到了阈值,并且要插入的数组位置上已经有元素,那么就会触发扩容,扩容后,数组大小为原来的2倍。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">resize</span><span class="params">(<span class="keyword">int</span> newCapacity)</span> </span>{</span><br><span class="line"> Entry[] oldTable = table;</span><br><span class="line"> <span class="keyword">int</span> oldCapacity = oldTable.length;</span><br><span class="line"> <span class="keyword">if</span> (oldCapacity == MAXIMUM_CAPACITY) {</span><br><span class="line"> threshold = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 新的数组</span></span><br><span class="line"> Entry[] newTable = <span class="keyword">new</span> Entry[newCapacity];</span><br><span class="line"> <span class="comment">// 将原来数组中的值迁移到新的更大的数组中</span></span><br><span class="line"> transfer(newTable, initHashSeedAsNeeded(newCapacity));</span><br><span class="line"> table = newTable;</span><br><span class="line"> threshold = (<span class="keyword">int</span>)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值迁移到新的数组中。<br>由于是双倍扩容,迁移过程中,会将原来table[i]中的链表的所有节点,分拆到新的数组的 <code>newTable[i]</code>和 <code>newTable[i + oldLength]</code> 位置上。如原来数组长度是16,那么扩容后,原来 table[0] 处的链表中的所有元素会被分配到新数组中 <code>newTable[0]</code> 和 <code>newTable[16]</code> 这两个位置。代码比较简单,这里就不展开了。</p>
<h3 id="1-7-get过程分析"><a href="#1-7-get过程分析" class="headerlink" title="1.7. get过程分析"></a>1.7. get过程分析</h3><p>相对于put过程,get过程是非常简单的。</p>
<ul>
<li>根据key计算hash值。</li>
<li>找到相应的数组下标:<code>hash & (length - 1)</code>。</li>
<li>遍历该数组位置处的链表,直到找到相等(==或equals)的key。</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> <span class="comment">// 之前说过,key 为 null 的话,会被放到 table[0],所以只要遍历下 table[0] 处的链表就可以了</span></span><br><span class="line"> <span class="keyword">if</span> (key == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> getForNullKey();</span><br><span class="line"> <span class="comment">// </span></span><br><span class="line"> Entry<K,V> entry = getEntry(key);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span> == entry ? <span class="keyword">null</span> : entry.getValue();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>getEntry(key):</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> Entry<K,V> <span class="title">getEntry</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (size == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">int</span> hash = (key == <span class="keyword">null</span>) ? <span class="number">0</span> : hash(key);</span><br><span class="line"> <span class="comment">// 确定数组下标,然后从头开始遍历链表,直到找到为止</span></span><br><span class="line"> <span class="keyword">for</span> (Entry<K,V> e = table[indexFor(hash, table.length)];</span><br><span class="line"> e != <span class="keyword">null</span>;</span><br><span class="line"> e = e.next) {</span><br><span class="line"> Object k;</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2-Java8中HashMap"><a href="#2-Java8中HashMap" class="headerlink" title="2. Java8中HashMap"></a>2. Java8中HashMap</h2><p>Java8对HashMap进行了一些修改,最大的不同就是利用了红黑树,所以其由 <code>数组+链表+红黑树 组成</code>。<br>根据Java7HashMap的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为O(n)。<br><strong>为了降低这部分的开销,在Java8中,当链表中的元素达到了8个时,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为O(logN)。</strong><br>来一张图简单示意一下吧:</p>
<p><img data-src="/images/pasted-66.png" alt="upload successful"></p>
<blockquote>
<p>注意,上图是示意图,主要是描述结构,不会达到这个状态的,因为这么多数据的时候早就扩容了。</p>
</blockquote>
<p>下面,我们还是用代码来介绍吧,个人感觉,Java8的源码可读性要差一些,不过精简一些。<br>Java7中使用Entry来代表每个HashMap中的数据节点,Java8中使用Node,基本没有区别,都是key,value,hash 和 next这四个属性,不过,Node只能用于链表的情况,红黑树的情况需要使用TreeNode。<br>我们根据数组元素中,第一个节点数据类型是Node还是TreeNode来判断该位置下是链表还是红黑树的。</p>
<h3 id="2-1-put过程分析"><a href="#2-1-put过程分析" class="headerlink" title="2.1. put过程分析"></a>2.1. put过程分析</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 第三个参数 onlyIfAbsent 如果是 true,那么只有在不存在该 key 时才会进行 put 操作</span></span><br><span class="line"><span class="comment">// 第四个参数 evict 我们这里不关心</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">boolean</span> onlyIfAbsent,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">boolean</span> evict)</span> </span>{</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="keyword">int</span> n, i;</span><br><span class="line"> <span class="comment">// 第一次 put 值的时候,会触发下面的 resize(),类似 java7 的第一次 put 也要初始化数组长度</span></span><br><span class="line"> <span class="comment">// 第一次 resize 和后续的扩容有些不一样,因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量</span></span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> n = (tab = resize()).length;</span><br><span class="line"> <span class="comment">// 找到具体的数组下标,如果此位置没有值,那么直接初始化一下 Node 并放置在这个位置就可以了</span></span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="keyword">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">else</span> {<span class="comment">// 数组该位置有数据</span></span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> <span class="comment">// 首先,判断该位置的第一个数据和我们要插入的数据,key 是不是"相等",如果是,取出这个节点</span></span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> e = p;</span><br><span class="line"> <span class="comment">// 如果该节点是代表红黑树的节点,调用红黑树的插值方法,本文不展开说红黑树</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 到这里,说明数组该位置上是一个链表</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> binCount = <span class="number">0</span>; ; ++binCount) {</span><br><span class="line"> <span class="comment">// 插入到链表的最后面(Java7 是插入到链表的最前面)</span></span><br><span class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) {</span><br><span class="line"> p.next = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个</span></span><br><span class="line"> <span class="comment">// 会触发下面的 treeifyBin,也就是将链表转换为红黑树</span></span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line"> treeifyBin(tab, hash);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果在该链表中找到了"相等"的 key(== 或 equals)</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="comment">// 此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 node</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> p = e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// e!=null 说明存在旧值的key与要插入的key"相等"</span></span><br><span class="line"> <span class="comment">// 对于我们分析的put操作,下面这个 if 其实就是进行 "值覆盖",然后返回旧值</span></span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> V oldValue = e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ++modCount;</span><br><span class="line"> <span class="comment">// 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容</span></span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize();</span><br><span class="line"> afterNodeInsertion(evict);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>和 Java7 稍微有点不一样的地方就是,Java7 是先扩容后插入新值的,Java8 先插值再扩容,不过这个不重要。</p>
<h3 id="2-2-数组扩容"><a href="#2-2-数组扩容" class="headerlink" title="2.2. 数组扩容"></a>2.2. 数组扩容</h3><p>resize()方法用于初始化数组或数组扩容,每次扩容后,容量为原来的2倍,并进行数据迁移。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> Node<K,V>[] resize() {</span><br><span class="line"> Node<K,V>[] oldTab = table;</span><br><span class="line"> <span class="keyword">int</span> oldCap = (oldTab == <span class="keyword">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line"> <span class="keyword">int</span> oldThr = threshold;</span><br><span class="line"> <span class="keyword">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (oldCap > <span class="number">0</span>) { <span class="comment">// 对应数组扩容</span></span><br><span class="line"> <span class="keyword">if</span> (oldCap >= MAXIMUM_CAPACITY) {</span><br><span class="line"> threshold = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span> oldTab;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将数组大小扩大一倍</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap << <span class="number">1</span>) < MAXIMUM_CAPACITY &&</span><br><span class="line"> oldCap >= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line"> <span class="comment">// 将阈值扩大一倍</span></span><br><span class="line"> newThr = oldThr << <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>) <span class="comment">// 对应使用 new HashMap(int initialCapacity) 初始化后,第一次 put 的时候</span></span><br><span class="line"> newCap = oldThr;</span><br><span class="line"> <span class="keyword">else</span> {<span class="comment">// 对应使用 new HashMap() 初始化后,第一次 put 的时候</span></span><br><span class="line"> newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line"> newThr = (<span class="keyword">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (newThr == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">float</span> ft = (<span class="keyword">float</span>)newCap * loadFactor;</span><br><span class="line"> newThr = (newCap < MAXIMUM_CAPACITY && ft < (<span class="keyword">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line"> (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"> threshold = newThr;</span><br><span class="line"> <span class="comment">// 用新的数组大小初始化新的数组</span></span><br><span class="line"> Node<K,V>[] newTab = (Node<K,V>[])<span class="keyword">new</span> Node[newCap];</span><br><span class="line"> table = newTab; <span class="comment">// 如果是初始化数组,到这里就结束了,返回 newTab 即可</span></span><br><span class="line"> <span class="keyword">if</span> (oldTab != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 开始遍历原数组,进行数据迁移。</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < oldCap; ++j) {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">if</span> ((e = oldTab[j]) != <span class="keyword">null</span>) {</span><br><span class="line"> oldTab[j] = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 如果该数组位置上只有单个元素,那就简单了,简单迁移这个元素就可以了</span></span><br><span class="line"> <span class="keyword">if</span> (e.next == <span class="keyword">null</span>)</span><br><span class="line"> newTab[e.hash & (newCap - <span class="number">1</span>)] = e;</span><br><span class="line"> <span class="comment">// 如果是红黑树,具体我们就不展开了</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> ((TreeNode<K,V>)e).split(<span class="keyword">this</span>, newTab, j, oldCap);</span><br><span class="line"> <span class="keyword">else</span> { </span><br><span class="line"> <span class="comment">// 这块是处理链表的情况,</span></span><br><span class="line"> <span class="comment">// 需要将此链表拆成两个链表,放到新的数组中,并且保留原来的先后顺序</span></span><br><span class="line"> <span class="comment">// loHead、loTail 对应一条链表,hiHead、hiTail 对应另一条链表,代码还是比较简单的</span></span><br><span class="line"> Node<K,V> loHead = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> hiHead = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> next;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> next = e.next;</span><br><span class="line"> <span class="keyword">if</span> ((e.hash & oldCap) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (loTail == <span class="keyword">null</span>)</span><br><span class="line"> loHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> loTail.next = e;</span><br><span class="line"> loTail = e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (hiTail == <span class="keyword">null</span>)</span><br><span class="line"> hiHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hiTail.next = e;</span><br><span class="line"> hiTail = e;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> ((e = next) != <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">if</span> (loTail != <span class="keyword">null</span>) {</span><br><span class="line"> loTail.next = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 第一条链表</span></span><br><span class="line"> newTab[j] = loHead;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (hiTail != <span class="keyword">null</span>) {</span><br><span class="line"> hiTail.next = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 第二条链表的新的位置是 j + oldCap,这个很好理解</span></span><br><span class="line"> newTab[j + oldCap] = hiHead;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newTab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-3-get过程分析"><a href="#2-3-get过程分析" class="headerlink" title="2.3. get过程分析"></a>2.3. get过程分析</h3><p>相对于put来说,get真的太简单了。</p>
<ul>
<li>计算key的hash值,根据hash值找到对应数组下标: <code>hash & (length-1)</code>.</li>
<li>判断数组该位置处的元素是否刚好就是我们要找的,如果不是,走第三步.</li>
<li>判断该元素类型是否是TreeNode,如果是,用红黑树的方法取数据,如果不是,走第四步.</li>
<li>遍历链表,直到找到相等(==或equals)的key.</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">return</span> (e = getNode(hash(key), key)) == <span class="keyword">null</span> ? <span class="keyword">null</span> : e.value;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> Node<K,V> <span class="title">getNode</span><span class="params">(<span class="keyword">int</span> hash, Object key)</span> </span>{</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> first, e; <span class="keyword">int</span> n; K k;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (first = tab[(n - <span class="number">1</span>) & hash]) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 判断第一个节点是不是就是需要的</span></span><br><span class="line"> <span class="keyword">if</span> (first.hash == hash && <span class="comment">// always check first node</span></span><br><span class="line"> ((k = first.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> first;</span><br><span class="line"> <span class="keyword">if</span> ((e = first.next) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 判断是否是红黑树</span></span><br><span class="line"> <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> <span class="keyword">return</span> ((TreeNode<K,V>)first).getTreeNode(hash, key);</span><br><span class="line"> <span class="comment">// 链表遍历</span></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>参考:</p>
<ol>
<li><a href="https://javadoop.com/post/hashmap" target="_blank" rel="noopener">Javadoop</a></li>
<li><a href="https://blog.csdn.net/sd_csdn_scy/article/details/55510453" target="_blank" rel="noopener">HashMap中hash函数h & (length-1)详解</a></li>
<li><a href="https://blog.csdn.net/doujinlong1/article/details/81196048" target="_blank" rel="noopener">HashMap</a></li>
</ol>
<blockquote>
<p><strong>原文:</strong><a href="https://www.cnblogs.com/jajian/p/10385063.html" target="_blank" rel="noopener">https://www.cnblogs.com/jajian/p/10385063.html</a></p>
</blockquote>
]]></content>
<categories>
<category>源码分析</category>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Map</tag>
</tags>
</entry>
<entry>
<title>Java中创建对象的5种不同方法</title>
<url>/posts/c7baf04f/</url>
<content><![CDATA[<p>作为Java开发者,我们每天都会创建大量的对象,但是,我们总是使用管理依赖系统(如Spring框架)来创建这些对象。其实还有其他方法可以创建对象,在接下来的文章中我会进行详细介绍。<br>1.使用new关键字<br>这是最常见的创建对象的方法,并且也非常简单。通过使用这种方法我们可以调用任何我们需要调用的构造函数。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">Employee emp1 = <span class="keyword">new</span> Employee();</span><br><span class="line">0: new #19 // class org/programming/mitra/exercises/Employee 3: dup 4: invokespecial #21 // Method org/programming/mitra/exercises/Employee."":V</span><br></pre></td></tr></table></figure>
<p>2.使用class类的newInstance方法<br>我们也可以使用class类的newInstance方法来创建对象。此newInstance方法调用无参构造函数以创建对象。<br>我们可以通过newInstance 用以下方式创建对象:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Employee emp2 = (Employee) Class.forName(<span class="string">"org.programming.mitra.exercises.Employee"</span>).newInstance;</span><br></pre></td></tr></table></figure>
<p>或者</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">Employee emp2 = Employee<span class="class">.<span class="keyword">class</span>.<span class="title">newInstance</span></span>;</span><br><span class="line">51: invokevirtual #70 // Method java/lang/Class.newInstance:Ljava/lang/Object;</span><br></pre></td></tr></table></figure>
<p>3.使用构造函数类的 newInstance方法<br>与使用class类的newInstance方法相似,java.lang.reflect.Constructor类中有一个可以用来创建对象的newInstance函数方法。通过使用这个newInstance方法我们也可以调用参数化构造函数和私有构造函数。<br>Constructor<br>111: invokevirtual #80 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;<br>这些 newInstance 方法被认为是创建对象的反射手段。实际上,内部类的newInstance方法使用构造函数类的 newInstance 方法。这就是为什么后者是首选并且使用不同的框架如Spring, Hibernate, Struts等。<br>4.使用clone方法<br>实际上无论何时我们调用clone 方法,JAVA虚拟机都为我们创建了一个新的对象并且复制了之前对象的内容到这个新的对象中。使用 clone方法创建对象不会调用任何构造函数。<br>为了在对象中使用clone方法,我们需要在其中实现可克隆类型并定义clone方法。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">Employee emp4 = (Employee) emp3.clone();</span><br><span class="line">162: invokevirtual #87 // Method org/programming/mitra/exercises/Employee.clone Ljava/lang/Object;</span><br></pre></td></tr></table></figure>
<p>5.使用反序列化<br>无论何时我们对一个对象进行序列化和反序列化,JAVA虚拟机都会为我们创建一个单独的对象。在反序列化中,JAVA虚拟机不会使用任何构造函数来创建对象。<br>对一个对象进行序列化需要我们在类中实现可序列化的接口。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">ObjectInputStream in = <span class="keyword">new</span> ObjectInputStream(<span class="keyword">new</span> FileInputStream(<span class="string">"data.obj"</span>)); </span><br><span class="line">Employee emp5 = (Employee) in.readObject();</span><br><span class="line">invokevirtual #118 // Method java/io/ObjectInputStream.readObject:Ljava/lang/Object;</span><br></pre></td></tr></table></figure>
<p>正如我们在以上的字节代码片段中所看到的,除第一种被转换为一个新的函数和一个 invokespecial 指令以外,其它4种方法都被调用并转换为invokevirtual。<br>示例<br>让我们来看看准备创建对象的 Employee 类:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Employee</span> <span class="keyword">implements</span> <span class="title">Cloneable</span>, <span class="title">Serializable</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Employee</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Employee Constructor Called..."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> prime = <span class="number">31</span>;</span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">1</span>;</span><br><span class="line"> result = (prime * result) + ((name == <span class="keyword">null</span>) ? <span class="number">0</span> : name.hashCode);</span><br><span class="line"> <span class="keyword">return</span> (result);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object obj)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span> == obj) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (obj == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (getClass != obj.getClass) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> Employee other = (Employee) obj;</span><br><span class="line"> <span class="keyword">if</span> (name == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (other.name != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (!name.equals(other.name)) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (<span class="string">"Employee [name="</span> + name + <span class="string">"]"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">clone</span><span class="params">()</span> </span>{</span><br><span class="line"> Object obj = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> obj = <span class="keyword">super</span>.clone;</span><br><span class="line"> } <span class="keyword">catch</span> (CloneNotSupportedException e) {</span><br><span class="line"> e.printStackTrace;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (obj);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在下面的Java程序中我们用5种方式来创建 Employee对象。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ObjectCreation</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String... args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// By using new keyword </span></span><br><span class="line"> Employee emp1 = <span class="keyword">new</span> Employee();</span><br><span class="line"> emp1.setName(<span class="string">"Naresh"</span>);</span><br><span class="line"> System.out.println(emp1 + <span class="string">", hashcode : "</span> + emp1.hashCode());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// By using Class class's newInstance method </span></span><br><span class="line"> Employee emp2 = (Employee) Class.forName(<span class="string">"org.programming.mitra.exercises.Employee"</span>).newInstance();</span><br><span class="line"> <span class="comment">// Or we can simply do this // Employee emp2 = Employee.class.newInstance(); </span></span><br><span class="line"> emp2.setName(<span class="string">"Rishi"</span>);</span><br><span class="line"> System.out.println(emp2 + <span class="string">", hashcode : "</span> + emp2.hashCode());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// By using Constructor class's newInstance method Constructor</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>此程序输出结果如下:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">Employee Constructor Called… Employee [name=Naresh], hashcode : -1968815046</span><br><span class="line">Employee Constructor Called… Employee [name=Rishi], hashcode : 78970652</span><br><span class="line">Employee Constructor Called… Employee [name=Yogesh], hashcode : -1641292792</span><br><span class="line">Employee [name=Atul], hashcode : 2051657 Employee [name=Akash], hashcode : 63313419</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Java动态代理、Cglib、AOP详解</title>
<url>/posts/3a5f5aec/</url>
<content><![CDATA[<h2 id="Java-代理模式实现方式,主要有如下五种方法"><a href="#Java-代理模式实现方式,主要有如下五种方法" class="headerlink" title="Java 代理模式实现方式,主要有如下五种方法"></a>Java 代理模式实现方式,主要有如下五种方法</h2><ul>
<li>静态代理,工程师编辑代理类代码,实现代理模式;在编译期就生成了代理类。</li>
<li>基于 JDK 实现动态代理,通过jdk提供的工具方法Proxy.newProxyInstance动态构建全新的代理类(继承Proxy类,并持有InvocationHandler接口引用 )字节码文件并实例化对象返回。(jdk动态代理是由java内部的反射机制来实例化代理对象,并代理的调用委托类方法)</li>
<li>基于CGlib 动态代理模式 基于继承被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可。(cglib动态代理底层是借助asm字节码技术</li>
<li>基于 Aspectj 实现动态代理(修改目标类的字节,织入代理的字节,在程序编译的时候 插入动态代理的字节码,不会生成全新的Class )</li>
<li>基于 instrumentation 实现动态代理(修改目标类的字节码、类装载的时候动态拦截去修改,基于javaagent) <code>-javaagent:spring-instrument-4.3.8.RELEASE.jar</code> (类装载的时候 插入动态代理的字节码,不会生成全新的Class )</li>
</ul>
<h2 id="Notes"><a href="#Notes" class="headerlink" title="Notes"></a>Notes</h2><ul>
<li>委托类 即指的是代理模式中的被代理对象</li>
<li>代理类 指的是生成的代表委托类的一个角色</li>
</ul>
<h2 id="静态代理实现"><a href="#静态代理实现" class="headerlink" title="静态代理实现"></a>静态代理实现</h2><p>静态代理是代理类在编译期间就创建好了,不是编译器生成的代理类,而是手动创建的类。在编译时就已经将接口,被代理类,代理类等确定下来。,软件设计中所指的代理一般是指静态代理,也就是在代码中显式指定的代理。</p>
<h3 id="实现步骤"><a href="#实现步骤" class="headerlink" title="实现步骤"></a>实现步骤</h3><ul>
<li>委托类和代理类之间的约束接口Cat</li>
<li>约束接口实现类 Lion,实现 Cat 接口,委托角色</li>
<li>代理类实现 FeederProxy,实现Cat 接口,并含有一个 Cat接口引用属性。 代理角色,代理 cat接口属性引用实例的行为并可以新增公共逻辑</li>
</ul>
<h4 id="Cat接口"><a href="#Cat接口" class="headerlink" title="Cat接口"></a>Cat接口</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.staticproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> PengRong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@package</span> org.vincent.proxy.staticproxy</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018/12/15 - 17:12</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ProjectName</span> JavaAopLearning</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 静态代理类接口, 委托类和代理类都需要实现的接口规范。 定义了一个猫科动物的两个行为接口,吃东西,奔跑。 作为代理类 和委托类之间的约束接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Cat</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">eatFood</span><span class="params">(String foodName)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">running</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="委托类-Lion"><a href="#委托类-Lion" class="headerlink" title="委托类 Lion"></a>委托类 Lion</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.staticproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> PengRong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@package</span> org.vincent.proxy.staticproxy</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018/12/15 - 17:15</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ProjectName</span> JavaAopLearning</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 狮子 实现了猫科动物接口Cat, 并实现了具体的行为。作为委托类实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Lion</span> <span class="keyword">implements</span> <span class="title">Cat</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> runningSpeed;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getRunningSpeed</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> runningSpeed;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setRunningSpeed</span><span class="params">(<span class="keyword">int</span> runningSpeed)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.runningSpeed = runningSpeed;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Lion</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">eatFood</span><span class="params">(String foodName)</span> </span>{</span><br><span class="line"> String eat = <span class="keyword">this</span>.name + <span class="string">" Lion eat food. foodName = "</span> + foodName;</span><br><span class="line"> System.out.println(eat);</span><br><span class="line"> <span class="keyword">return</span> eat;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">running</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="keyword">this</span>.name + <span class="string">" Lion is running . Speed :"</span> + <span class="keyword">this</span>.runningSpeed);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="代理类角色-FeederProxy"><a href="#代理类角色-FeederProxy" class="headerlink" title="代理类角色(FeederProxy)"></a>代理类角色(FeederProxy)</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.staticproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> PengRong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@package</span> org.vincent.proxy.staticproxy</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018/12/15 - 17:19</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ProjectName</span> JavaAopLearning</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 饲养员 实现Cat接口,作为静态代理类实现。代理狮子的行为。 代理类中可以新增一些其他行为,在实践中主要做的是参数校验的功能。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeederProxy</span> <span class="keyword">implements</span> <span class="title">Cat</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Cat cat;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">FeederProxy</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">FeederProxy</span><span class="params">(Cat cat)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (cat <span class="keyword">instanceof</span> Cat) {</span><br><span class="line"> <span class="keyword">this</span>.cat = cat;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCat</span><span class="params">(Cat cat)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (cat <span class="keyword">instanceof</span> Cat) {</span><br><span class="line"> <span class="keyword">this</span>.cat = cat;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">eatFood</span><span class="params">(String foodName)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"proxy Lion exec eatFood "</span>);</span><br><span class="line"> <span class="keyword">return</span> cat.eatFood(foodName);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">running</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"proxy Lion exec running."</span>);</span><br><span class="line"> <span class="keyword">return</span> cat.running();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="静态代理类测试"><a href="#静态代理类测试" class="headerlink" title="静态代理类测试"></a>静态代理类测试</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.staticproxy.Cat;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.staticproxy.FeederProxy;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.staticproxy.Lion;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> PengRong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@package</span> org.vincent.proxy</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018/12/15 - 18:31</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ProjectName</span> JavaAopLearning</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 静态代理类测试</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">staticProxyTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> Lion lion = <span class="keyword">new</span> Lion();</span><br><span class="line"> lion.setName(<span class="string">"狮子 小王"</span>);</span><br><span class="line"> lion.setRunningSpeed(<span class="number">100</span>); <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * new 静态代理类,静态代理类在编译前已经创建好了,和动态代理的最大区别点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Cat proxy = <span class="keyword">new</span> FeederProxy(lion);</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">" -- "</span> + proxy.eatFood(<span class="string">"水牛"</span>));</span><br><span class="line"> proxy.running();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>静态代理很好的诠释了代理设计模式,代理模式最主要的就是有一个公共接口(Cat),一个委托类(Lion),一个代理类(FeederProxy),代理类持有委托类的实例,代为执行具体类实例方法。 上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指客户端不直接调用实际对象的方法,客户端依赖公共接口并使用代理类。 那么我们在代理过程中就可以加上一些其他用途。 就这个例子来说在 eatFood方法调用中,代理类在调用具体实现类之前添加<code>System.out.println("proxy Lion exec eatFood ");</code>语句 就是添加间接性带来的收益。代理类存在的意义是为了增加一些公共的逻辑代码。</p>
<h2 id="动态代理类-基于接口实现"><a href="#动态代理类-基于接口实现" class="headerlink" title="动态代理类(基于接口实现)"></a>动态代理类(基于接口实现)</h2><p>静态代理是代理类在代码运行前已经创建好,并生成class文件;动态代理类 是代理类在程序运行时创建的代理模式。<br>动态代理类的代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 想想你有100个静态代理类,现在有一个需求,每个代理类都需要新增一个处理逻辑,你需要打开100个代理类在每个代理方法里面新增处理逻辑吗? 有或者代理类有5个方法,每个方法都需要新增一个处理逻辑, 你需要在每个方法都手动新增处理逻辑吗? 想想就挺无趣的。动态代理类帮你一键搞定。</p>
<h3 id="动态代理类涉及角色"><a href="#动态代理类涉及角色" class="headerlink" title="动态代理类涉及角色"></a>动态代理类涉及角色</h3><ul>
<li>委托类和代理类实现的公共接口(Person.java)</li>
<li>实现公共接口的具体委托类(SoftwareEngineer.java)</li>
<li>InvocationHandler接口被Proxy类回调处理,一般实现 InvocationHandler 接口的类具有委托类引用,接口方法 invoke 中添加公共代码并调用委托类的接口方法。(PersonInvocationHandler.java)</li>
<li>JDK提供生成动态代理类的核心类Proxy (JDK 提供的Proxy.java)</li>
</ul>
<h3 id="基于JDK技术-动态代理类技术核心-Proxy类和一个-InvocationHandler-接口"><a href="#基于JDK技术-动态代理类技术核心-Proxy类和一个-InvocationHandler-接口" class="headerlink" title="基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口"></a>基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口</h3><p>java的java.lang.reflect包下提供了Proxy类和一个 InvocationHandler 接口,这个类Proxy定义了生成JDK动态代理类的方法 <code>getProxyClass(ClassLoader loader,Class<?>... interfaces)</code>生成动态代理类,返回class实例代表一个class文件。可以保存该 class 文件查看jdk生成的代理类文件长什么样<br>该生成的动态代理类继承Proxy类,(重要特性) ,并实现公共接口。<br>InvocationHandler这个接口 是被动态代理类回调的接口,我们所有需要增加的针对委托类的统一处理逻辑都增加到invoke 方法里面在调用委托类接口方法之前或之后 结束战斗。</p>
<h4 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h4><h5 id="公共接口"><a href="#公共接口" class="headerlink" title="公共接口"></a>公共接口</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PengRong on 2018/12/25. 创建Person 接口 用于定义 委托类和代理类之间的约束行为</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Person</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> name 人名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> dst 工作目的地</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">goWorking</span><span class="params">(String name, String dst)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取名称</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">String <span class="title">getName</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置名称</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> name</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="具体实现类,等下被委托,被代理的类-SoftwareEngineer-java"><a href="#具体实现类,等下被委托,被代理的类-SoftwareEngineer-java" class="headerlink" title="具体实现类,等下被委托,被代理的类 SoftwareEngineer.java"></a>具体实现类,等下被委托,被代理的类 SoftwareEngineer.java</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PengRong on 2018/12/25. 动态代理委托类实现, 实现接口 Person。 被动态生成的代理类代理</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SoftwareEngineer</span> <span class="keyword">implements</span> <span class="title">Person</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SoftwareEngineer</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SoftwareEngineer</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">goWorking</span><span class="params">(String name, String dst)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"name ="</span> + name + <span class="string">" , 去 "</span> + dst + <span class="string">" 工作"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="InvocationHandler-接口实现-PersonInvocationHandler-java"><a href="#InvocationHandler-接口实现-PersonInvocationHandler-java" class="headerlink" title="InvocationHandler 接口实现 PersonInvocationHandler.java"></a>InvocationHandler 接口实现 PersonInvocationHandler.java</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.InvocationHandler;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PengRong on 2018/12/25. PersonInvocationHandler 类 实现InvocationHandler接口,这个类中持有一个被代理对象(委托类)的实例target。该类别JDK</span></span><br><span class="line"><span class="comment"> * Proxy类回调 InvocationHandler 接口中有一个invoke方法,当一个代理实例的方法被调用时,代理方法将被编码并分发到</span></span><br><span class="line"><span class="comment"> * InvocationHandler接口的invoke方法执行。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PersonInvocationHandler</span><<span class="title">T</span>> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 被代理对象引用,invoke 方法里面method 需要使用这个 被代理对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> T target;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">PersonInvocationHandler</span><span class="params">(T target)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.target = target;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> proxy 代表动态生成的 动态代理 对象实例</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> method 代表被调用委托类的接口方法,和生成的代理类实例调用的接口方法是一致的,它对应的Method 实例</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args 代表调用接口方法对应的Object参数数组,如果接口是无参,则为null; 对于原始数据类型返回的他的包装类型。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Throwable</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{ <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在转调具体目标对象之前,可以执行一些功能处理</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> System.out.println(</span><br><span class="line"> <span class="string">"被动态代理类回调执行, 代理类 proxyClass ="</span> + proxy.getClass() + <span class="string">" 方法名: "</span> + method.getName()</span><br><span class="line"> + <span class="string">"方法. 方法返回类型:"</span> + method.getReturnType()</span><br><span class="line"> + <span class="string">" 接口方法入参数组: "</span> + (args == <span class="keyword">null</span> ? <span class="string">"null"</span> : Arrays.toString(args))); <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 代理过程中插入监测方法,计算该方法耗时</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> MonitorUtil.start();</span><br><span class="line"> Thread.sleep(<span class="number">1</span>); <span class="comment">/** 调用呗代理对象的真实方法,*/</span></span><br><span class="line"> Object result = method.invoke(target, args);</span><br><span class="line"> MonitorUtil.finish(method.getName());</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="PersonInvocationHandler-invoke-方法中添加的公共代码,这里简单以统计方法执行时间为逻辑"><a href="#PersonInvocationHandler-invoke-方法中添加的公共代码,这里简单以统计方法执行时间为逻辑" class="headerlink" title="PersonInvocationHandler invoke 方法中添加的公共代码,这里简单以统计方法执行时间为逻辑"></a>PersonInvocationHandler invoke 方法中添加的公共代码,这里简单以统计方法执行时间为逻辑</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PengRong on 2018/12/25. 方法用时监控类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MonitorUtil</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ThreadLocal<Long> tl = <span class="keyword">new</span> ThreadLocal<>();</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</span><br><span class="line"> tl.set(System.currentTimeMillis());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 结束时打印耗时</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> methodName 方法名</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">finish</span><span class="params">(String methodName)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> finishTime = System.currentTimeMillis();</span><br><span class="line"> System.out.println(methodName + <span class="string">"方法执行耗时"</span> + (finishTime - tl.get()) + <span class="string">"ms"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="最后的是-怎么创建代理类"><a href="#最后的是-怎么创建代理类" class="headerlink" title="最后的是 怎么创建代理类"></a>最后的是 怎么创建代理类</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.jdkdynamicProxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Constructor;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.InvocationHandler;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Proxy;</span><br><span class="line"><span class="keyword">import</span> java.nio.file.Path;</span><br><span class="line"><span class="keyword">import</span> java.nio.file.Paths;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.dynamicproxy.Person;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.dynamicproxy.PersonInvocationHandler;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.dynamicproxy.SoftwareEngineer;</span><br><span class="line"><span class="keyword">import</span> sun.misc.ProxyGenerator;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 动态代理类测试 Created by PengRong on 2018/12/25.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdkDynamicProxyTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>{ <span class="comment">// 打开保存JDK动态代理生成的类文件</span></span><br><span class="line"> saveGeneratedJdkProxyFiles(); <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 第一种方法: 通过 Proxy.newProxyInstance 方法 获取代理对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> System.out</span><br><span class="line"> .println(<span class="string">"-------------------第一种创建代理类方法--------------"</span>); <span class="comment">//创建一个实例对象,这个对象是被代理的对象,委托类</span></span><br><span class="line"> Person person = <span class="keyword">new</span> SoftwareEngineer(</span><br><span class="line"> <span class="string">"Vincent"</span>); <span class="comment">//创建一个与代理类相关联的InvocationHandler,每一个代理类都有一个关联的 InvocationHandler,并将代理类引用传递进去</span></span><br><span class="line"> InvocationHandler Handler = <span class="keyword">new</span> PersonInvocationHandler<>(</span><br><span class="line"> person); <span class="comment">//创建一个 代理对象 personProxy 来代理 person,创建的代理对象的每个执行方法都会被替换执行Invocation接口中的invoke方法</span></span><br><span class="line"> Person personProxy = (Person) Proxy</span><br><span class="line"> .newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class},</span><br><span class="line"> Handler); <span class="comment">/** 代理类信息 */</span></span><br><span class="line"> System.out.println(</span><br><span class="line"> <span class="string">"package = "</span> + personProxy.getClass().getPackage() + <span class="string">" SimpleName = "</span> + personProxy</span><br><span class="line"> .getClass().getSimpleName() + <span class="string">" name ="</span> + personProxy.getClass().getName()</span><br><span class="line"> + <span class="string">" CanonicalName = "</span> + <span class="string">""</span> + personProxy.getClass().getCanonicalName()</span><br><span class="line"> + <span class="string">" 实现的接口 Interfaces = "</span> + Arrays.toString(personProxy.getClass().getInterfaces())</span><br><span class="line"> + <span class="string">" superClass = "</span> + personProxy.getClass().getSuperclass() + <span class="string">" methods ="</span> + Arrays</span><br><span class="line"> .toString(personProxy.getClass().getMethods())); <span class="comment">// 通过 代理类 执行 委托类的代码逻辑</span></span><br><span class="line"> personProxy.goWorking(personProxy.getName(), <span class="string">"深圳"</span>);</span><br><span class="line"> System.out.println(<span class="string">"-------------------第二种创建代理类方法--------------"</span>); <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 动态代理对象步骤</span></span><br><span class="line"><span class="comment"> * 1、 创建一个与代理对象相关联的 InvocationHandler,以及真实的委托类实例</span></span><br><span class="line"><span class="comment"> * 2、Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass,该类继承Proxy类,实现 Person.java接口;JDK动态代理的特点是代理类必须继承Proxy类</span></span><br><span class="line"><span class="comment"> * 3、通过代理类 proxyClass 获得他的带InvocationHandler 接口的构造函数 ProxyConstructor</span></span><br><span class="line"><span class="comment"> * 4、通过 构造函数实例 ProxyConstructor 实例化一个代理对象,并将 InvocationHandler 接口实例传递给代理类。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="comment">// 1、创建 InvocationHandler 实例并设置代理的目标类对象</span></span><br><span class="line"> Person persontwo = <span class="keyword">new</span> SoftwareEngineer(<span class="string">"Vincent"</span>);</span><br><span class="line"> InvocationHandler Handlertwo = <span class="keyword">new</span> PersonInvocationHandler<>(</span><br><span class="line"> persontwo); <span class="comment">// 2 创建代理类,是一个字节码文件, 把 proxyClass 保存起来就能看到 他继承Proxy 类,实现Person接口</span></span><br><span class="line"> Class<?> proxyClass = Proxy.getProxyClass(Person<span class="class">.<span class="keyword">class</span>.<span class="title">getClassLoader</span>(),</span></span><br><span class="line"> new Class<?>[]{Person.class}); /** 代理类信息 */</span><br><span class="line"> System.out.println(</span><br><span class="line"> <span class="string">"package = "</span> + proxyClass.getPackage() + <span class="string">" SimpleName = "</span> + proxyClass.getSimpleName()</span><br><span class="line"> + <span class="string">" name ="</span> + proxyClass.getName() + <span class="string">" CanonicalName = "</span> + <span class="string">""</span> + proxyClass</span><br><span class="line"> .getCanonicalName() + <span class="string">" 实现的接口 Interfaces = "</span> + Arrays</span><br><span class="line"> .toString(proxyClass.getInterfaces()) + <span class="string">" superClass = "</span> + proxyClass.getSuperclass()</span><br><span class="line"> + <span class="string">" methods ="</span> + Arrays.toString(proxyClass</span><br><span class="line"> .getMethods())); <span class="comment">// 3、 通过 proxyClass 获得 一个带有InvocationHandler参数的构造器constructor</span></span><br><span class="line"> Constructor<?> ProxyConstructor = proxyClass</span><br><span class="line"> .getConstructor(InvocationHandler<span class="class">.<span class="keyword">class</span>)</span>; <span class="comment">// 4、通过构造器创建一个 动态代理类 实例</span></span><br><span class="line"> Person stuProxy = (Person) ProxyConstructor.newInstance(Handlertwo); <span class="comment">/** 检测生成的类是否是代理类 */</span></span><br><span class="line"> System.out.println(<span class="string">"stuProxy isProxy "</span> + Proxy</span><br><span class="line"> .isProxyClass(stuProxy.getClass())); <span class="comment">/** 获取 代理类关联的 InvocationHandler 是哪个*/</span></span><br><span class="line"> InvocationHandler handlerObject = Proxy.getInvocationHandler(stuProxy);</span><br><span class="line"> System.out.println(handlerObject.getClass().getName());</span><br><span class="line"> stuProxy.goWorking(stuProxy.getName(), <span class="string">"广州"</span>); <span class="comment">// 保存代理類</span></span><br><span class="line"> saveClass(<span class="string">"$PersonProxy0"</span>, proxyClass.getInterfaces(), <span class="string">"D:/123/"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成代理类 class 并保持到文件中</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> className 生成的代理类名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> interfaces 代理类需要实现的接口</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> pathdir 代理类保存的目录路径,以目录分隔符结尾</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">saveClass</span><span class="params">(String className, Class<?>[] interfaces, String pathdir)</span> </span>{ <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 第一个参数是 代理类 名 。</span></span><br><span class="line"><span class="comment"> * 第二个参数是 代理类需要实现的接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">byte</span>[] classFile = ProxyGenerator.generateProxyClass(className, interfaces); <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 如果目录不存在就新建所有子目录</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Path path1 = Paths.get(pathdir);</span><br><span class="line"> <span class="keyword">if</span> (!path1.toFile().exists()) {</span><br><span class="line"> path1.toFile().mkdirs();</span><br><span class="line"> }</span><br><span class="line"> String path = pathdir + className + <span class="string">".class"</span>;</span><br><span class="line"> <span class="keyword">try</span> (FileOutputStream fos = <span class="keyword">new</span> FileOutputStream(path)) {</span><br><span class="line"> fos.write(classFile);</span><br><span class="line"> fos.flush();</span><br><span class="line"> System.out.println(<span class="string">"代理类class文件写入成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(<span class="string">"写文件错误"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置保存Java动态代理生成的类文件。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">saveGeneratedJdkProxyFiles</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> Field field = System.class.getDeclaredField("props");</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Properties props = (Properties) field.get(<span class="keyword">null</span>);</span><br><span class="line"> props.put(<span class="string">"sun.misc.ProxyGenerator.saveGeneratedFiles"</span>, <span class="string">"true"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="解析JDK生成的动态代理类"><a href="#解析JDK生成的动态代理类" class="headerlink" title="解析JDK生成的动态代理类"></a>解析JDK生成的动态代理类</h4><p>saveGeneratedJdkProxyFiles方法 打开了存储jdk生成的动态代理类 以 接口方法 goWorking 为例讲解</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//</span></span><br><span class="line"><span class="keyword">package</span> com.sun.proxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.InvocationHandler;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Proxy;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.UndeclaredThrowableException;</span><br><span class="line"><span class="keyword">import</span> org.vincent.proxy.dynamicproxy.Person;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> $<span class="title">Proxy0</span> <span class="keyword">extends</span> <span class="title">Proxy</span> <span class="keyword">implements</span> <span class="title">Person</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m1;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m4;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m3;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m2;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m5;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m0;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> $Proxy0(InvocationHandler var1) <span class="keyword">throws</span> {</span><br><span class="line"> <span class="keyword">super</span>(var1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object var1)</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> ((Boolean) <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m1, <span class="keyword">new</span> Object[]{var1})).booleanValue();</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var3) {</span><br><span class="line"> <span class="keyword">throw</span> var3;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var4) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var4);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String var1)</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m4, <span class="keyword">new</span> Object[]{var1});</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var3) {</span><br><span class="line"> <span class="keyword">throw</span> var3;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var4) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var4);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">getName</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (String) <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m3, (Object[]) <span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">toString</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (String) <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m2, (Object[]) <span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 对接口 goWorking 的调用 转变成 super.h.invoke(this, m5, new Object[]{var1, var2}); 调用。 h</span></span><br><span class="line"><span class="comment"> * 就是Proxy.java类的一个 InvocationHandler 接口 属性, 我们在创建 动态代理类实例时候都必须 传一个 InvocationHandler 接口的实例过去。</span></span><br><span class="line"><span class="comment"> * 这里就是刚才我们定义的 PersonInvocationHandler 。 回到过后是不是就回到了 PersonInvocationHandler.invoke方法里面,所以</span></span><br><span class="line"><span class="comment"> * PersonInvocationHandler 是我们生成的动态代理类的拦截器,拦截所有方法调用。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">goWorking</span><span class="params">(String var1, String var2)</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m5, <span class="keyword">new</span> Object[]{var1, var2});</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var4) {</span><br><span class="line"> <span class="keyword">throw</span> var4;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var5) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var5);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> ((Integer) <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m0, (Object[]) <span class="keyword">null</span>)).intValue();</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 静态代码块,根据动态代理实现的公共接口类接口方法 获取到所有接口方法 的 Method 实例*/</span></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> m1 = Class.forName(<span class="string">"java.lang.Object"</span>)</span><br><span class="line"> .getMethod(<span class="string">"equals"</span>, <span class="keyword">new</span> Class[]{Class.forName(<span class="string">"java.lang.Object"</span>)});</span><br><span class="line"> m4 = Class.forName(<span class="string">"org.vincent.proxy.dynamicproxy.Person"</span>)</span><br><span class="line"> .getMethod(<span class="string">"setName"</span>, <span class="keyword">new</span> Class[]{Class.forName(<span class="string">"java.lang.String"</span>)});</span><br><span class="line"> m3 = Class.forName(<span class="string">"org.vincent.proxy.dynamicproxy.Person"</span>)</span><br><span class="line"> .getMethod(<span class="string">"getName"</span>, <span class="keyword">new</span> Class[<span class="number">0</span>]);</span><br><span class="line"> m2 = Class.forName(<span class="string">"java.lang.Object"</span>).getMethod(<span class="string">"toString"</span>, <span class="keyword">new</span> Class[<span class="number">0</span>]);</span><br><span class="line"> m5 = Class.forName(<span class="string">"org.vincent.proxy.dynamicproxy.Person"</span>).getMethod(<span class="string">"goWorking"</span>,</span><br><span class="line"> <span class="keyword">new</span> Class[]{Class.forName(<span class="string">"java.lang.String"</span>), Class.forName(<span class="string">"java.lang.String"</span>)});</span><br><span class="line"> m0 = Class.forName(<span class="string">"java.lang.Object"</span>).getMethod(<span class="string">"hashCode"</span>, <span class="keyword">new</span> Class[<span class="number">0</span>]);</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchMethodException var2) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchMethodError(var2.getMessage());</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoClassDefFoundError(var3.getMessage());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时默认不会保存在文件,放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建代理对象实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。<br>我们可以对 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,被Proxy类回调。在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把客户端对invoke的调用最终都转为对被代理对象的调用。<br>客户端代码通过代理类引用调用接口方法时,通过代理类关联的中介类对象引用来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理Proxy类提供了模板实现,对外提供扩展点,外部通过实现InvocationHandler接口将被代理类纳入JDK代理类Proxy。</p>
<h4 id="一个典型的基于JDK动态代理创建对象过程可分为以下四个步骤:"><a href="#一个典型的基于JDK动态代理创建对象过程可分为以下四个步骤:" class="headerlink" title="一个典型的基于JDK动态代理创建对象过程可分为以下四个步骤:"></a>一个典型的基于JDK动态代理创建对象过程可分为以下四个步骤:</h4><ol>
<li>通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);</li>
<li>通过为Proxy类指定ClassLoader对象和一组interface代理类需要实现的接口,创建动态代理类类文件,默认JDK并不会保存这个文件到文件中;可以保存起来观察生成的代理类结构<code>Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});</code></li>
<li>通过上面新建的代理clazz的反射机制获取动态代理类的一个构造函数,其构造函数入参类型是调用处理器接口(<code>IvocationHandler</code>)类型 <code>Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});</code></li>
<li>通过构造函数实例创建代理类实例,此时需将调用处理器对象作为参数被传入 Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler)); 为了简化对象创建过程,Proxy类中的newInstance工具方法封装了2~4,只需两步即可完成代理对象的创建。</li>
</ol>
<h4 id="JDK动态代理特点总结"><a href="#JDK动态代理特点总结" class="headerlink" title="JDK动态代理特点总结"></a>JDK动态代理特点总结</h4><ul>
<li>生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,Java的继承机制决定了JDK动态代理类们无法实现对 类 的动态代理。所以也就决定了java动态代理只能对接口进行代理,</li>
<li>每个生成的动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行</li>
<li>代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖; 二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被调用处理器分派到委托类执行。</li>
</ul>
<h4 id="JDK动态代理不足"><a href="#JDK动态代理不足" class="headerlink" title="JDK动态代理不足"></a>JDK动态代理不足</h4><p>JDK动态代理的代理类字节码在创建时,需要实现业务实现类所实现的接口作为参数。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。(JDK动态代理重要特点是代理接口) 并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。<br>动态代理只能对接口产生代理,不能对类产生代理</p>
<h3 id="基于CGlib-技术动态代理代理类实现-基于继承"><a href="#基于CGlib-技术动态代理代理类实现-基于继承" class="headerlink" title="基于CGlib 技术动态代理代理类实现 (基于继承)"></a>基于CGlib 技术动态代理代理类实现 (基于继承)</h3><p>Cglib是针对类来实现代理的,他的原理是对代理的目标类生成一个子类,并覆盖其中方法实现增强,因为底层是基于创建被代理类的一个子类,所以它避免了JDK动态代理类的缺陷。<br>但因为采用的是继承,所以不能对final修饰的类进行代理。final修饰的类不可继承。</p>
<h4 id="导入maven-依赖"><a href="#导入maven-依赖" class="headerlink" title="导入maven 依赖"></a>导入maven 依赖</h4><p>cglib 是基于asm 字节修改技术。导入 cglib 会间接导入 asm, ant, ant-launcher 三个jar 包。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- cglib 动态代理依赖 begin --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>cglib<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>cglib<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.2.5<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!-- cglib 动态代理依赖 stop --></span></span><br></pre></td></tr></table></figure>
<h4 id="业务类实现"><a href="#业务类实现" class="headerlink" title="业务类实现"></a>业务类实现</h4><p>cglib是针对类来实现代理的,原理是对指定的业务类生成他的一个子类,并覆盖其中的业务方法来实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.cglibproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.proxy.cglibproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: Cglib 代理模式中 被代理的委托类 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-17:55 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Dog</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">call</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"wang wang wang"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Dog .."</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="方法拦截器-实现-MethodInterceptor-接口"><a href="#方法拦截器-实现-MethodInterceptor-接口" class="headerlink" title="方法拦截器 实现 MethodInterceptor 接口"></a>方法拦截器 实现 MethodInterceptor 接口</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.cglibproxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.Enhancer;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.MethodInterceptor;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.MethodProxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.proxy.cglibproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: Cglib 方法拦截器,不用依赖被代理业务类的引用。 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-17:56 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CglibMethodInterceptor</span> <span class="keyword">implements</span> <span class="title">MethodInterceptor</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用于生成 Cglib 动态代理类工具方法</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> target 代表需要 被代理的 委托类的 Class 对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">CglibProxyGeneratory</span><span class="params">(Class target)</span> </span>{ <span class="comment">/** 创建cglib 代理类 start */</span></span><br><span class="line"> <span class="comment">// 创建加强器,用来创建动态代理类</span></span><br><span class="line"> Enhancer enhancer = <span class="keyword">new</span> Enhancer(); <span class="comment">// 为代理类指定需要代理的类,也即是父类</span></span><br><span class="line"> enhancer.setSuperclass(</span><br><span class="line"> target); <span class="comment">// 设置方法拦截器回调引用,对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept() 方法进行拦截</span></span><br><span class="line"> enhancer.setCallback(<span class="keyword">this</span>); <span class="comment">// 获取动态代理类对象并返回</span></span><br><span class="line"> <span class="keyword">return</span> enhancer.create(); <span class="comment">/** 创建cglib 代理类 end */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 功能主要是在调用业务类方法之前 之后添加统计时间的方法逻辑. intercept 因为 具有 MethodProxy proxy 参数的原因 不再需要代理类的引用对象了,直接通过proxy</span></span><br><span class="line"><span class="comment"> * 对象访问被代理对象的方法(这种方式更快)。 当然 也可以通过反射机制,通过 method 引用实例 Object result = method.invoke(target,</span></span><br><span class="line"><span class="comment"> * args); 形式反射调用被代理类方法, target 实例代表被代理类对象引用, 初始化 CglibMethodInterceptor 时候被赋值 。但是Cglib不推荐使用这种方式</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> obj 代表Cglib 生成的动态代理类 对象本身</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> method 代理类中被拦截的接口方法 Method 实例</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args 接口方法参数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> proxy 用于调用父类真正的业务类方法。可以直接调用被代理类接口方法</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Throwable</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">intercept</span><span class="params">(Object obj, Method method, Object[] args, MethodProxy proxy)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> System.out.println(<span class="string">"before"</span>);</span><br><span class="line"> MonitorUtil.start();</span><br><span class="line"> Object result = proxy</span><br><span class="line"> .invokeSuper(obj, args); <span class="comment">//Object result = method.invoke(target, args);</span></span><br><span class="line"> System.out.println(<span class="string">"after"</span>);</span><br><span class="line"> MonitorUtil.finish(method.getName());</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="一个切面,用于在方法拦截器中intercept-方法中调用真正业务方法之前-之后处理逻辑"><a href="#一个切面,用于在方法拦截器中intercept-方法中调用真正业务方法之前-之后处理逻辑" class="headerlink" title="一个切面,用于在方法拦截器中intercept 方法中调用真正业务方法之前 之后处理逻辑"></a>一个切面,用于在方法拦截器中intercept 方法中调用真正业务方法之前 之后处理逻辑</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.cglibproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PengRong on 2018/12/25. 方法用时监控类,作为一个切面 ,具有两个方法</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MonitorUtil</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ThreadLocal<Long> tl = <span class="keyword">new</span> ThreadLocal<>();</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</span><br><span class="line"> tl.set(System.currentTimeMillis());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 结束时打印耗时</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> methodName 方法名</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">finish</span><span class="params">(String methodName)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> finishTime = System.currentTimeMillis();</span><br><span class="line"> System.out.println(methodName + <span class="string">"方法执行耗时"</span> + (finishTime - tl.get()) + <span class="string">"ms"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Cglib测试类"><a href="#Cglib测试类" class="headerlink" title="Cglib测试类"></a>Cglib测试类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.proxy.cglibproxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.core.DebuggingClassWriter;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.Enhancer;</span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.proxy.cglibproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: TODO <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-18:05 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CglibTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCglib</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(System.getProperty(<span class="string">"user.dir"</span>)); <span class="comment">/** 开启 保存cglib生成的动态代理类类文件*/</span></span><br><span class="line"> saveGeneratedCGlibProxyFiles(</span><br><span class="line"> System.getProperty(<span class="string">"user.dir"</span>)); <span class="comment">/** 第一种方法: 创建cglib 代理类 start */</span></span><br><span class="line"> <span class="comment">// 创建加强器,用来创建动态代理类</span></span><br><span class="line"> Enhancer enhancer = <span class="keyword">new</span> Enhancer(); <span class="comment">// 为代理类指定需要代理的类,也即是父类</span></span><br><span class="line"> enhancer.setSuperclass(Dog<span class="class">.<span class="keyword">class</span>)</span>; <span class="comment">// new 一个新的方法拦截器</span></span><br><span class="line"> CglibMethodInterceptor cglibMethodInterceptor = <span class="keyword">new</span> CglibMethodInterceptor(); <span class="comment">// 设置方法拦截器回调引用,对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept() 方法进行拦截</span></span><br><span class="line"> enhancer.setCallback(cglibMethodInterceptor); <span class="comment">// 获取动态代理类对象并返回</span></span><br><span class="line"> Dog dog = (Dog) enhancer.create(); <span class="comment">/** 创建cglib 代理类 end */</span></span><br><span class="line"> System.out</span><br><span class="line"> .println(dog.call()); <span class="comment">// 对于上面这几步,可以新增一个工具方法 放置在 CglibMethodInterceptor 里面;也就有了第二种方法</span></span><br><span class="line"> <span class="comment">// new 一个新的方法拦截器,该拦截器还顺带一个用于创建代理类的工具方法。看起来简单很多</span></span><br><span class="line"> cglibMethodInterceptor = <span class="keyword">new</span> CglibMethodInterceptor();</span><br><span class="line"> dog = (Dog) cglibMethodInterceptor.CglibProxyGeneratory(Dog<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> System.out.println(dog.call());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置保存Cglib代理生成的类文件。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveGeneratedCGlibProxyFiles</span><span class="params">(String dir)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> Field field = System.class.getDeclaredField("props");</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Properties props = (Properties) field.get(<span class="keyword">null</span>);</span><br><span class="line"> System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, dir);<span class="comment">//dir为保存文件路径</span></span><br><span class="line"> props.put(<span class="string">"net.sf.cglib.core.DebuggingClassWriter.traceEnabled"</span>, <span class="string">"true"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Cglib-总结"><a href="#Cglib-总结" class="headerlink" title="Cglib 总结"></a>Cglib 总结</h4><ul>
<li>CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.</li>
<li>由于是继承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的</li>
<li>做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用.</li>
<li>提供callback 和filter设计,可以灵活地给不同的方法绑定不同的callback。编码更方便灵活。</li>
<li>CGLIB会默认代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。</li>
</ul>
<h2 id="静态代理-基于JDK动态代理-基于Cglib-动态代理"><a href="#静态代理-基于JDK动态代理-基于Cglib-动态代理" class="headerlink" title="静态代理 基于JDK动态代理 基于Cglib 动态代理"></a>静态代理 基于JDK动态代理 基于Cglib 动态代理</h2><p>静态代理是通过在代码中显式编码定义一个业务实现类的代理类,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;<br>JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;<br>CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;<br>静态代理在编译时产生class字节码文件,可以直接使用,效率高。动态代理必须实现InvocationHandler接口,通过invoke调用被委托类接口方法是通过反射方式,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。 cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。</p>
<h2 id="AOP-实现案例"><a href="#AOP-实现案例" class="headerlink" title="AOP 实现案例"></a>AOP 实现案例</h2><p>AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。 总的来说,反射机制在生成类的过程中比较高效,执行时候通过反射调用委托类接口方法比较慢;而asm在生成类之后的相关代理类执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。 还有一点必须注意:jdk动态代理的应用前提,必须是委托类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。 由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。<br>实现AOP关键特点是定义好两个角色 切点 和 切面 。 代理模式中被代理类 委托类处于切点角色,需要添加的其他比如 校验逻辑,事务,审计逻辑 属于非功能实现逻辑通过 切面类定义的方法插入进去。</p>
<h3 id="JDK动态代理-aop-实现方式"><a href="#JDK动态代理-aop-实现方式" class="headerlink" title="JDK动态代理 aop 实现方式"></a>JDK动态代理 aop 实现方式</h3><h4 id="定义切面接口,完成将通用公共方法注入到被代理类接口调用处理中"><a href="#定义切面接口,完成将通用公共方法注入到被代理类接口调用处理中" class="headerlink" title="定义切面接口,完成将通用公共方法注入到被代理类接口调用处理中"></a>定义切面接口,完成将通用公共方法注入到被代理类接口调用处理中</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 定义切面接口,切面接口定义了两个切面方法,分别在切点接口方法执行前和执行后执行 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IAspect</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在切点接口方法执行之前执行</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args 切点参数列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">startTransaction</span><span class="params">(Object... args)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在切点接口方法执行之后执行</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">endTrasaction</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="定义切面实现类"><a href="#定义切面实现类" class="headerlink" title="定义切面实现类"></a>定义切面实现类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Objects;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 改类作为AOP 模型中切面角色类, 实现切面接口,切面接口定义了两个切面方法,分别在切点接口方法执行前和执行后执行 。 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomAspect</span> <span class="keyword">implements</span> <span class="title">IAspect</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 对参数 做判空处理</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args 切点参数列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">startTransaction</span><span class="params">(Object... args)</span> </span>{</span><br><span class="line"> Objects.nonNull(args);</span><br><span class="line"> <span class="keyword">boolean</span> result = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">for</span> (Object temp : args) {</span><br><span class="line"> <span class="keyword">if</span> (Objects.isNull(temp)) {</span><br><span class="line"> result = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">endTrasaction</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"I get datasource here and end transaction"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="定义切点角色接口-因为是基于JDK实现的Aop-,所以委托类需要基于接口实现。"><a href="#定义切点角色接口-因为是基于JDK实现的Aop-,所以委托类需要基于接口实现。" class="headerlink" title="定义切点角色接口 因为是基于JDK实现的Aop ,所以委托类需要基于接口实现。"></a>定义切点角色接口 因为是基于JDK实现的Aop ,所以委托类需要基于接口实现。</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: AOP基于动态代理 实现 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IUserService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">saveUser</span><span class="params">(String username, String password)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="委托类实现"><a href="#委托类实现" class="headerlink" title="委托类实现"></a>委托类实现</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: UserService接口实现类UserServiceImpl 该类 作为AOP中切点角色,切面定义的方法插入到切点的接口方法 执行前和执行后执行。 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">IUserService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveUser</span><span class="params">(String username, String password)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"save user[username="</span> + username + <span class="string">",password="</span> + password + <span class="string">"]"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="JDK动态代理生成器工具类"><a href="#JDK动态代理生成器工具类" class="headerlink" title="JDK动态代理生成器工具类"></a>JDK动态代理生成器工具类</h4><p>可以看到 generatorJDKProxy 方法入参只有两个参数 一个切点接口引用,一个切面接口引用;在InvocationHandler 内部类中可以完整看到切面类方法是怎么影响切点代码执行逻辑的。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.dynamicproxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.InvocationHandler;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Proxy;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: JDK动态代理类生成器 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-16:48 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JDKDynamicProxyGenerator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> targetPoint 需要被代理的委托类对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> aspect 切面对象,该对象方法将在切点方法之前或之后执行</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">generatorJDKProxy</span><span class="params">(IUserService targetPoint, <span class="keyword">final</span> IAspect aspect)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> Proxy.newProxyInstance( <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 委托类使用的类加载器</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> targetPoint.getClass().getClassLoader(), <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 委托类实现的接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> targetPoint.getClass().getInterfaces(), <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成的动态代理类关联的 执行处理器,代理我们的业务逻辑被生成的动态代理类回调</span></span><br><span class="line"><span class="comment"> * 具体逻辑代码执行,返回值为方法执行结果, 在aop模型中,委托类的接口方法称为切点。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">new</span> InvocationHandler() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> Throwable </span>{ <span class="comment">// 执行切面方法,对入参进行校验</span></span><br><span class="line"> <span class="keyword">boolean</span> prepareAction = aspect.startTransaction(args);</span><br><span class="line"> <span class="keyword">if</span> (prepareAction) { <span class="comment">// 具体逻辑代码执行,返回值为方法执行结果</span></span><br><span class="line"> Object result = method.invoke(targetPoint, args);</span><br><span class="line"> aspect.endTrasaction();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"args: "</span> + Arrays.toString(args) + <span class="string">"不能为null "</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="测试类"><a href="#测试类" class="headerlink" title="测试类"></a>测试类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.dynamicproxy.CustomAspect;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.dynamicproxy.IUserService;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.dynamicproxy.JDKDynamicProxyGenerator;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.dynamicproxy.UserServiceImpl;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 基于动态代理类AOP测试案例 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-16:56 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">testAopJDKProxy</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testJDKProxy</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"无代理前 调用方法 userService.saveUser 输出......"</span>);</span><br><span class="line"> IUserService userService = <span class="keyword">new</span> UserServiceImpl();</span><br><span class="line"> userService.saveUser(<span class="string">"zby"</span>, <span class="string">"1234567890"</span>);</span><br><span class="line"> System.out.println(<span class="string">"有代理后AOP 是怎么样的? Proxy......"</span>);</span><br><span class="line"> IUserService proxyUserService = (IUserService) JDKDynamicProxyGenerator</span><br><span class="line"> .generatorJDKProxy(userService, <span class="keyword">new</span> CustomAspect());</span><br><span class="line"> proxyUserService.saveUser(<span class="string">"zby"</span>, <span class="string">"1234567890"</span>); <span class="comment">/** 制造异常,两个入参都是null */</span></span><br><span class="line"> proxyUserService.saveUser(<span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Cglib-aop-实现方式"><a href="#Cglib-aop-实现方式" class="headerlink" title="Cglib aop 实现方式"></a>Cglib aop 实现方式</h3><h4 id="定义切面接口"><a href="#定义切面接口" class="headerlink" title="定义切面接口"></a>定义切面接口</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.cglib;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 定义切面接口,切面接口定义了两个切面方法,分别在切点接口方法执行前和执行后执行 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IAspect</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在切点接口方法执行之前执行</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">startTransaction</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在切点接口方法执行之后执行</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">endTrasaction</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="切面实现"><a href="#切面实现" class="headerlink" title="切面实现"></a>切面实现</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.cglib;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 改类作为AOP 模型中切面角色类, 实现切面接口,切面接口定义了两个切面方法,分别在切点接口方法执行前和执行后执行 。 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomAspect</span> <span class="keyword">implements</span> <span class="title">IAspect</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startTransaction</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"cglib. I get datasource here and start transaction"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">endTrasaction</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"cglib I get datasource here and end transaction"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Cglib-是基于类实现的动态代理即业务类只需要实现类即可,不用强制必须实现某个接口为了突出这个优点这里没有实现接口"><a href="#Cglib-是基于类实现的动态代理即业务类只需要实现类即可,不用强制必须实现某个接口为了突出这个优点这里没有实现接口" class="headerlink" title="Cglib 是基于类实现的动态代理即业务类只需要实现类即可,不用强制必须实现某个接口为了突出这个优点这里没有实现接口"></a>Cglib 是基于类实现的动态代理即业务类只需要实现类即可,不用强制必须实现某个接口为了突出这个优点这里没有实现接口</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.cglib;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.dynamicproxy <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 业务实现类UserServiceImpl 该类 作为AOP中切点角色,切面定义的方法插入到切点的接口方法 执行前和执行后执行。 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveUser</span><span class="params">(String username, String password)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"cglib save user[username="</span> + username + <span class="string">",password="</span> + password + <span class="string">"]"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Cglib-动态代理生成器工具类"><a href="#Cglib-动态代理生成器工具类" class="headerlink" title="Cglib 动态代理生成器工具类"></a>Cglib 动态代理生成器工具类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop.cglib;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.Enhancer;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.MethodInterceptor;</span><br><span class="line"><span class="keyword">import</span> net.sf.cglib.proxy.MethodProxy;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent.aop.cglib <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 基于Cglib代理类生成器工具类 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-17:04 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CglibProxyGenerator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> target 需要被代理的委托类对象,Cglib需要继承该类生成子类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> aspect 切面对象,改对象方法将在切点方法之前或之后执行</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">generatorCglibProxy</span><span class="params">(<span class="keyword">final</span> Object target,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">final</span> IAspect aspect)</span> </span>{ <span class="comment">//3.1 new Enhancer</span></span><br><span class="line"> Enhancer enhancer = <span class="keyword">new</span> Enhancer(); <span class="comment">//3.2 设置需要代理的父类</span></span><br><span class="line"> enhancer.setSuperclass(target.getClass()); <span class="comment">//3.3 设置回调</span></span><br><span class="line"> enhancer.setCallback(<span class="keyword">new</span> MethodInterceptor() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">intercept</span><span class="params">(Object proxy, Method method, Object[] args, MethodProxy methodProxy)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> Throwable </span>{ <span class="comment">// 执行切面方法</span></span><br><span class="line"> aspect.startTransaction(); <span class="comment">// 具体逻辑代码执行,返回值为方法执行结果</span></span><br><span class="line"> Object result = methodProxy.invokeSuper(proxy, args); <span class="comment">// 执行切面方法</span></span><br><span class="line"> aspect.endTrasaction(); <span class="comment">// 返回方法执行结果</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> }); <span class="comment">// 3.4 创建代理对象</span></span><br><span class="line"> <span class="keyword">return</span> enhancer.create();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="测试类-1"><a href="#测试类-1" class="headerlink" title="测试类"></a>测试类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.vincent.aop;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.cglib.CglibProxyGenerator;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.cglib.CustomAspect;</span><br><span class="line"><span class="keyword">import</span> org.vincent.aop.cglib.UserServiceImpl;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Package</span>: org.vincent <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 基于动态代理类AOP测试案例 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: lenovo <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Company</span>: PLCC <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Copyright</span>: Copyright (c) 2019 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0 <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Modified</span> By: <br/></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by lenovo on 2018/12/26-16:56 <br/></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">testAopCglibKProxy</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCglibProxy</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"before Proxy......"</span>);</span><br><span class="line"> UserServiceImpl userService = <span class="keyword">new</span> UserServiceImpl();</span><br><span class="line"> userService.saveUser(<span class="string">"zby"</span>, <span class="string">"1234567890"</span>);</span><br><span class="line"> System.out.println(<span class="string">"引入Cglib Proxy代理库 后......"</span>);</span><br><span class="line"> UserServiceImpl proxyUserService = (UserServiceImpl) CglibProxyGenerator</span><br><span class="line"> .generatorCglibProxy(userService, <span class="keyword">new</span> CustomAspect());</span><br><span class="line"> proxyUserService.saveUser(<span class="string">"zby"</span>, <span class="string">"1234567890"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="AspectJ-实现-AOP-效果"><a href="#AspectJ-实现-AOP-效果" class="headerlink" title="AspectJ 实现 AOP 效果"></a>AspectJ 实现 AOP 效果</h3><p>AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类:</p>
<ul>
<li>静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段通过AOP框架指令生成 AOP 代理类,因此也称为编译时增强;还有一种静态代理是编写代码实现不用工具;这种方式一般是代理模式会使用。</li>
<li>动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。</li>
</ul>
<h4 id="基于-AspectJ-的编译时增强进行-AOP-POM-依赖"><a href="#基于-AspectJ-的编译时增强进行-AOP-POM-依赖" class="headerlink" title="基于 AspectJ 的编译时增强进行 AOP POM 依赖"></a>基于 AspectJ 的编译时增强进行 AOP POM 依赖</h4><p>原生 AspectJ 不依赖Spring案例, 基于 AspectJ 的编译时增强进行 AOP 它是在编译期修改字节码,增强功能;并不会生成新的代理类字节码。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- AspectJ begin--></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.aspectj<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>aspectjrt<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.9.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.aspectj<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>aspectjweaver<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.9.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!-- AspectJ stop--></span></span><br></pre></td></tr></table></figure>
<h2 id="动态代理-使用场景"><a href="#动态代理-使用场景" class="headerlink" title="动态代理 使用场景"></a>动态代理 使用场景</h2><ul>
<li>日志集中打印</li>
<li>事务</li>
<li>权限管理</li>
<li>AOP</li>
</ul>
<blockquote>
<p><strong>原文链接:</strong><a href="https://cloud.tencent.com/developer/article/1461796" target="_blank" rel="noopener">https://cloud.tencent.com/developer/article/1461796</a></p>
</blockquote>
]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Proxy</tag>
<tag>CGlib</tag>
<tag>AOP</tag>
</tags>
</entry>
<entry>
<title>Java线上CPU占用过高问题排查思路</title>
<url>/posts/36f079e3/</url>
<content><![CDATA[<h4 id="一、根据-Java-进程-ID,用-ps-或-top-命令查询出-CPU-占用率高的线程"><a href="#一、根据-Java-进程-ID,用-ps-或-top-命令查询出-CPU-占用率高的线程" class="headerlink" title="一、根据 Java 进程 ID,用 ps 或 top 命令查询出 CPU 占用率高的线程"></a>一、根据 Java 进程 ID,用 <code>ps</code> 或 <code>top</code> 命令查询出 CPU 占用率高的线程</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">ps -mp <pid> -o THREAD,tid,time | sort -rn | more // (sort -rn 已数值的方式进行逆序排列)</span><br><span class="line">// 或top -Hp <pid></span><br><span class="line">top - 08:31:16 up 30 min, 0 users, load average: 0.75, 0.59, 0.35</span><br><span class="line">Threads: 11 total, 1 running, 10 sleeping, 0 stopped, 0 zombie</span><br><span class="line"><span class="meta">%</span><span class="bash">Cpu(s): 3.5 us, 0.6 sy, 0.0 ni, 95.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st</span></span><br><span class="line">KiB Mem: 2046460 total, 1924856 used, 121604 free, 14396 buffers</span><br><span class="line">KiB Swap: 1048572 total, 0 used, 1048572 free. 1192532 cached Mem</span><br><span class="line"></span><br><span class="line"> PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND</span><br><span class="line"> 10 root 20 0 2557160 289824 15872 R 79.3 14.2 0:41.49 java</span><br><span class="line"> 11 root 20 0 2557160 289824 15872 S 13.2 14.2 0:06.78 java</span><br></pre></td></tr></table></figure>
<h4 id="二、转换线程-ID-为-16-进制"><a href="#二、转换线程-ID-为-16-进制" class="headerlink" title="二、转换线程 ID 为 16 进制"></a>二、转换线程 ID 为 16 进制</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">printf "%x\n" <tid></span><br><span class="line">// printf "%x\n" 10</span><br><span class="line">// a</span><br></pre></td></tr></table></figure>
<h4 id="三、利用-JDK-提供的工具-jstack-打印导出线程信息"><a href="#三、利用-JDK-提供的工具-jstack-打印导出线程信息" class="headerlink" title="三、利用 JDK 提供的工具 jstack 打印导出线程信息"></a>三、利用 JDK 提供的工具 <code>jstack</code> 打印导出线程信息</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">jstack <pid> | grep <16tid> -A 30 // 或导出 jstack <pid> >> jstack.txt 文件查看</span><br></pre></td></tr></table></figure>
<h4 id="四、查看线程信息并处理"><a href="#四、查看线程信息并处理" class="headerlink" title="四、查看线程信息并处理"></a>四、查看线程信息并处理</h4><h5 id="4-1-如果是用户线程"><a href="#4-1-如果是用户线程" class="headerlink" title="4.1 如果是用户线程"></a>4.1 如果是用户线程</h5><p><img data-src="/images/pasted-48.png" alt="upload successful"></p>
<p>查看相关代码并处理</p>
<p><strong>附 <code>jstack</code> 死锁日志</strong></p>
<p><img data-src="/images/pasted-47.png" alt="upload successful"></p>
<h5 id="4-2-如果是-Full-GC-次数过多"><a href="#4-2-如果是-Full-GC-次数过多" class="headerlink" title="4.2 如果是 Full GC 次数过多"></a>4.2 如果是 <strong>Full GC</strong> 次数过多</h5><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">"main" #1 prio=5 os_prio=0 tid=0x00007f8718009800 nid=0xb runnable [0x00007f871fe41000]</span><br><span class="line"> java.lang.Thread.State: RUNNABLE</span><br><span class="line"> at com.aibaobei.chapter2.eg2.UserDemo.main(UserDemo.java:9)</span><br><span class="line"></span><br><span class="line">"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable</span><br></pre></td></tr></table></figure>
<p><strong>nid=0xa</strong> 为系统线程 ID<br>使用 JDK 提供的工具 <code>jstat</code> 查看 GC 情况</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">jstat -gcutil <pid> 1000 10</span><br><span class="line">S0 S1 E O M CCS YGC YGCT FGC FGCT GCT</span><br><span class="line">0.00 0.00 0.00 75.07 59.09 59.60 3259 0.919 6517 7.715 8.635</span><br><span class="line">0.00 0.00 0.00 0.08 59.09 59.60 3306 0.930 6611 7.822 8.752</span><br><span class="line">0.00 0.00 0.00 0.08 59.09 59.60 3351 0.943 6701 7.924 8.867</span><br><span class="line">0.00 0.00 0.00 0.08 59.09 59.60 3397 0.955 6793 8.029 8.984</span><br></pre></td></tr></table></figure>
<p>使用 JDK 提供的 <code>jmap</code> 工具导出内存日志到 Eclipse mat工具进行查看</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">// 简单查看存活对象的大小数目</span><br><span class="line">jmap -histo:live <pid> | more</span><br><span class="line">// dump 内存</span><br><span class="line">jmap -dump:live,format=b,file=problem.bin <pid></span><br></pre></td></tr></table></figure>
<p><img data-src="/images/pasted-49.png" alt="upload successful"></p>
<p>主要有以下两种原因:</p>
<ol>
<li>代码中一次获取了大量的对象,导致内存溢出</li>
<li>内存占用不高,但是 Full GC 次数还是比较多,此时可能是显示的 <code>System.gc()</code> 调用导致 GC 次数过多,这可以通过添加 <code>-XX:+DisableExplicitGC</code> 来禁用JVM对显示GC的响应</li>
</ol>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>通过 <code>ps</code> 或 <code>top</code> 命令找出 CPU 过高的线程,将其线程 ID 转换为十六进制,然后在 <code>jstack</code> 日志中查看该线程信息,分为以下两种情况:</p>
<ol>
<li>如果是正常的用户线程,则通过该线程的堆栈信息查看其具体是在哪处用户代码处运行比较消耗 CPU</li>
<li>如果该线程是 <strong>VM Thread</strong> 则通过 <code>jstat -gcutil <pid> <period> <times></code> 命令监控当前系统的 GC 状况,然后通过 <code>jmap dump:format=b,file=<filepath> <pid></code> 导出系统当前的内存数据,导出之后将内存情况放到 eclipse 的 mat 工具中进行分析即可得出内存中主要是什么对象比较消耗内存,进而可以处理相关代码</li>
</ol>
<blockquote>
<p>参考链接:</p>
<ul>
<li><a href="https://blog.csdn.net/baiye_xing/article/details/90483169" target="_blank" rel="noopener">https://blog.csdn.net/baiye_xing/article/details/90483169</a></li>
<li><a href="https://my.oschina.net/zhangxufeng/blog/3017521" target="_blank" rel="noopener">https://my.oschina.net/zhangxufeng/blog/3017521</a></li>
<li><a href="https://www.cnblogs.com/youxin/p/11229071.html" target="_blank" rel="noopener">https://www.cnblogs.com/youxin/p/11229071.html</a></li>
<li><a href="https://www.javatang.com/archives/2017/10/19/33151873.html" target="_blank" rel="noopener">JVM 故障分析及性能优化系列文章</a></li>
</ul>
</blockquote>
]]></content>
<categories>
<category>问题排查</category>
<category>Java</category>
</categories>
<tags>
<tag>Tool</tag>
<tag>Java</tag>
<tag>OOM</tag>
<tag>Full GC</tag>
<tag>JVM command</tag>
</tags>
</entry>
<entry>
<title>MYSQL建表规约</title>
<url>/posts/e0fecff9/</url>
<content><![CDATA[<p>【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint( 1 表示是,0 表示否),此规则同样适用于 odps 建表。<br>说明:任何字段如果为非负数,必须是 unsigned。<br>举例:<code>is_star</code> tinyint unsigned DEFAULT NULL COMMENT ‘项目状态(1 表示是,0 表示否)’</p>
<hr>
<p>【强制】表名、字段名必须使用小写字母或数字;禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。<br>正例:getter_admin,task_config,level3_name<br>反例:GetterAdmin,taskConfig,level_3_name</p>
<hr>
<p>【强制】表名不使用复数名词。<br>说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。</p>
<hr>
<p>【强制】禁用保留字,如 desc、range、match、delayed 等,参考官方保留字。</p>
<hr>
<p>【强制】唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。<br>说明:uk_ 即 unique key;idx_ 即 index 的简称。</p>
<hr>
<p>【强制】小数类型为 decimal,禁止使用 float 和 double。<br>说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。</p>
<hr>
<p>【强制】如果存储的字符串长度几乎相等,使用 CHAR 定长字符串类型。</p>
<hr>
<p>【强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 TEXT,独立出来一张表,用主键来对应,避免影响其它字段索引效率。</p>
<hr>
<p>【强制】表必备三字段:id, gmt_create, gmt_modified。<br>说明:其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1; 分表时改为从TDDL Sequence 取值,确保分表之间的全局唯一。gmt_create, gmt_modified 的类型均为date_time 类型。</p>
<hr>
<p>【推荐】表的命名最好是加上“业务名称_表的作用”,避免上云梯后,再与其它业务表关联时有混淆。<br>正例:tiger_task / tiger_reader / mpp_config</p>
<hr>
<p>【推荐】库名与应用名称尽量一致。</p>
<hr>
<p>【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。</p>
<hr>
<p>【推荐】字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:<br>1)不是频繁修改的字段。<br>2)不是 varchar 超长字段,更不能是 text 字段。<br>正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。</p>
<hr>
<p>【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。<br>说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。<br>反例:某业务三年总数据量才 2 万行,却分成 1024 张表,问:你为什么这么设计?答:分 1024张表,不是标配吗?<br>【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。<br>正例:人的年龄用 unsigned tinyint(表示范围 0-255,人的寿命不会超过 255 岁);海龟就必须是 smallint,但如果是太阳的年龄,就必须是 int;如果是所有恒星的年龄都加起来,那么就必须使用 bigint。</p>
]]></content>
<categories>
<category>数据库</category>
</categories>
<tags>
<tag>MySQL</tag>
</tags>
</entry>
<entry>
<title>MyCLI:一个支持自动补全和语法高亮的MySQL客户端</title>
<url>/posts/2f03f1fe/</url>
<content><![CDATA[<p><img data-src="/images/pasted-19.png" alt="upload successful"><br>MyCLI 是一个易于使用的命令行客户端,可用于受欢迎的数据库管理系统 MySQL、MariaDB 和 Percona,支持自动补全和语法高亮。它是使用 prompt_toolkit 库写的,需要 Python 2.7、3.3、3.4、3.5 和 3.6 的支持。MyCLI 还支持通过 SSL 安全连接到 MySQL 服务器。</p>
<h2 id="MyCLI-的特性"><a href="#MyCLI-的特性" class="headerlink" title="MyCLI 的特性"></a>MyCLI 的特性</h2><ul>
<li>当你第一次使用它的时候,将会自动创建一个文件 ~/.myclirc。</li>
<li>当输入 SQL 的关键词和数据库中的表、视图和列时,支持自动补全。</li>
<li>默认情况下也支持智能补全,能根据上下文的相关性提供补全建议。</li>
</ul>
<p>比如:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">SELECT * FROM <Tab> - 这将显示出数据库中的表名。</span><br><span class="line">SELECT * FROM users WHERE <Tab> - 这将简单的显示出列名称。</span><br></pre></td></tr></table></figure>
<ul>
<li>通过使用 Pygents 支持语法高亮</li>
<li>支持 SSL 连接</li>
<li>提供多行查询支持</li>
<li>它可以将每一个查询和输出记录到一个文件中(默认情况下禁用)。</li>
<li>允许保存收藏一个查询(使用 \fs 别名 保存一个查询,并可使用 \f 别名 运行它)。</li>
<li>支持 SQL 语句执行和表查询计时</li>
<li>以更吸引人的方式打印表格数据</li>
</ul>
<h2 id="如何在-Linux-上为-MySQL-和-MariaDB-安装-MyCLI"><a href="#如何在-Linux-上为-MySQL-和-MariaDB-安装-MyCLI" class="headerlink" title="如何在 Linux 上为 MySQL 和 MariaDB 安装 MyCLI"></a>如何在 Linux 上为 MySQL 和 MariaDB 安装 MyCLI</h2><p>在 Debian/Ubuntu 发行版上,你可以很容易的像下面这样使用 apt 命令 来安装 MyCLI 包:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get install mycli</span><br></pre></td></tr></table></figure>
<p>同样,在 Fedora 22+ 上也有 MyCLI 的可用包,你可以像下面这样使用 dnf 命令 来安装它:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo dnf install mycli</span><br></pre></td></tr></table></figure>
<p>对于其他 Linux 发行版,比如 RHEL/CentOS,你需要使用 Python 的 pip 工具来安装 MyCLI。首先,使用下面的命令来安装 pip:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ sudo yum install pip</span><br></pre></td></tr></table></figure>
<p>安装好 pip 以后,你可以像下面这样安装 MyCLI:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ sudo pip install mycli</span><br></pre></td></tr></table></figure>
<h2 id="在-Linux-中如何使用-MyCLI-连接-MySQL-和-MariaDB"><a href="#在-Linux-中如何使用-MyCLI-连接-MySQL-和-MariaDB" class="headerlink" title="在 Linux 中如何使用 MyCLI 连接 MySQL 和 MariaDB"></a>在 Linux 中如何使用 MyCLI 连接 MySQL 和 MariaDB</h2><p>安装好 MyCLI 以后,你可以像下面这样使用它:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ mycli -u root -h localhost</span><br></pre></td></tr></table></figure>
<h2 id="自动补全"><a href="#自动补全" class="headerlink" title="自动补全"></a>自动补全</h2><p>对于关键词和 SQL 函数可以进行简单的自动补全:</p>
<p><img data-src="/images/pasted-20.png" alt="upload successful"></p>
<h2 id="智能补全"><a href="#智能补全" class="headerlink" title="智能补全"></a>智能补全</h2><p>当输入 FROM 关键词以后会进行表名称的补全:</p>
<p><img data-src="/images/pasted-21.png" alt="upload successful"></p>
<h2 id="别名支持"><a href="#别名支持" class="headerlink" title="别名支持"></a>别名支持</h2><p>当表的名称设置别名以后,也支持列名称的补全:</p>
<p><img data-src="/images/pasted-22.png" alt="upload successful"></p>
<h2 id="语法高亮"><a href="#语法高亮" class="headerlink" title="语法高亮"></a>语法高亮</h2><p>支持 MySQL 语法高亮:</p>
<p><img data-src="/images/pasted-23.png" alt="upload successful"></p>
<h2 id="格式化-SQL-的输出"><a href="#格式化-SQL-的输出" class="headerlink" title="格式化 SQL 的输出"></a>格式化 SQL 的输出</h2><p>MySQL 的输出会通过 less 命令[1] 进行格式化输出:</p>
<p><img data-src="/images/pasted-24.png" alt="upload successful"></p>
<p>要登录 MySQL 并同时选择数据库,你可以使用和下面类似的命令:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ mycli local_database</span><br><span class="line">$ mycli -h localhost -u root app_db</span><br><span class="line">$ mycli mysql://amjith@localhost:3306/django_poll</span><br></pre></td></tr></table></figure>
<p>更多使用选项,请输入:</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ mycli --<span class="built_in">help</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>MyCLI 主页: <a href="http://mycli.net/index" target="_blank" rel="noopener">http://mycli.net/index</a></p>
</blockquote>
]]></content>
<categories>
<category>数据库</category>
</categories>
<tags>
<tag>MySQL</tag>
<tag>Shell</tag>
</tags>
</entry>
<entry>
<title>MySQL常见性能优化</title>
<url>/posts/74b6f95e/</url>
<content><![CDATA[<h2 id="优化Group-By语句"><a href="#优化Group-By语句" class="headerlink" title="优化Group By语句"></a>优化Group By语句</h2><p>默认情况下,MySQL 排序所有GROUP BY col1,col2,….。查询的方法如同在查询中指定ORDER BY col1,col2,…。如果显式包括一个包含相同的列的ORDER BY子句,MySQL 可以毫不减速地对它进行优化,尽管仍然进行排序。如果查询包括GROUP BY 但你想要避免排序结果的消耗,你可以指定ORDER BY NULL禁止排序。</p>
<h2 id="优化Order-by语句"><a href="#优化Order-by语句" class="headerlink" title="优化Order by语句"></a>优化Order by语句</h2><p>在某些情况中,MySQL 可以使用一个索引来满足ORDER BY 子句,而不需要额外的排序。where 条件和order by 使用相同的索引,并且order by 的顺序和索引顺序相同,并且order by 的字段都是升序或者都是降序。</p>
<h2 id="优化insert语句"><a href="#优化insert语句" class="headerlink" title="优化insert语句"></a>优化insert语句</h2><p>如果你同时从同一客户插入很多行,使用多个值表的INSERT 语句。这比使用分开 INSERT 语句快(在一些情况中几倍)。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">mysql> insert into test values(1,2),(1,3),(1,4)…</span><br></pre></td></tr></table></figure>
<p>如果你从不同客户插入很多行,能通过使用INSERT DELAYED 语句得到更高的速度。Delayed 的含义是让insert 语句马上执行,其实数据都被放在内存的队列中,并没有真正的写入磁盘;这比每条语句都分别插入要快的多;LOW_PRIORITY刚好相反,在所有其他用户对表的读写完成后才进行插入。<br>将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项);<br>如果进行批量插入,可以增加bulk_insert_buffer_size 变量值的方法来提高速度,但是,这只能对myisam表使用<br>当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快20倍;<br>根据应用情况使用replace 语句代替insert;<br>根据应用情况使用ignore 关键字忽略重复记录。</p>
<h2 id="大批量插入数据"><a href="#大批量插入数据" class="headerlink" title="大批量插入数据"></a>大批量插入数据</h2><ol>
<li>对于Myisam 类型的表,可以通过以下方式快速的导入大量的数据。<br>ALTER TABLE tblname DISABLE KEYS;<br>这两个命令用来打开或者关闭Myisam 表非唯一索引的更新。在导入大量的数据到一个非空的Myisam 表时,通过设置这两个命令,可以提高导入的效率。对于导入大量数据到一个空的Myisam 表,默认就是先导入数据然后才创建索引的,所以不用进行设置。</li>
<li>而对于Innodb 类型的表,这种方式并不能提高导入数据的效率。对于Innodb 类型的表,我们有以下几种方式可以提高导入的效率:<br>a. 因为Innodb 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果Innodb 表没有主键,那么系统会默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这个优势提高导入数据的效率。<br>b. 在导入数据前执行SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行SETUNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。<br>c. 如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。</li>
</ol>
<h2 id="查询的优化"><a href="#查询的优化" class="headerlink" title="查询的优化"></a>查询的优化</h2><p>读为主可以设置low_priority_updates=1,写的优先级调低,告诉MYSQL尽量先处理读求<br>为查询缓存优化你的查询<br>大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。<br>这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例:<br>// 查询缓存不开启</p>
<h2 id="拆分大的-DELETE-或-INSERT-语句"><a href="#拆分大的-DELETE-或-INSERT-语句" class="headerlink" title="拆分大的 DELETE 或 INSERT 语句"></a>拆分大的 DELETE 或 INSERT 语句</h2><p>如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。<br>Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。<br>如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。<br>所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。</p>