-
Notifications
You must be signed in to change notification settings - Fork 0
/
feed.xml
7559 lines (7425 loc) · 363 KB
/
feed.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"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>JBake</title>
<link>https://blog.yangxiaochen.com</link>
<atom:link href="https://blog.yangxiaochen.com/feed.xml" rel="self" type="application/rss+xml" />
<description>JBake Bootstrap Template</description>
<language>en-gb</language>
<pubDate>星期三, 1 六月 2022 00:10:37 +0800</pubDate>
<lastBuildDate>星期三, 1 六月 2022 00:10:37 +0800</lastBuildDate>
<item>
<title>如何理解 Gradle 构建脚本</title>
<link>https://blog.yangxiaochen.com/blog/2022/0531-understand-gradle-build-script.html</link>
<pubDate>星期二, 31 五月 2022 00:00:00 +0800</pubDate>
<guid isPermaLink="false">blog/2022/0531-understand-gradle-build-script.html</guid>
<description>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_使用显式语法理解构建脚本">使用显式语法理解构建脚本</a>
<ul class="sectlevel2">
<li><a href="#_project_中包含哪些东西">project 中包含哪些东西</a></li>
</ul>
</li>
</ul>
</div>
<div class="sect1">
<h2 id="_使用显式语法理解构建脚本">使用显式语法理解构建脚本</h2>
<div class="sectionbody">
<div class="paragraph">
<p>这些文件来自项目: <a href="https://github.com/yxc023/gradle-practice" class="bare">https://github.com/yxc023/gradle-practice</a></p>
</div>
<div class="paragraph">
<p>每一个 project 下都有一个名为 <code>build.gradle</code> 的构建脚本。</p>
</div>
<div class="paragraph">
<p>每一个 <code>build.gradle</code> 构建脚本背后,都隐含了一个 <code>Project</code> 对象。</p>
</div>
<div class="paragraph">
<p>这个构建脚本中定义的各种属性或者方法,基本都是这个 project 中包含的。比如你可以在脚本中直接使用 <code>Project</code> 接口中定义好的变量和方法。</p>
</div>
<div class="paragraph">
<p>当在构建脚本中显示的指定类型后,可以写成下面这种写法</p>
</div>
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">allprojects { Project p -&gt;
p.group = 'com.yangxiaochen.gradle.practice'
p.version = '1.0.0-SNAPSHOT'
p.apply (plugin: 'java')
p.apply (plugin: 'java-library')
p.apply plugin: 'eclipse'
p.apply plugin: 'idea'
apply plugin: 'pmd'
p.repositories( { RepositoryHandler rh -&gt;
rh.maven( { MavenArtifactRepository m -&gt;
m.url('https://maven.aliyun.com/repository/public')
})
mavenCentral()
})
// 通过 ext 定义一些变量
p.ext {
mysqlVersion = '8.0.18'
jooqVersion = '3.13.1'
jooqGenDataSourceDriver = 'com.mysql.jdbc.Driver'
jooqGenDataSourceUrl = 'jdbc:mysql://127.0.0.1:3306/gp_database'
jooqGenDataSourceUrlUser = 'gp_database_user'
jooqGenDataSourceUrlPassword = 'test'
jooqGenDataSourceInputSchema = 'gp_database'
}
dependencies {
pmd 'com.alibaba.p3c:p3c-pmd:1.3.6'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
p.convention.sourceCompatibility = 1.8
targetCompatibility = 1.8
p.pmd( { PmdExtension pe -&gt;
pe.consoleOutput = true
...
})
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Gradle 目前也支持使用 kotlin 作为构建语言,构建脚本里的语句会更加显式,但我并没有怎么用过。</p>
</div>
<div class="sect2">
<h3 id="_project_中包含哪些东西">project 中包含哪些东西</h3>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>自身的属性和方法</p>
</li>
<li>
<p>tasks - 当前 project 中包含的任务实例。引入一些 plugin 时,也会向 project 中添加 task</p>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// 创建一个名字为 `jooqTask`,类型为 `JooqTask` 的 task
project.tasks.create("jooqTask", JooqTask.class)</code></pre>
</div>
</div>
</li>
<li>
<p>extra property - 通过 ext block 声明的额外变量</p>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">ext {
mysqlVersion = '8.0.18'
jooqVersion = '3.13.1'
jooqGenDataSourceDriver = 'com.mysql.jdbc.Driver'
jooqGenDataSourceUrl = 'jdbc:mysql://127.0.0.1:3306/gp_database'
jooqGenDataSourceUrlUser = 'gp_database_user'
jooqGenDataSourceUrlPassword = 'test'
jooqGenDataSourceInputSchema = 'gp_database'
}</code></pre>
</div>
</div>
</li>
<li>
<p>extensions - 引入一些 plugin 时, 会向 project 中添加一些 extension 对象,并命名。</p>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// 创建一个名字为 `$JOOQ_EXTENSION_NAME`,类型为 `JooqExtension` 的 extension。后面两个参数是 `JooqExtension` 的构造参数
project.extensions.create('jooq', JooqExtension.class, whenConfigurationAdded, 'jooq')</code></pre>
</div>
</div>
<div class="paragraph">
<p>加入 extension 后,就可以在构建脚本中定义</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code>jooq {
version = jooqVersion
edition = 'OSS'
generateSchemaSourceOnCompilation = false
}</code></pre>
</div>
</div>
</li>
<li>
<p>convention - 引入一些 plugin 时,会加入一些 convention object,翻译过来叫‘约定’,‘预定大于配置’的‘约定’。convention object 通常是 POJO,为 project 提供一些拓展属性。</p>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// 这两个变量,在引入 java plugin 之后,就能在构建脚本里够直接定义下面的变量。
sourceCompatibility = 1.8
targetCompatibility = 1.8</code></pre>
</div>
</div>
</li>
</ol>
</div>
<div class="paragraph">
<p>总之,这些 project 中包含的重要信息的作用,它们定义了 <code>build.gradle</code> 中可以写什么 property 或者 block</p>
</div>
<div class="paragraph">
<p>当 <code>build.gradle</code> 中使用了一个 property 或者 block,他的查找顺序是:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>是否是 project 中的属性和方法</p>
<div class="listingblock">
<div class="content">
<pre>// version 这个变量,即是 project.version
version = '1.0.0'</pre>
</div>
</div>
</li>
<li>
<p>是否是 <code>ext</code> 定义的属性</p>
</li>
<li>
<p>是否是 extensions 中的 extension 的名字</p>
</li>
<li>
<p>是否是 convention 中定义的 pojo 中的变量</p>
</li>
<li>
<p>是否是 task 的名字</p>
</li>
<li>
<p>是否在上层 project 的 ext 和 convention 中</p>
</li>
</ol>
</div>
</div>
</div>
</div>
</description>
</item>
<item>
<title>Gradle多模块项目中的配置实践</title>
<link>https://blog.yangxiaochen.com/blog/2022/0504-gradle-practice.html</link>
<pubDate>星期三, 4 五月 2022 00:00:00 +0800</pubDate>
<guid isPermaLink="false">blog/2022/0504-gradle-practice.html</guid>
<description>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_前言">前言</a></li>
<li><a href="#_一个多模块项目的构建脚本例子">一个多模块项目的构建脚本例子</a>
<ul class="sectlevel2">
<li><a href="#_一些公共脚本">一些公共脚本</a>
<ul class="sectlevel3">
<li><a href="#_发布功能gradlepublish_gradle">发布功能:gradle/publish.gradle</a></li>
<li><a href="#_数据库访问代码生成功能gradlejooq_gradle">数据库访问代码生成功能:gradle/jooq.gradle</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<div class="sect1">
<h2 id="_前言">前言</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Gradle 是一个第一眼看上去比较简单,但是有一定入门门槛的构建工具。</p>
</div>
<div class="paragraph">
<p>配置仓库、引入依赖这种基本配置,是比较简单的,也有很多例子。</p>
</div>
<div class="paragraph">
<p>但是想做一些其他的个性化配置,经常无处下手。官方文档,虽然很详实,但是对于没有进行系统学习的人,看起来还挺费劲。而且,文档中给出的例子,跟实际情况脱离较远,不是很好参照。</p>
</div>
<div class="paragraph">
<p>Gradle 的配置文件,大多使用 groovy 来进行书写,弱类型的语言、dsl、closures 的表达方式,都会让很多 java 程序员摸不着头脑。说是用代码来做的配置,但是这种语法缺读不明白。</p>
</div>
<div class="paragraph">
<p>Gradle 简单明了的配置语法、极度灵活的自定义构建配置,是我一直使用它的原因。过程中也遇到了不少问题,虽然我没有对 Gradle 进行特别系统的学习,但也翻过不少文档和源码。</p>
</div>
<div class="paragraph">
<p>本篇文章,是对 Gradle 使用的一些实践记录。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_一个多模块项目的构建脚本例子">一个多模块项目的构建脚本例子</h2>
<div class="sectionbody">
<div class="paragraph">
<p>这些文件来自项目: <a href="https://github.com/yxc023/gradle-practice" class="bare">https://github.com/yxc023/gradle-practice</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre>gradle-practice
├── build.gradle
├── gp-api
│ ├── build.gradle
├── gp-app
│ ├── build.gradle
├── gp-db
│ ├── build.gradle
├── gp-service
│ ├── build.gradle
├── gradle
│ ├── jooq.gradle
│ ├── publish.gradle
│ └── wrapper
├── gradlew
├── gradlew.bat
└── settings.gradle</pre>
</div>
</div>
<div class="listingblock">
<div class="title">settings.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// config plugin repositories
pluginManagement {
repositories {
// aliyun repository for gradle plugin
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
// default repository
gradlePluginPortal()
}
}
// set projects, root dir is root project. Each module is a sub project
rootProject.name = 'gradle-practice'
// include all sub projects. The name is location of the new project in the project hierarchy, for example 'a:b:c', not the file path
// Sub project's default path is the '${rootDir}/${projectName}'.
include 'gp-app'
// use ':' as a separator of project.
include 'gp-api'
include 'gpService'
include 'gp-db'
// Set a custom path for a project
project(':gpService').projectDir = new File(settingsDir, 'gp-service')</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>settings.gradle</code> 中定义所有的项目</p>
</div>
<div class="listingblock">
<div class="title">build.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">/*
为构建脚本引入依赖。
在 「模块 gp-db」 中,我们使用了 jooq 生成代码的 plugin,所以,这里,会先把这个 plugin 的 library 引入进来
*/
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
dependencies {
classpath 'nu.studer:gradle-jooq-plugin:4.2'
}
}
/*
在多模块的项目中,根目录下的 build.gradle 文件,尽量把通用的全局配置都配置到这里
「project」是 gradle 里的核心概念。每个 module 都是一个 project,根目录下是 rootProject
*/
allprojects {
// 定义所有项目的 group 和 version
group = 'com.yangxiaochen.gradle.practice'
version = '1.0.0-SNAPSHOT'
// 为所有项目引入插件
apply plugin: 'java'
apply plugin: 'pmd'
// 为所有项目
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
mavenCentral()
}
// 定义一些变量
ext {
mysqlVersion = '8.0.18'
jooqVersion = '3.13.1'
...
}
// 为所有的项目设置依赖
dependencies {
pmd 'com.alibaba.p3c:p3c-pmd:1.3.6'
}
// Java compiler compile java source file with utf-8 (default gbk in the Windows OS with Simplified Chinese). Java source file must be 'UTF-8'.
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// Set java compile version
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
// Config for every subprojects
subprojects {
// Project gp-api is a library, it will be published as a sdk lib. So it should define exact dependencies in project's build.gradle file
// Define spring framework's core dependencies for most projects.
if (!['gp-api'].contains(project.name)) {
dependencies {
// 'implementation platform' define Spring bom
implementation platform('org.springframework.boot:spring-boot-dependencies:2.1.11.RELEASE')
implementation platform('org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR3')
// Spring framework core dependencies
implementation("org.springframework:spring-context")
implementation("org.springframework:spring-context-support")
...
// Common utils dependencies
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
...
}
}
// Dependency resolve
configurations {
all {
resolutionStrategy {
force 'com.google.guava:guava:28.2-jre'
}
exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>这是一个多模块的项目,通过根项目下的 <code>build.gradle</code> 文件,做好全局配置,让每个子模块中的 <code>build.gradle</code> 足够简单。只需要配置额外的依赖即可,如</p>
</div>
<div class="listingblock">
<div class="title">gb-service/build.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// 只需额外定义该模块所需的依赖
dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
}</code></pre>
</div>
</div>
<div class="sect2">
<h3 id="_一些公共脚本">一些公共脚本</h3>
<div class="paragraph">
<p>对项目中,很多模块都会用到的功能,抽出到一个文件中,使用 include 引入</p>
</div>
<div class="sect3">
<h4 id="_发布功能gradlepublish_gradle">发布功能:gradle/publish.gradle</h4>
<div class="listingblock">
<div class="title">gp-api/build.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">// 每一个需要发布的模块,可以配置这个
apply from: "${rootProject.projectDir}/gradle/publish.gradle"</code></pre>
</div>
</div>
<div class="paragraph">
<p>引入后可以使用 <code>./gradlew :gp-api:publishAllPublicationsToSnapshotRepository</code> 和 <code>./gradlew :gp-api:publishAllPublicationsToReleaseRepository</code> 来发布 gp-api 模块。</p>
</div>
</div>
<div class="sect3">
<h4 id="_数据库访问代码生成功能gradlejooq_gradle">数据库访问代码生成功能:gradle/jooq.gradle</h4>
<div class="listingblock">
<div class="title">gp-db/build.gradle</div>
<div class="content">
<pre class="prettyprint highlight"><code class="language-groovy" data-lang="groovy">ext {
// 设置 jooq 要生成的表
jooqGenIncludeTables = 'table_a|table_b|table_c_*'
// 设置 jooq 生成代码的包
jooqGenPackageName = 'com.yangxiaochen.gradle.practice.db'
}
// 引入 jooq 通用配置,每个需要生成数据库访问代码的模块,都可以引用这个
apply from: "${rootProject.projectDir}/gradle/jooq.gradle"</code></pre>
</div>
</div>
<div class="paragraph">
<p>引入后可以使用 <code>./gradlew generateGp-dbJooqSchemaSource</code> 来生成 gp-db 模块的数据库访问代码</p>
</div>
</div>
</div>
</div>
</div>
</description>
</item>
<item>
<title>spring 中一个 alias 别名导致的 bean 无法找到问题</title>
<link>https://blog.yangxiaochen.com/blog/stackoverflow/bean-not-found-becauseof-alias.html</link>
<pubDate>星期日, 22 三月 2020 00:00:00 +0800</pubDate>
<guid isPermaLink="false">blog/stackoverflow/bean-not-found-becauseof-alias.html</guid>
<description>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_起因">起因</a></li>
<li><a href="#_初步排查">初步排查</a></li>
<li><a href="#_进一步排查">进一步排查</a></li>
<li><a href="#_后记">后记</a></li>
</ul>
</div>
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
本文为原创, 转载请注明出处 <a href="https://blog.yangxiaochen.com" class="bare">https://blog.yangxiaochen.com</a>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_起因">起因</h2>
<div class="sectionbody">
<div class="paragraph">
<p>前几天遇到一个奇怪的问题, spring boot 升级坂本后, 项目中定义的一个 bean 无法被 autowired 获得, 这个 beanName 是 <code>taskExecutor</code>, 是一个项目内自定义的类, 姑且叫 <code>com.a.b.TaskExecutor</code>.</p>
</div>
<div class="paragraph">
<p>仔细检查了 scanPackage 的路径, 发现没有问题, 按道理肯定能加载到才对, 统计目录下的其他 bean 都能扫描到.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_初步排查">初步排查</h2>
<div class="sectionbody">
<div class="paragraph">
<p>找到抛出异常的地方, 打个断点, 往前跟踪了几行代码, 确实是 spring 的 beanFactory 在找类型为 <code>com.a.b.TaskExecutor</code> 的 bean 时找不到.</p>
</div>
<div class="paragraph">
<p>我调用了一下 <code>getBeanDefinitionByName("taskExecutor")</code>, 发现存在一个 beanDefinition, 并且能看到它的 <code>beanClassName = "com.a.b.TaskExecutor"</code></p>
</div>
<div class="paragraph">
<p>我又调用了下 <code>getBeanNamesByType(com.a.b.TaskExecutor.class)</code>, 居然返回 <code>null</code></p>
</div>
<div class="paragraph">
<p>又是"灵异事件"&#8230;&#8203; 我通常把很奇怪的问题叫做灵异事件&#8230;&#8203;</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_进一步排查">进一步排查</h2>
<div class="sectionbody">
<div class="paragraph">
<p>这里绕了一些弯路, 一直在找这个 bean definition 跟其他的 <strong>bean definition (后面简称 bd)</strong> 有什么区别, 造成这两个方法获取时的不同. 发现这个 taskExecutor 的 bd 中, 只有字符串类型的 <code>beanClassName</code>, 没有真正可以标明类型的 <code>resolvedType</code>.</p>
</div>
<div class="paragraph">
<p>那么 <code>resolvedType</code> 是什么时候产生的呢, 发现在 <code>getBeanNameForType()</code> 的过程中, 会便利所有的 bd, 其中有一步, 如果没有 <code>resolvedType</code>, 会进行类型的 resolve.</p>
</div>
<div class="paragraph">
<p>这就奇怪了, 我明明调用了 <code>getBeanNamesByType(com.a.b.TaskExecutor.class)</code>, 为啥还会获取不到.</p>
</div>
<div class="paragraph">
<p>聚焦跟踪 <code>getBeanNamesByType(com.a.b.TaskExecutor.class)</code> 的运行代码.</p>
</div>
<div class="paragraph">
<p>发现在 <code>doGetBeanNameForType()</code> 的方法中, 会对每一个 bd 进行判断, 但是在每个 bd 判断之前, 有一句 <code>if(!isAlias(beanName))</code>, 只有当不是 alias 时, 才做判断. 否则就指向 alias 关联的 bean 的 bd.</p>
</div>
<div class="paragraph">
<p>也就是说, 当一个 bean 由 a, b, c 三个名字时, b 和 c 都是 a 的 alias 别名. 当 <code>doGetBeanNameForType()</code> 中发现 b 是别名, 则使用 a 的 bd 来做类型检测.</p>
</div>
<div class="paragraph">
<p>回到我们的例子中, <code>taskExecutor</code> 被判断是一个别名, 在 aliasMap 里有一个键值对, 指向 <code>applicationTaskExecutor</code>.</p>
</div>
<div class="paragraph">
<p>破案了, 一个名字为 <code>applicationTaskExecutor</code> 的 bean, 用了两个名字, 第二个名字是`taskExecutor`. 这个关系被维护到了一个 aliasMap 中. 这个 bean 的定义位置也在一个 autoconfiguration 的 bean 里找到了, 确实如上所说.</p>
</div>
<div class="paragraph">
<p>而我们自己定义的 <code>taskExecutor</code>, 虽然也添加到了 bdMap 中, 但是在 <code>getBean</code> 的过程中会先检测 aliasMap. 所以即使在 bdMap 中有定义, 也拿不到..</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_后记">后记</h2>
<div class="sectionbody">
<div class="paragraph">
<p>我还没有对比两版 spring 这里的处理, 旧版的 spring boot 框架是没啥问题的. 顺便一提原版本是 2.1.0, 新版本是 2.1.9</p>
</div>
<div class="paragraph">
<p>感觉可以有一个断言, 凡是在 aliasMap 里的别名, 均不应该在 bdMap 里有 bean definition 定义.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
写的时候没有再翻代码, 都是记忆, 以上内容方法名字可能略有出入.
</td>
</tr>
</table>
</div>
</div>
</div>
</description>
</item>
<item>
<title>如何处理 java 代码中的异常</title>
<link>https://blog.yangxiaochen.com/blog/java/how-to-deal-with-java-exception.html</link>
<pubDate>星期五, 17 一月 2020 00:00:00 +0800</pubDate>
<guid isPermaLink="false">blog/java/how-to-deal-with-java-exception.html</guid>
<description>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_前言">前言</a></li>
<li><a href="#_如何判断是否要处理异常">如何判断是否要处理异常</a></li>
<li><a href="#_如何处理异常">如何处理异常</a>
<ul class="sectlevel2">
<li><a href="#_处理非业务异常">处理非业务异常</a></li>
<li><a href="#_处理业务异常">处理业务异常</a></li>
</ul>
</li>
</ul>
</div>
<div class="sect1">
<h2 id="_前言">前言</h2>
<div class="sectionbody">
<div class="paragraph">
<p>对异常我们一般有这么几个处理方式:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>我处理掉, 让上层无感知.</p>
</li>
<li>
<p>对异常进行封装, 封装成一个新的异常, 加入我的解释, 帮上层理解异常.</p>
</li>
<li>
<p>不做处理.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_如何判断是否要处理异常">如何判断是否要处理异常</h2>
<div class="sectionbody">
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">if (当前代码在抛出可预期异常) { <i class="conum" data-value="1"></i><b>(1)</b>
处理异常()
} else if (根据我的分析, 这段代码会发生潜在异常) { <i class="conum" data-value="2"></i><b>(2)</b>
if (需要关心这种异常) { <i class="conum" data-value="3"></i><b>(3)</b>
处理异常()
}
} else {
// 再发生异常时就是我预测不到的, 我统称为 bug. <i class="conum" data-value="4"></i><b>(4)</b>
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>众所周知, java 中异常分为可预期(excepted)异常和 runtime 异常. 为什么会有<strong>可预期异常</strong>这种东西? 帮助经验不足 java 程序员知道这段代码有发生哪些异常</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>比如有网络请求, 有参数校验等, 我知道这段代码可能会在某种可以想到的情况下发生异常</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td><strong>如何判断是否需要关心?</strong> 我当前的程序不需要较好的健壮性, 这个异常也不会影响到别人, <strong>没有人因为这个异常没处理会感到疑惑或者背地里骂我</strong>, 那么我就可以不关心</td>
</tr>
<tr>
<td><i class="conum" data-value="4"></i><b>4</b></td>
<td>我想象到不到会出现什么异常. 当然, 如果发生线上问题, 就要背锅交学费了</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_如何处理异常">如何处理异常</h2>
<div class="sectionbody">
<div class="paragraph">
<p>判断完是否要处理异常, 如果需要处理, 就要看看如何处理异常:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">function 处理异常(ex) {
if (我在非业务逻辑中) { <i class="conum" data-value="1"></i><b>(1)</b>
pass:q[**处理非业务异常(ex)**]
} else if (我在业务逻辑中) {
处理业务异常(ex)
}
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>什么叫非业务异常? 跟业务逻辑无关的中立代码. 比如 <code>发送 HTTP 请求的工具类</code>, <code>初始化项目基础设施的代码</code></td>
</tr>
</table>
</div>
<div class="sect2">
<h3 id="_处理非业务异常">处理非业务异常</h3>
<div class="paragraph">
<p>非业务代码中的异常, 一般是无法原地处理的, 都是要抛给上层, 让真正知道业务的地方去判断出了这种问题该如何处理.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">function 处理非业务异常(ex) {
if (我很确定重试后成功的概率很高) { <i class="conum" data-value="1"></i><b>(1)</b>
do: 考虑尝试 1-2 次
如果成功就 ok, return
}
do: 抛出特定异常, 说明错误原因, 带上原始异常. 最好带上特定 异常 code, 带有结构化的数据能够对异常做详细说明 <i class="conum" data-value="2"></i><b>(2)</b>
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>一般都是不能抢救的. 例外的, 比如创建链接超时这种不确定异常, 可以重试下, 很多 HTTP 请求框架也是有这么做的.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>一个包含了足够信息的异常. <a href="http://blog.yangxiaochen.com/blog/design-and-thinking/expressive-exception-lib.html">一个更有表现里的异常</a></td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_处理业务异常">处理业务异常</h3>
<div class="paragraph">
<p>业务逻辑中处理异常会稍微复杂一些, 到这里已经不是一个技术问题, 而是个业务思维问题了.</p>
</div>
<div class="paragraph">
<p>先看流程吧:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code class="language-java" data-lang="java">function 处理异常(ex) {
if (业务逻辑不需要回滚) {
if (这种异常不需要处理, 或者可以被自动处理) {
do: warn 日志|错误计数
return
} else {
do: error 日志
return
}
}
if (不符合业务逻辑规则, 我可以抛给用户, 可以让用户知道发生了什么, 并且能够理解的) { // 非 bug
do: 抛出 ServiceException, 带上错误 tip. 在用户接口层对这个错误进行处理, 返回提示, 打印 warn 日志, 或进行审计计数.
return
} else if (不符合业务逻辑规则, 但是也没有办法通知用户到用户的) {
return
} else if (系统内部运行不符合预期的) { // bug 或 问题
do: 抛出 ServiceErrorException, tip: 系统错误. 统一 catch 输出异常提示, 打 error 日志
return
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>&lt;1&gt;
&lt;2&gt; 通常是我不能处理的, 因为我不知道这个异常对于整个业务逻辑有着什么影响, 所以一般都是不能抢救的. 例外的, 对于创建链接超时这种不确定异常, 可以重试下, 很多 HTTP 请求框架也是有这么做的.</p>
</div>
<div class="ulist">
<ul>
<li>
<p>warn 日志 - 不太需要关注, 通常可以自动恢复</p>
</li>
<li>
<p>error 日志 - 错误, 需要报警关注</p>
</li>
<li>
<p>异常收集计数 report</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>通常不太需要关注, 但是达到某种阈值会变成问题.</p>
</li>
<li>
<p>需要定期例行关注下.</p>
</li>
</ol>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</description>
</item>
<item>
<title>一个更有表现力的异常设计</title>
<link>https://blog.yangxiaochen.com/blog/design-and-thinking/expressive-exception-lib.html</link>
<pubDate>星期日, 8 十二月 2019 00:00:00 +0800</pubDate>
<guid isPermaLink="false">blog/design-and-thinking/expressive-exception-lib.html</guid>
<description>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_动机">1. 动机</a></li>
<li><a href="#_设计意图">2. 设计意图</a>
<ul class="sectlevel2">
<li><a href="#_message">2.1. message</a></li>
<li><a href="#_code">2.2. code</a></li>
<li><a href="#_tip">2.3. tip</a></li>
<li><a href="#_data">2.4. data</a></li>
<li><a href="#_level">2.5. level</a></li>
</ul>
</li>
<li><a href="#_usage">3. USAGE</a>
<ul class="sectlevel2">
<li><a href="#_引入">3.1. 引入</a></li>
<li><a href="#_使用">3.2. 使用</a></li>
<li><a href="#_拓展异常_level">3.3. 拓展异常 level</a></li>
</ul>
</li>
<li><a href="#_如何处理异常">4. 如何处理异常</a>
<ul class="sectlevel2">
<li><a href="#_spring_mvc">4.1. spring mvc</a></li>
<li><a href="#_dubbo">4.2. dubbo</a></li>
</ul>
</li>
<li><a href="#_意见收集">5. 意见收集</a></li>
</ul>
</div>
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Build an exception with optional fields: <code>code</code>, <code>message</code>, <code>tip</code>, <code>level</code>, <code>data</code>.</p>
</div>
<div class="paragraph">
<p>一个能够包含更多信息的异常基础库. 是一套异常设计和处理的方法论的落地.</p>
</div>
<div class="paragraph">
<p>git 地址: <a href="https://github.com/yxc023/expressive-exception" class="bare">https://github.com/yxc023/expressive-exception</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_动机">1. 动机</h2>
<div class="sectionbody">
<div class="paragraph">
<p>在业务项目实践中, 异常经常用来传递一些业务错误或者警告.</p>
</div>
<div class="paragraph">
<p>通常, 这些业务错误和警告, 经常要包含更多的信息, 比如错误编码, 错误消息. 有时为了给用户更好体验, 还会放入一些便于用户阅读的消息. 甚至, 还会需要一些数据.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_设计意图">2. 设计意图</h2>
<div class="sectionbody">
<div class="paragraph">
<p>在多个业务系统实践中, 我做了一个总结, 一个好用的异常, 要包含以下几个数据:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>message</code> - 异常都会包含的消息</p>
</li>
<li>
<p><code>code</code> - 异常编码</p>
</li>
<li>
<p><code>tip</code> - 异常提示</p>
</li>
<li>
<p><code>data</code> - 可选, 异常携带的数据</p>
</li>
<li>
<p><code>level</code> - 可选, 异常级别</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>下面对每一项进行详细说明.</p>
</div>
<div class="sect2">
<h3 id="_message">2.1. message</h3>
<div class="paragraph">
<p>通常意义下的 exception message, 通常是对异常的描述. 比如当要删除一个订单, 但给的订单号并不存在时:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>Order not found, id: 1001</pre>
</div>
</div>
<div class="paragraph">
<p>通常是英文, 且格式标准专业, 包含了异常相关足够的信息.</p>
</div>
</div>
<div class="sect2">
<h3 id="_code">2.2. code</h3>
<div class="paragraph">
<p>因为业务比较复杂, 异常情况也很多, 我们基本不会对每一种异常设计一个异常类型. 比如在处理订单操作时, 我们只定义一种异常类型: <code>OrderOperationException</code>.</p>
</div>
<div class="paragraph">
<p>那么更细节的异常我们可以通过编码来表示:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>SUCCESS - 成功都是相同的
// 而失败各有不同
FAILURE - 通用的失败编码
ORDER_NOT_FOUND - 订单不存在
ORDER_HAD_PAYED - 订单状态异常: 已经支付过了
...</pre>
</div>
</div>
<div class="paragraph">
<p><code>code</code> 使用字符串, 好处是更易读.</p>
</div>
</div>
<div class="sect2">
<h3 id="_tip">2.3. tip</h3>
<div class="paragraph">
<p><code>tip</code> 和 <code>message</code> 很像, 都是用来表达异常的信息. <code>tip</code> 的设计意图在于<mark>提供用户可读的异常信息</mark>. 比如</p>
</div>
<div class="listingblock">
<div class="content">
<pre>要操作的订单号[1001]不存在.
订单[1001]已经支付过了.</pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_data">2.4. data</h3>
<div class="paragraph">
<p><code>data</code> 的作用是与请求成功响应时返回的数据项对齐.</p>
</div>
<div class="paragraph">
<p>在发生异常时, <code>data</code> 其实并不常用, 场景比较少. 只是在发生异常时需要返回一些关联数据. 举一个场景:</p>
</div>
<div class="paragraph">
<p>当用户购买一个比较抢手的产品时, 有一个购买限制: 一个用户下单后必须支付才能下第二单.</p>
</div>
<div class="paragraph">
<p>那么, 当用户触发这个限制时, 返回的异常中要包含未支付的订单号, 再由统一的异常处理转换成带有 data 的异常返回信息.</p>
</div>
</div>
<div class="sect2">
<h3 id="_level">2.5. level</h3>
<div class="paragraph">
<p>异常为什么要分级? 因为我在业务逻辑中, 所有不符合最常规业务逻辑流程的, 都使用异常来返回.</p>
</div>
<div class="paragraph">
<p>那么有的异常可能并不算是错误. 比如登录时账号密码不匹配, 这并不是系统 bug 引起的错误, 也不需要记录 error 日志, 甚至报警.</p>
</div>
<div class="paragraph">
<p>而有的, 比如逻辑执行中, 某个数据一定应该存在的, 结果没有查询到, 代表着数据完整性异常, 那么这是真真正正的 error.</p>
</div>
<div class="paragraph">
<p>而其他的, 甚至还有说偶尔异常没问题, 大量异常有问题的. 比如客户端断开连接, 偶尔出现很正常, 但大量出现就是有问题的.</p>
</div>
<div class="paragraph">
<p>所以在设计中, 默认将异常 level 分为了两类:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>SERVICE_LEVEL</p>
</li>
<li>
<p>ERROR_LEVEL</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_usage">3. USAGE</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_引入">3.1. 引入</h3>
<div class="listingblock">
<div class="content">
<pre>dependencies {
compile 'com.yangxiaochen:expressive-exception-core:1.2.1-RELEASE'
}</pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_使用">3.2. 使用</h3>
<div class="paragraph">
<p>在 <code>exception-core</code> 中, 提供了 <code>HasTip</code>, <code>HasCode</code>, <code>HasData</code>, <code>HasLevel</code> 几个接口, 你需要定义自己的异常类时:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>public class MyException extends Exception implements HasTip, HasCode, HasData, HasLevel {
...
}</pre>
</div>
</div>
<div class="paragraph">
<p>为了方便定义异常类, 提供了两个抽象类 <code>BaseExprException</code>, <code>BaseExprRuntimeException</code>, 可以直接继承这两个类:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>public class MyException extends BaseExprException {
...
}</pre>
</div>
</div>
<div class="paragraph">
<p>打印出的异常 log 例子:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>com.yangxiaochen.exception.test.application.exception.ServiceRuntimeException: [SERVICE_EXCEPTION] default service exception, tip: 默认业务异常, ctxVars: {fooId=1002, time=Wed Aug 21 18:17:26 CST 2019}
...</pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_拓展异常_level">3.3. 拓展异常 level</h3>
<div class="paragraph">
<p>可以通过实现 <code>ExceptionLevel</code> 来定义新的异常 level.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_如何处理异常">4. 如何处理异常</h2>
<div class="sectionbody">
<div class="paragraph">
<p><mark>异常定义只是一个方面, 如何看待, 解释, 处理我们定义的异常是另一个方面.</mark></p>
</div>
<div class="sect2">
<h3 id="_spring_mvc">4.1. spring mvc</h3>