-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
508 lines (250 loc) · 225 KB
/
atom.xml
File metadata and controls
508 lines (250 loc) · 225 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>江流的个人博客</title>
<icon>https://www.gravatar.com/avatar/13672193897b3dfe5bec9baac0db5740</icon>
<subtitle>一名落魄的Java程序员</subtitle>
<link href="https://jiangliujl.github.io/atom.xml" rel="self"/>
<link href="https://jiangliujl.github.io/"/>
<updated>2023-02-15T09:17:15.065Z</updated>
<id>https://jiangliujl.github.io/</id>
<author>
<name>JiangLiu</name>
<email>1794952398@qq.com</email>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>《SpringBoot常用注解》</title>
<link href="https://jiangliujl.github.io/2023/02/15/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/"/>
<id>https://jiangliujl.github.io/2023/02/15/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/</id>
<published>2023-02-14T16:00:00.000Z</published>
<updated>2023-02-15T09:17:15.065Z</updated>
<content type="html"><![CDATA[<h1 id="SpringCloud项目本身"><a href="#SpringCloud项目本身" class="headerlink" title="SpringCloud项目本身"></a>SpringCloud项目本身</h1><h2 id="iml文件引发的启动报错"><a href="#iml文件引发的启动报错" class="headerlink" title="iml文件引发的启动报错"></a>iml文件引发的启动报错</h2><h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><p>在学习SpringCloud Ribbon的时候,创建了多个微服务项目,并且有一个项目专门给其他项目提供依赖、常量类,但是某一个项目一直启动不起来,一直报<code>没有为模块指定输出路径</code>(常量类模块)的问题。</p><h3 id="报错原因"><a href="#报错原因" class="headerlink" title="报错原因"></a>报错原因</h3><p>比对了报错项目和其他项目的不同之处,发现了创建的iml文件不太一样</p><p>正常项目:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><module version="4"></span><br><span class="line"> <component name="FacetManager"></span><br><span class="line"> <facet type="Spring" name="Spring"></span><br><span class="line"> <configuration /></span><br><span class="line"> </facet></span><br><span class="line"> </component></span><br><span class="line"></module></span><br></pre></td></tr></table></figure><p>异常项目:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><module version="4"></span><br><span class="line"> <component name="NewModuleRootManager"></span><br><span class="line"> <content url="file://$MODULE_DIR$"></span><br><span class="line"> <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /></span><br><span class="line"> <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="true" /></span><br><span class="line"> </content></span><br><span class="line"> </component></span><br><span class="line"></module></span><br></pre></td></tr></table></figure><p>去了解了一下,iml文件是IDEA自动创建的文件,<code>information of module</code>,就是IDEA的项目标识文件,没有这个文件IDEA会无法识别项目(可以去把这个文件删了然后刷新一下maven试试),一般情况下我们不用去理会。</p><h3 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h3><p><del>二般情况下</del>,解决办法也很简单</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><module version="4"></span><br><span class="line"> <component name="NewModuleRootManager"></span><br><span class="line"> <facet type="Spring" name="Spring"></span><br><span class="line"> <configuration /></span><br><span class="line"> </facet></span><br><span class="line"> </component></span><br><span class="line"></module></span><br></pre></td></tr></table></figure><p>把这些内容替换掉就行了</p><p>如果这个方法解决不了,并且你的异常项目没有启动类,尝试重新创建项目,启动一次,再把启动类删掉,初始化一下输出目录</p>]]></content>
<summary type="html"><h1 id="SpringCloud项目本身"><a href="#SpringCloud项目本身" class="headerlink" title="SpringCloud项目本身"></a>SpringCloud项目本身</h1><h2 id="iml文件引发的启动报错"</summary>
<category term="SpringCloud" scheme="https://jiangliujl.github.io/tags/SpringCloud/"/>
</entry>
<entry>
<title>《Java后端面试大全》</title>
<link href="https://jiangliujl.github.io/2023/02/12/Java%E5%BC%80%E5%8F%91%E9%9D%A2%E8%AF%95%E9%A2%98%E6%95%B4%E7%90%86/"/>
<id>https://jiangliujl.github.io/2023/02/12/Java%E5%BC%80%E5%8F%91%E9%9D%A2%E8%AF%95%E9%A2%98%E6%95%B4%E7%90%86/</id>
<published>2023-02-11T16:00:00.000Z</published>
<updated>2023-02-12T04:30:32.134Z</updated>
<content type="html"><![CDATA[<p>JDK、JRE、JVM之间的区别和联系</p><ul><li>JDK是Java语言的软件开发工具包,它提供了Java编译、运行所需的各种工具和资源,包括Java编译器,运行时环境以及常用的Java类库等</li><li>JRE是Java的运行环境,包含了JVM的标准实现和Java的核心类库,并不是开发环境</li><li>JVM是Java运行时的环境,即Java虚拟机,它是Java实现跨平台最核心的部分,负责运行字节码文件</li></ul><hr><p>hashcode()与equals()的联系</p><ul><li>hashcode和equals都可以作为判断两个对象是否相同的判断标准,:<ul><li>如果两个对象的hashcode不同,那两个对象一定不同</li><li>如果hashcode相同,两个对象也不一定相同</li><li>如果两个对象相同,那么它们的hashcode也一定相同</li></ul></li></ul><p>在Java一些集合类或者工具包的视线中,有判断对象是否相等的一些工具函数,里面通常会先去判断hashcode,如果hashcode相同,再去使用equals进行比较</p><hr><p>String、StringBuffer、StringBuilder区别</p><ul><li><p>String、StringBuffer、StringBuilder三个类都提供了</p></li><li><p>在Java中,String作为典型的引用型变量,是不可变的,如果改变变量的值,只会改变堆中指针的指向,并不会改变常量池中的值</p></li><li><p>因此,Java提供了两个可变字符串类StringBuffer和StringBuilder</p></li></ul>]]></content>
<summary type="html"><p>JDK、JRE、JVM之间的区别和联系</p>
<ul>
<li>JDK是Java语言的软件开发工具包,它提供了Java编译、运行所需的各种工具和资源,包括Java编译器,运行时环境以及常用的Java类库等</li>
<li>JRE是Java的运行环境,包含了JVM的标准实</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
</entry>
<entry>
<title>《SpringCloud》——详解Ribbon</title>
<link href="https://jiangliujl.github.io/2022/11/24/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Ribbon/"/>
<id>https://jiangliujl.github.io/2022/11/24/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Ribbon/</id>
<published>2022-11-23T16:00:00.000Z</published>
<updated>2023-02-24T09:18:41.794Z</updated>
<content type="html"><![CDATA[<p><del>Ribbon来喽</del></p><h1 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h1><h1 id="什么是Ribbon"><a href="#什么是Ribbon" class="headerlink" title="什么是Ribbon"></a>什么是Ribbon</h1><p>Ribbon是一个在Cloud中经过实战测试的IPC库,提供以下功能:</p><ul><li>客户端负载均衡</li><li>容错性</li><li>异步和反应模型中的多协议支持(HTTP、TCP、UDP)</li><li>缓存和批处理</li></ul><p>SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具,能够自动基于某种规则去调用这些服务,也可以使用我们自己的负载均衡算法。</p><p>Ribbon虽然是一个组件,但是并不需要像注册中心那样独立部署,它几乎存在于每一个SpringCloud中,因为IPC调用基本都是通过它来实现。</p><h1 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h1><p>负载均衡在微服务架构中是一个很重要的组件,是对系统的高可用、环节网络压力和服务器扩容的重要手段之一。</p><p>负载均衡又分为客户端负载均衡和服务端负载均衡,而我们说的一般是指<strong>服务端负载均衡</strong>,架构如下:</p><p><img src="/img/image-20230213151146518.png" alt="image-20230213151146518"></p><h2 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h2><p>客户端和服务端的负载均衡,最大的不同点就是服务清单存储的位置:</p><ul><li>在客户端负载均衡中,每一个客户端都维护者一份自己要访问的服务清单</li><li>而在服务端中,这些服务器的清单来自于注册中心,比如<em>Eureka</em></li></ul><p>和服务端一样,客户端也需要心跳去维护服务清单的健康性</p><hr><p>在SpringCloud实现的服务治理框架中,默认会对各个服务治理框架的Ribbon自动化整合配置,比如Eureka的<code>org.springframework.cloud.netflix.ribbon.eureka.RibbinEurekaAutoConfiguration</code></p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>通过Spring Cloud Ribbon的封装,我们使用负载均衡会变得很简单:</p><ol><li>服务提供者只需要启动多个服务实例并且注册到一个注册中心或是多个相关联的注册中心</li><li>服务消费者直接通过调用被<code>@LoadBalanced</code>注解修饰过的<code>RestTemplate</code>来实现服务接口的调用</li></ol><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>首先启动多个其他实例,并且注册到注册中心</p><p><img src="/img/image-20230220164517822.png" alt="image-20230220164517822"></p><p>将RestTemplate交给Spring管理,并且加上<code>@LoadBalanced</code>注解用于开启负载均衡</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@LoadBalanced</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用RestTemplate请求服务</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getMethod</span><span class="params">()</span></span>{</span><br><span class="line"> ResponseEntity<String> responseEntity = restTemplate.getForEntity(</span><br><span class="line"> <span class="string">"http://userInfoApplication/v1/user/userInfo/getMethod/我是参数"</span>, String.class</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">return</span> responseEntity.getBody();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><strong>注</strong>:由于Ribbon已经被集成在spring-cloud-starter-netflix-eureka-client中了,不需要单独引入spring-cloud-starter-netflix-ribbon,否则容易造成依赖冲突</p><hr><h1 id="源码剖析"><a href="#源码剖析" class="headerlink" title="源码剖析"></a>源码剖析</h1><p>在使用<code>RestTemplate</code>的时候,我们会发现,这是Spring自己提供的包,和Ribbon并没有什么太大关系,负载均衡是通过<code>@LoadBalanced</code>注解来实现的。</p><p>查看LoadBalanced的源码,里面有一段注释</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Annotation to mark a RestTemplate or WebClient bean to be configured to use a</span><br><span class="line"> * LoadBalancerClient.</span><br><span class="line"> * @author Spencer Gibb</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><p>从注释中可以了解到这个注解是用来给RestTemplate或者WebClient做标记,以使用LoadBalancerClient的。</p><p>再去查看LoadBalancerClient的源码,里面有两个方法:</p><ul><li>T excute(String serviceId, LoadBalancerRequest request):从负载均衡器中挑选出服务实例来执行</li><li>URL reconstructURL:为系统构建合适的URL用来请求</li></ul><p>在老版本的Cloud中,还有一个方法,<code>ServiceInstance choose(String serviceId)</code>,这个方法会根据传入的<code>ServiceId</code>,从服务列表中挑选一个服务实例,但是这个方法在后面被独立到了<code>ServiceInstanceChooser</code>类中</p><p>顺着<code>LoadBalancerClient</code>的包下,可以找到一个<code>LoadBalancerInterceptor</code>类,这里面注入了<code>LoadBalancerClient</code>方法,而<code>LoadBalancerAutoConfiguration</code>类有新建了<code>LoadBalancerInterceptor</code>类,从这个类的注释可以看到,这是用来自动配置客户端负载均衡的</p><p>这个类上面有几个注解:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(RestTemplate.class)</span></span><br><span class="line"><span class="meta">@ConditionalOnBean(LoadBalancerClient.class)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(LoadBalancerClientsProperties.class)</span></span><br></pre></td></tr></table></figure><p>从注解里我们可以知道,实现负载均衡需要满足以下两个条件:</p><ul><li>@ConditionalOnClass(RestTemplate.class):RestTemplate必须在当前的工程环境中</li><li>@ConditionalOnBean(LoadBalancerClient.class):在当前工程中必须有实现了<code>@LoadBalancerClient</code>的Bean</li></ul><p>往下翻可以看到这样一段代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@LoadBalanced</span></span><br><span class="line"><span class="meta">@Autowired(required = false)</span></span><br><span class="line"><span class="keyword">private</span> List<RestTemplate> restTemplates = Collections.emptyList();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> SmartInitializingSingleton <span class="title">loadBalancedRestTemplateInitializerDeprecated</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="keyword">final</span> ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> () -> restTemplateCustomizers.ifAvailable(customizers -> {</span><br><span class="line"><span class="keyword">for</span> (RestTemplate restTemplate : LoadBalancerAutoConfiguration.<span class="keyword">this</span>.restTemplates) {</span><br><span class="line"><span class="keyword">for</span> (RestTemplateCustomizer customizer : customizers) {</span><br><span class="line">customizer.customize(restTemplate);</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="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplateCustomizer <span class="title">restTemplateCustomizer</span><span class="params">(<span class="keyword">final</span> LoadBalancerInterceptor loadBalancerInterceptor)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> restTemplate -> {</span><br><span class="line">List<ClientHttpRequestInterceptor> list = <span class="keyword">new</span> ArrayList<>(restTemplate.getInterceptors());</span><br><span class="line">list.add(loadBalancerInterceptor);</span><br><span class="line">restTemplate.setInterceptors(list);</span><br><span class="line">};</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@Conditional(RetryMissingOrDisabledCondition.class)</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">LoadBalancerInterceptorConfig</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> LoadBalancerInterceptor <span class="title">loadBalancerInterceptor</span><span class="params">(LoadBalancerClient loadBalancerClient,</span></span></span><br><span class="line"><span class="params"><span class="function">LoadBalancerRequestFactory requestFactory)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> LoadBalancerInterceptor(loadBalancerClient, requestFactory);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplateCustomizer <span class="title">restTemplateCustomizer</span><span class="params">(<span class="keyword">final</span> LoadBalancerInterceptor loadBalancerInterceptor)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> restTemplate -> {</span><br><span class="line">List<ClientHttpRequestInterceptor> list = <span class="keyword">new</span> ArrayList<>(restTemplate.getInterceptors());</span><br><span class="line">list.add(loadBalancerInterceptor);</span><br><span class="line">restTemplate.setInterceptors(list);</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>从这里可以看到在该自动化配置中,主要做了三件事:</p><ul><li>创建了一个<code>LoadBalancerInterceptor</code>的Bean,用于实现客户端发起请求时进行拦截,从而实现客户端负载均衡</li><li>创建了一个<code>RestTemplateCustomizer</code>的Bean用于给<code>RestTemplate</code>添加<code>LoadBalancerInterceptor</code>拦截器</li><li>维护了一个被<code>@LoadBalanced</code>注解修饰过的RestTemplate对象列表,并在这里初始化,通过调用<code>RestTemplateCustomizer</code>的实例来给需要客户端负载均衡的<code>RestTemplate</code>添加负载均衡拦截器</li></ul><hr><h1 id="初次使用可能存在的问题"><a href="#初次使用可能存在的问题" class="headerlink" title="初次使用可能存在的问题"></a>初次使用可能存在的问题</h1><h2 id="java-net-UnknownHostException"><a href="#java-net-UnknownHostException" class="headerlink" title="java.net.UnknownHostException"></a>java.net.UnknownHostException</h2><p>初次使用Ribbon做负载均衡,利用RestTemplate请求接口可能会出现这个报错,这可能是因为RestTemplate没有用<code>@LoadBalanced</code>注解进行修饰,这个注解可以开启客户端的负载均衡</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@LoadBalanced</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将RestTemplate封装成一个bean交给Spring进行管理,使用<code>@LoadBalanced</code>注解进行修饰</p><h2 id="No-instances-available-for-userInfoApplication"><a href="#No-instances-available-for-userInfoApplication" class="headerlink" title="No instances available for userInfoApplication"></a>No instances available for userInfoApplication</h2><p>这里可能有几个原因</p><h3 id="原因一"><a href="#原因一" class="headerlink" title="原因一"></a>原因一</h3><p>服务提供方没有注册到注册中心,可以打开注册中心的管理界面看一下</p><p><img src="/img/image-20230217172953716.png" alt="image-20230217172953716"></p><p>比如这里<code>FILEAPPLICATION</code>是服务调用方,<code>USERINFOAPPLICATION</code>是服务提供方,双方都需要注册到注册中心</p><h3 id="原因二"><a href="#原因二" class="headerlink" title="原因二"></a>原因二</h3><p>如果注册中心上有双方的数据了,看一下服务调用方的<code>properties</code>配置文件,有没有把获取服务的配置禁用</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">fetch-registry:</span> <span class="literal">true</span> <span class="comment"># 从注册中心获取注册信息,默认true</span></span><br></pre></td></tr></table></figure><p>这里默认是true,不要改为false</p><h3 id="原因三"><a href="#原因三" class="headerlink" title="原因三"></a>原因三</h3><p>重复引入ribbon造成的依赖冲突,在springcloud中,ribbon已经被集成在<code>spring-cloud-starter-netflix-eureka-client</code>中了,不需要单独引入<code>spring-cloud-starter-netflix-ribbon</code>,springcloud在2020.0.0之后,将依赖改成了<code>spring-cloud-starter-loadbalancer</code></p>]]></content>
<summary type="html"><p><del>Ribbon来喽</del></p>
<h1 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h1><h1 id="什么是Ribbon"><a href="#什么是Ribbon</summary>
<category term="SpringCloud" scheme="https://jiangliujl.github.io/tags/SpringCloud/"/>
</entry>
<entry>
<title>《SpringCloud》——详解Eureka</title>
<link href="https://jiangliujl.github.io/2022/11/17/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Eureka/"/>
<id>https://jiangliujl.github.io/2022/11/17/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Eureka/</id>
<published>2022-11-16T16:00:00.000Z</published>
<updated>2022-11-24T03:44:46.171Z</updated>
<content type="html"><![CDATA[<p>火速滚回来填坑</p><h1 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h1><p>Spring官方对于Eureka的介绍:</p><p>项目源码地址:</p><h1 id="什么是Eureka"><a href="#什么是Eureka" class="headerlink" title="什么是Eureka"></a>什么是Eureka</h1><p>Eureka是一种基于RESTful的服务,用于定位服务,可以说是微服务架构中最核心也是最为基础的模块,主要用于实现各个微服务的自动注册和服务发现,它基于REST服务,采用了CS架构,也就是说,Eureka由两个组件组成:<strong>服务端</strong>(Server)和<strong>客户端</strong>(Client)组成。</p><p>Eureka由Netflix公司开发,SpringCloud对其进行了二次封装用于实现SpringBoot的服务发现和服务注册。</p><h1 id="为什么要用Eureka"><a href="#为什么要用Eureka" class="headerlink" title="为什么要用Eureka"></a>为什么要用Eureka</h1><p>在最开始构建微服务时,我们的服务可能并不多,可以直接通过一些静态配置来完成服务的调用。</p><p>比如,一开始我们有A B两个服务,A服务需要调用B来完成一系列的业务操作,为了实现B服务的高可用,我们对B服务进行了横向拓展,无论是采用服务器负载均衡还是客户端负载均衡,我们都需要维护B服务的具体实例,但是随着业务的发展,我们的服务会越来越多,系统功能也会越来越复杂,静态配置就会变得难以维护,为了解决这种问题,服务治理应运而生。</p><h1 id="服务端与客户端"><a href="#服务端与客户端" class="headerlink" title="服务端与客户端"></a>服务端与客户端</h1><p>Eureka分为服务端和客户端</p><h2 id="服务端"><a href="#服务端" class="headerlink" title="服务端"></a>服务端</h2><p>我们也称为服务的注册中心,如同其他注册中心一样,Eureka也支持高可用配置,依托于一致性提供良好的服务实例可用性,额可以用谷底多种不同的故障场景。</p><h2 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h2><p>主要用于服务的注册与发现,客户端服务通过注解和参数的方式配置,嵌入在客户端应用程序的代码中,在服务运行时,客户端会向注册中心注册自身提供的服务,并且周期性发送心跳来更新它的服务续约,同时也能够从服务端查询当前注册的服务信息并把他们缓存到本地并周期性的刷新服务状态。</p><h1 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h1><h2 id="搭建服务注册中心"><a href="#搭建服务注册中心" class="headerlink" title="搭建服务注册中心"></a>搭建服务注册中心</h2><h3 id="pom依赖"><a href="#pom依赖" class="headerlink" title="pom依赖"></a>pom依赖</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-server<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><h3 id="启动类"><a href="#启动类" class="headerlink" title="启动类"></a>启动类</h3><p>通过<code>@EnableEurekaServer</code>注解启动一个服务注册中心并提供给其他应用进行对话</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JiangEurekaApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(JiangEurekaApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><p>在默认配置下,注册中心会将自己作为客户端尝试注册它自己,所以我们需要禁用他的客户端注册行为,增加如下配置</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"> <span class="attr">hostname:</span> <span class="string">localhost</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://${eureka.instance.hostname}:${server.port}/eureka</span></span><br><span class="line"> <span class="attr">register-with-eureka:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">fetch-registry:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">healthcheck:</span></span><br><span class="line"> <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="配置详解"><a href="#配置详解" class="headerlink" title="配置详解"></a>配置详解</h4><ul><li>eureka<ul><li>instance<ul><li>hostname:主机实例名</li></ul></li><li>client<ul><li>server-url<ul><li>defaultZone:eureka服务器地址,如果注册中心为集群形式,多个注册中心以逗号分隔</li></ul></li><li>register-with-eureka:是否需要向注册中心注册自己</li><li>fetch-register:是否需要向注册中心检索服务</li></ul></li></ul></li></ul><h3 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h3><p>运行好后,可以直接去<code>localhost:8080</code>查看,但是上面还没注册任何服务,如图</p><p><img src="/img/image-20221121160348362.png" alt="image-20221121160348362"></p><h2 id="注册服务提供者"><a href="#注册服务提供者" class="headerlink" title="注册服务提供者"></a>注册服务提供者</h2><h3 id="pom依赖-1"><a href="#pom依赖-1" class="headerlink" title="pom依赖"></a>pom依赖</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><h3 id="启动类-1"><a href="#启动类-1" class="headerlink" title="启动类"></a>启动类</h3><p>同样,Eureka客户端也需要加上一个注解<code>@EnableEurekaClient</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserInfoApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(UserInfoApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="配置文件-1"><a href="#配置文件-1" class="headerlink" title="配置文件"></a>配置文件</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># eureka客户端配置</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"> <span class="attr">hostname:</span> <span class="string">${spring.application.name}</span></span><br><span class="line"> <span class="attr">instance-id:</span> <span class="string">localhost:${spring.application.name}:${server.port}</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment">#启用eureka</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://localhost:8080/eureka</span></span><br></pre></td></tr></table></figure><h4 id="配置详解-1"><a href="#配置详解-1" class="headerlink" title="配置详解"></a>配置详解</h4><p>部分在注册中心的配置中有,这里不再讲述</p><ul><li>eureka<ul><li>instance<ul><li>instance-id:注册到eureka的实例ID</li></ul></li></ul></li></ul><h3 id="运行-1"><a href="#运行-1" class="headerlink" title="运行"></a>运行</h3><p>然后到<code>http://localhost:8080</code>上,可以看到<code>userInfoApplication</code>服务已经注册上去了</p><p><img src="/img/image-20221121143143516.png" alt="image-20221121143143516"></p><center>2022-11-21</center><hr><h1 id="高可用注册中心"><a href="#高可用注册中心" class="headerlink" title="高可用注册中心"></a>高可用注册中心</h1><p>在微服务架构中,我们经常会考虑到一个问题:这个服务挂了怎么办?</p><p>常用的解决办法就是进行高可用部署,就是把这个服务横向拓展,一台不够部署两台,这样的情况下,一台服务挂了,还可以用另一台暂时撑一下</p><p>注册中心既然是微服务架构的核心组件,那我们肯定也需要对其进行高可用部署</p><h2 id="高可用实现"><a href="#高可用实现" class="headerlink" title="高可用实现"></a>高可用实现</h2><p>在Eureka的设计中,每个服务既是服务的提供方,也是服务的消费方,注册中心也不例外,想要对注册中心进行高可用部署,那就要让Eureka也注册到自身</p><p>还记得前面作为单节点的服务注册中心,通过设置下面这两个参数,让服务注册中心不注册自身</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">eureka.client.register-with-eureka</span>=<span class="string">false</span></span><br><span class="line"><span class="meta">eureka.client.fetch-register</span>=<span class="string">false</span></span><br></pre></td></tr></table></figure><p>将Eureka高可用部署,实际上就是将Eureka作为一个服务向集群中的所有实例注册,这样几个Eureka服务就可以形成一个集群,相互同步,相互注册,达到高可用的效果,所以这两个配置当然要改成true</p><p>具体实现:</p><p>首先肯定是要修改配置文件</p><p>别问为什么不新开一个服务,懒</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">application:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">JiangEurekaApplication</span></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="comment"># port: 8761</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8762</span></span><br><span class="line"><span class="comment"># port: 8763</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"><span class="comment"># hostname: Eureka8761</span></span><br><span class="line"> <span class="attr">hostname:</span> <span class="string">Eureka8762</span></span><br><span class="line"><span class="comment"># hostname: Eureka8763</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">register-with-eureka:</span> <span class="literal">true</span> <span class="comment"># 是否注册</span></span><br><span class="line"> <span class="attr">fetch-registry:</span> <span class="literal">true</span> <span class="comment"># 查询获取注册中心服务信息</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="comment"># 注册中心地址,如果有多个Eureka,可以相互注册</span></span><br><span class="line"><span class="comment"># defaultZone: http://Eureka8762:8762/eureka/,http://Eureka8763:8763/eureka/</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://Eureka8761:8761/eureka/,http://Eureka8763:8763/eureka/</span></span><br><span class="line"><span class="comment"># defaultZone: http://Eureka8761:8761/eureka/,http://Eureka8762:8762/eureka/</span></span><br></pre></td></tr></table></figure><p>三台都启动起来,进入<code>localhost:8761</code>,你就能发现诡异的一幕</p><p><img src="/img/image-20221123125000215.png" alt="image-20221123125000215"></p><p>虽然集群是注册上了,但是示例一个都没有,注意我们这里<code>service-url</code>属性,用的并不是<code>localhost</code>或者<code>127.0.0.1</code>,所以要改一下host文件做一下映射</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1 Eureka8761</span><br><span class="line">127.0.0.1 Eureka8762</span><br><span class="line">127.0.0.1 Eureka8763</span><br></pre></td></tr></table></figure><p>host文件加上这三行</p><p>在进入到<code>localhost:8761</code>或者<code>Eureka8761:8761</code></p><p>当然,8762和8763端口也都是一样</p><p><img src="/img/image-20221123125234605.png" alt="image-20221123125234605"></p><p>高可用Eureka注册成功</p><h1 id="Eureka详解"><a href="#Eureka详解" class="headerlink" title="Eureka详解"></a>Eureka详解</h1><p>这里是一些理论的东西,不感兴趣的可以直接跳过</p><h2 id="基础架构"><a href="#基础架构" class="headerlink" title="基础架构"></a>基础架构</h2><p>Eureka的三大核心要素:</p><ul><li>服务注册中心</li><li>服务提供者</li><li>服务消费者</li></ul><p>服务消费者需要配合Ribbon一起使用,下一章再讲</p><p>大多数情况下,服务提供者同时也是服务消费者</p><h2 id="服务通信机制"><a href="#服务通信机制" class="headerlink" title="服务通信机制"></a>服务通信机制</h2><p>来看一下Eureka各个节点之间是如何通信的</p><p><img src="/img/image-20221123151029237.png" alt="image-20221123151029237"></p><p>如图,有两个Eureka服务注册中心,他们相互注册形成了一个高可用集群</p><p>服务提供者拥有两个实例,分别注册在 1 和 2 两台注册中心上,还有两个服务消费者,也分别指向了一个注册中心</p><h3 id="服务注册"><a href="#服务注册" class="headerlink" title="服务注册"></a>服务注册</h3><p>服务提供者在服务启动的时候,会向Eureka发送REST请求,将自己注册到<code>Eureka Server</code>上,同时也会附带一些自身服务的元数据,Eureka接收到这个REST请求后,会将元数据信息存储在一个双层结构Map中,第一层的key是服务名,第二层的key是服务实例名。</p><blockquote><p>注意:配置中eureka.client.register-with-eureka一定要开启,否则服务启动不会进行注册操作</p></blockquote><h3 id="服务同步"><a href="#服务同步" class="headerlink" title="服务同步"></a>服务同步</h3><p>上图中,两个不同的服务提供者分别注册到了两个不同的注册中心,这时,由于两台Eureka服务器之间相互注册,当服务提供者将请求发送到其中一台服务器时,注册请求也会被转发给另一台服务器,从而实现两台Eureka之间的同步</p><h3 id="服务续约"><a href="#服务续约" class="headerlink" title="服务续约"></a>服务续约</h3><p>在服务正常停止时,服务会向Eureka发送一次REST请求,告诉<code>Eureka Service</code>,“我走了,不要想我”,Eureka就会将该服务踢出注册中心,但是为了防止某些极端情况发生,比如服务器非正常关闭,<code>Eureka Server</code>会一直维持着该服务的实例,Eureka提供了服务续约</p><p>服务提供者会维护一个心跳,用来告诉Eureka:“我还活着”,防止Eureka将服务提供者踢出服务列表,这个操作我们成为服务续约,为了达成这个操作,我们需要设置两个重要属性</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr">instance:</span></span><br><span class="line"><span class="attr">lease-renewal-interval-in-seconds:</span> <span class="number">30</span></span><br><span class="line"><span class="attr">lease-expiration-duration-in-seconds:</span> <span class="number">90</span></span><br></pre></td></tr></table></figure><ul><li>lease-renewal-interval-in-seconds:服务续约任务的调用时间间隔,也就是客户端向注册中心发送心跳的时间间隔</li><li>lease-expiration-duration-in-seconds:服务失效时间,注册中心在收到客户端心跳策略后,等待下一次心跳的超时时间,如果超过了这个时间,则移除该客户端</li></ul><h3 id="获取服务"><a href="#获取服务" class="headerlink" title="获取服务"></a>获取服务</h3><p>我们知道在服务启动的时候,服务提供者会发送一个REST请求给注册中心,来注册当前服务,服务消费者也是一样,在启动的时候,会发送一个REST请求,用来获取注册的服务清单。</p><p>同时为了性能考虑,Eureka会维护一份只读的服务清单返给客户端,该清单30s更新一次</p><blockquote><p>注意:获取服务列表是服务消费的前提,确保eureka.client.fetch-register=true</p></blockquote><p>可以通过<code>eureka.client.register-fetch-interval-seconds=30</code>修改缓存清单的更新时间</p><h3 id="服务下线"><a href="#服务下线" class="headerlink" title="服务下线"></a>服务下线</h3><p>当服务提供者停止服务时,会向Eureka发送一个REST请求告诉它,“我要下线了”,而<code>Eureka Server</code>收到请求后会将该客户端踢出服务提供列表。</p><h3 id="自我保护"><a href="#自我保护" class="headerlink" title="自我保护"></a>自我保护</h3><p>我们之前调试Eureka的时候,经常会看到一句红色的</p><blockquote><p><font color="#dd0000">EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE</font></p></blockquote><p>这就是触发了Eureka Server的自我保护机制</p><p>之前我们介绍过客户端会向服务端维护一个心跳连接,在Eureka运行期间,如果15分钟内的心跳连接失败概率低于85%,就会将当前实例保护起来,不让它过期,这种情况在生产上很常见,大多都因为网络连接问题,但是在保护期间内,如果服务真的出问题了,那就会出现调用失败的情况,这时候断路器就很重要了。</p><p>我们可以通过下面这个配置来关闭自我保护</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr">enableself-preservation:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><h1 id="配置详解-2"><a href="#配置详解-2" class="headerlink" title="配置详解"></a>配置详解</h1><h2 id="服务注册类配置"><a href="#服务注册类配置" class="headerlink" title="服务注册类配置"></a>服务注册类配置</h2><p>关于服务注册类的信息,可以在<code>org.springframework.cloud.netflix,eureka.EurekaClientConfigBean</code>里查看详细的内容</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr">client:</span></span><br><span class="line"><span class="attr">enable:</span> <span class="comment">#是否启用Eureka客户端,默认为true</span></span><br><span class="line"><span class="attr">registry-fetch-interval-second:</span> <span class="comment">#从服务端获取注册信息的间隔时间,单位为秒,默认30</span></span><br><span class="line"><span class="attr">instance-info-replication-interval-seconds:</span> <span class="comment">#更新实例信息变化到Eureka服务端的间隔时间,单位为秒,默认30</span></span><br><span class="line"><span class="attr">initial-instance-info-replication-interval-seconds:</span> <span class="comment">#实例初始化信息到服务端的间隔时间,单位为秒,默认40</span></span><br><span class="line"><span class="attr">eureka-service-url-poll-interval-seconds:</span> <span class="comment">#轮询Eureka服务端地址更改的时间间隔,单位为秒,默认300</span></span><br><span class="line"><span class="attr">eureka-server-read-timeout-seconds:</span> <span class="comment">#读取服务端信息的超时时间,单位为秒,默认为8</span></span><br><span class="line"><span class="attr">eureka-server-connect-timeout-seconds:</span> <span class="comment">#连接服务端的超时时间,单位为秒,默认为5</span></span><br><span class="line"><span class="attr">eureka-server-total-connections:</span> <span class="comment">#从客户端到所有服务端的连接总数,默认200</span></span><br><span class="line"><span class="attr">eureka-server-total-connections-per-host:</span> <span class="comment">#从客户端连接到每个Eureka服务端主机的连接总数,默认50</span></span><br><span class="line"><span class="attr">eureka-connection-idle-timeout-seconds:</span> <span class="comment">#服务端连接的空闲关闭时间,单位为秒,默认30</span></span><br><span class="line"><span class="attr">heartbeat-executor-thread-pool-size:</span> <span class="comment">#心跳连接池的初始化线程数,默认为5</span></span><br><span class="line"><span class="attr">heartbeat-executor-exponential-back-off-bound:</span> <span class="comment">#心跳超时重试延迟时间的最大乘数值,默认为10</span></span><br><span class="line"><span class="attr">cache-refresh-executor-thread-pool-size:</span> <span class="comment">#缓存刷新线程池的初始化线程数,默认为2</span></span><br><span class="line"><span class="attr">cache-refresh-executor-exponential-back-off-bound:</span> <span class="comment">#缓存刷新重试延迟时间的最大乘数,默认为10</span></span><br><span class="line"><span class="attr">use-dns-for-fetching-service-urls:</span> <span class="comment">#使用DNS来获取服务端的serviceUrl,默认false</span></span><br><span class="line"><span class="attr">register-with-eureka:</span> <span class="comment">#是否将自身注册到服务端,默认true</span></span><br><span class="line"><span class="attr">prefer-same-zone-eureka:</span> <span class="comment">#是否偏好使用处于相同Zone的服务端,默认true</span></span><br><span class="line"><span class="attr">filter-only-up-instances:</span> <span class="comment">#获取实例是否过滤,仅保留UP状态的实例,默认true</span></span><br><span class="line"><span class="attr">fetch-registry:</span> <span class="comment">#是否从服务端获取注册信息,默认true</span></span><br></pre></td></tr></table></figure><h2 id="服务实例类配置"><a href="#服务实例类配置" class="headerlink" title="服务实例类配置"></a>服务实例类配置</h2><p>关于服务实例类的配置,可以在<code>org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean</code>中查看,所有配置都会在通过这个类进行加载</p><p>在这个Bean中,大部分的配置都是服务元数据的配置</p><blockquote><p>元数据:客户端向注册中心发送注册请求时,用于描述自身服务信息的对象,例如:实例名、实例IP、服务名称、实例端口等</p></blockquote><p>使用Eureka时,配置信息通过<code>EurekaInstanceConfigBean</code>进行加载,但是发送给服务端的时候,还是会将服务信息包装成一个类,在<code>ocom.netflix.eureka.appinfo.InstanceInfo</code>这里。</p><p>我们可以通过<code>eureka.instance.metadataMap.<key>=<value></code>进行配置服务的元数据信息,例如:</p><p><code>eureka.instance.metadataMap.zone=zhejiang</code></p><h3 id="常用元数据配置"><a href="#常用元数据配置" class="headerlink" title="常用元数据配置"></a>常用元数据配置</h3><h4 id="实例名"><a href="#实例名" class="headerlink" title="实例名"></a>实例名</h4><p>即InstanceInfo中的<code>InstanceId</code>项</p><p><code>eureka.instance.instance-id: xxxx</code>通过这个规则进行配置实例名。</p><h4 id="端点配置"><a href="#端点配置" class="headerlink" title="端点配置"></a>端点配置</h4><p>在<code>InstanceInfo</code>中,有一些Url信息,比如:<code>homePageUrl</code>、<code>statusPageUrl</code>、<code>healthCheckUrl</code>,分别代表了应用主页url、状态页url、健康检查url</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"><span class="attr">instance:</span></span><br><span class="line"><span class="attr">status-page-url-path:</span> <span class="string">/xxxxx</span></span><br><span class="line"><span class="attr">health-check-url:</span> <span class="string">/xxx</span></span><br></pre></td></tr></table></figure><p>也可以改成绝对路径</p><h4 id="其他配置"><a href="#其他配置" class="headerlink" title="其他配置"></a>其他配置</h4><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"> <span class="attr">prefer-ip-address:</span> <span class="comment">#是否有限使用IP作为主机名的标识,默认false</span></span><br><span class="line"> <span class="attr">lease-renewal-interval-in-seconds:</span> <span class="comment">#客户端向服务端发送心跳的时间间隔,单位秒,默认30</span></span><br><span class="line"> <span class="attr">lease-expiration-duration-in-seconds:</span> <span class="comment">#服务端在收到最后一次心跳之后等待的上限时间,单位秒,默认90</span></span><br><span class="line"> <span class="attr">non-secure-port:</span> <span class="comment">#非安全的通信端口号,默认80</span></span><br><span class="line"> <span class="attr">secure-port:</span> <span class="comment">#安全通信端口号,默认443</span></span><br><span class="line"> <span class="attr">non-secure-port-enabled:</span> <span class="comment">#是否启用非安全通信端口,默认true</span></span><br><span class="line"> <span class="attr">secure-port-enabled:</span> <span class="comment">#是否启用安全的通信端口</span></span><br><span class="line"> <span class="attr">appname:</span> <span class="comment">#服务名,默认取spring.application.name的配置项,没有则为unknown</span></span><br><span class="line"> <span class="attr">hostname:</span> <span class="comment">#主机名,不配置默认操作系统的主机名</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>火速滚回来填坑</p>
<h1 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h1><p>Spring官方对于Eureka的介绍:</p>
<p>项目源码地址:</p>
<h1 id=</summary>
<category term="SpringCloud" scheme="https://jiangliujl.github.io/tags/SpringCloud/"/>
</entry>
<entry>
<title>《SpringCloud》——Cloud五大组件</title>
<link href="https://jiangliujl.github.io/2022/11/14/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94cloud%E4%BA%94%E5%A4%A7%E7%BB%84%E4%BB%B6/"/>
<id>https://jiangliujl.github.io/2022/11/14/SpringCloud%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94cloud%E4%BA%94%E5%A4%A7%E7%BB%84%E4%BB%B6/</id>
<published>2022-11-13T16:00:00.000Z</published>
<updated>2022-12-09T03:54:24.508Z</updated>
<content type="html"><![CDATA[<h1 id="SpringCloud"><a href="#SpringCloud" class="headerlink" title="SpringCloud"></a>SpringCloud</h1><p>SpringCloud是微服务系统架构的一站式解决方案,基于SpringBoot。</p><p>SpringCloud并不是一个框架,而是一系列框架的集合,包含了世面上较好的微服务框架</p><h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><p>优点:</p><ul><li>耦合度低,各个服务之间不会直接调用,一定程度上减轻了雪崩问题</li><li>降低了开发成本,各个服务之间独立开发</li><li>跨平台,可以用任何一种语言进行开发</li><li>各个服务之间可以使用不同的数据库,也可以使用公用数据库</li></ul><p>缺点:</p><ul><li>由于服务粒度小,维护成本相对比较高</li><li>数据管理比较麻烦</li><li>性能监控比较麻烦</li></ul><h2 id="五大组件"><a href="#五大组件" class="headerlink" title="五大组件"></a>五大组件</h2><p>SpringCloud有五大常用组件:</p><ul><li>注册中心组件,常用:Eureka、nacos</li><li>客户端负载均衡,常用:Ribbon</li><li>断路器组件,常用:Hystrix</li><li>服务网关,常用:Zuul</li><li>分布式配置,常用:SpringCloudConfig、Apollo</li></ul><h2 id="各大组件的作用"><a href="#各大组件的作用" class="headerlink" title="各大组件的作用"></a>各大组件的作用</h2><h3 id="注册中心"><a href="#注册中心" class="headerlink" title="注册中心"></a>注册中心</h3><p>注册中心,用于服务发现与服务注册,为了解耦服务提供者与服务消费者</p><h4 id="透明化路由"><a href="#透明化路由" class="headerlink" title="透明化路由"></a>透明化路由</h4><p>在分布式微服务架构中,各个服务之间需要相互调用,但是不需要通过硬编码的方式去调用服务提供者的服务,而是通过注册中心主动查询和被动通知的方式获取服务提供者的地址信息,消费者只需要知道哪个系统发布了什么服务,不需要清楚服务存在于什么位置,这就叫<strong>透明化路由</strong>。</p><p>为了实现透明化路由,注册中心需要存储服务提供者的信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息</p><hr><p>如果需要了解为什么需要注册中心,可以去看一下微服务的发展历史</p><h3 id="客户端负载均衡"><a href="#客户端负载均衡" class="headerlink" title="客户端负载均衡"></a>客户端负载均衡</h3><p>负载均衡:指单台服务器达到性能瓶颈时,通过横向拓展服务器来增加系统的吞吐量</p><p>客户端负载均衡出现在cloud之后,最常见的就是ribbon,通常和Eureka一起使用,发送请求前,ribbon会去Eureka获取服务端列表,然后通过负载均衡算法选择一个服务器进行访问,即在客户端完成负载均衡分配。</p><center>2022-11-17</center><hr><h3 id="断路器组件"><a href="#断路器组件" class="headerlink" title="断路器组件"></a>断路器组件</h3><h4 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h4><p>在微服务架构中,我们会将业务拆分成一个个服务,服务与服务之间通过RPC相互调用,这里就存在着一个隐患,假设现有A B C三个服务,三个服务的相互调用关系如下</p><p><img src="/img/image-20221117143032424.png" alt="image-20221117143032424"></p><p>A服务需要调用B服务的某个接口,B服务又要调用C服务的某个接口</p><p>某一天,C服务宕机了</p><p><img src="/img/image-20221117144144216.png" alt="image-20221117144144216"></p><p>刚好这时B服务有大量的请求进来了,这些请求都需要去请求C服务,会造成B服务的IO阻塞,于是B服务也宕机了,最终A服务也会宕机,这就是Cloud的雪崩问题</p><h4 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h4><p>引入断路器组件就可以很好的解决这个问题,最常用的断路器就是Hystrix,那么Hystrix如何做到这一点:</p><ul><li>对所有的外部系统调用包装在一个<code>HystrixCommand</code>或<code>HystrixObservableCommand</code>中,该对象通常在单独的线程中执行</li><li>在配置中我们可以指定一个调用阈值,对于超过阈值的调用,会返回失败</li><li>为每个依赖项维护一个小的线程池;如果该线程池已满,发往该依赖的请求将立即被拒绝,而不会排队</li><li>测量成功、失败、超时和线程拒绝</li><li>如果服务的失败百分比超过阈值,则手动或自动触发断路器以停止对这个服务的请求一段时间</li><li>当请求失败、被拒绝、超时或短路时执行回退逻辑</li><li>近乎实时监控指标和配置变化</li></ul><h3 id="服务网关"><a href="#服务网关" class="headerlink" title="服务网关"></a>服务网关</h3><p>网关是微服务架构中的一个关键角色,用于保护、增强和控制对于微服务的访问,网关是一个处于应用程序和微服务之前的系统,用于管理授权、访问控制和流量限制,这样微服务就会被网关保护起来,对于调用者不透明</p><p>作用:</p><ul><li>身份验证和安全性——识别每个资源的身份验证要求,并拒绝不满足的请求</li><li>洞察力和监控——在边缘跟踪有意义的数据和统计数据,以便我们准确了解生产情况</li><li>动态路由——根据需要动态地将请求路由到不同的后端集群</li><li>压力测试——逐渐增加集群的流量以衡量性能</li><li>减轻负载——为每种类型的请求分配容量并丢弃超过限制的请求</li><li>静态响应处理——直接在边缘构建一些响应,而不是将它们转发到内部集群</li><li>多区域弹性——跨AWS区域路由请求,以分散我们的ELB使用</li></ul><h3 id="分布式配置"><a href="#分布式配置" class="headerlink" title="分布式配置"></a>分布式配置</h3><p>相比于其他组件,分布式配置中心很好理解,就是把原来服务中的properties文件分理出自身的系统,并且让这些信息能够被实时获取</p><h4 id="为什么需要分布式配置中心"><a href="#为什么需要分布式配置中心" class="headerlink" title="为什么需要分布式配置中心"></a>为什么需要分布式配置中心</h4><p>使用分布式配置中心,可以在不同节点上设置不同的额皮质,并且对各种配置进行相应的操作。</p><hr><p>当然,SpringCloud并不止这些组件,还有智能路由、微代理、控制总线、全局锁等等</p><p>以后会把这些组件进行详细介绍(先画个饼,能不能填上以后再说)</p>]]></content>
<summary type="html"><h1 id="SpringCloud"><a href="#SpringCloud" class="headerlink" title="SpringCloud"></a>SpringCloud</h1><p>SpringCloud是微服务系统架构的一站式解决方案,基于Spri</summary>
<category term="SpringCloud" scheme="https://jiangliujl.github.io/tags/SpringCloud/"/>
</entry>
<entry>
<title>《Feign远程调用返回对象变成了LinkedHashMap》</title>
<link href="https://jiangliujl.github.io/2022/09/24/feign%E8%B0%83%E7%94%A8%E8%BF%94%E5%9B%9E%E5%AF%B9%E8%B1%A1%E5%8F%98%E6%88%90%E4%BA%86LinkedHashMap/"/>
<id>https://jiangliujl.github.io/2022/09/24/feign%E8%B0%83%E7%94%A8%E8%BF%94%E5%9B%9E%E5%AF%B9%E8%B1%A1%E5%8F%98%E6%88%90%E4%BA%86LinkedHashMap/</id>
<published>2022-09-23T16:00:00.000Z</published>
<updated>2022-09-24T04:04:12.305Z</updated>
<content type="html"><![CDATA[<p>近期在工作中遇到了一个类型转换问题,排查下来发现是由于OpenFeign进行远程调用时,将返回类型变成了LinkedHashMap</p><h1 id="问题复现"><a href="#问题复现" class="headerlink" title="问题复现"></a>问题复现</h1><p>不要纠结于这些名字,只是临时复现用的</p><p>服务提供方</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/9/24 10:05:45</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="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping("/api")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApi</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping("/test")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseDTO <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> User<String> a = <span class="keyword">new</span> User<>();</span><br><span class="line"> a.setAge(<span class="number">18</span>);</span><br><span class="line"> a.setName(<span class="string">"黑牛"</span>);</span><br><span class="line"> a.setTest(<span class="keyword">new</span> ArrayList<>());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseDTO().success(a);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/9/24 11:37:12</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="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResponseDTO</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="keyword">private</span> String code;</span><br><span class="line"> <span class="keyword">private</span> String msg;</span><br><span class="line"> <span class="keyword">private</span> T data;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseDTO <span class="title">failed</span><span class="params">(String msg)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.code = <span class="string">"400"</span>;</span><br><span class="line"> <span class="keyword">this</span>.msg = msg;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseDTO <span class="title">success</span><span class="params">(T data)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.code = <span class="string">"200"</span>;</span><br><span class="line"> <span class="keyword">this</span>.data = data;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="服务消费方"><a href="#服务消费方" class="headerlink" title="服务消费方"></a>服务消费方</h1><p>(由于懒的搞负载均衡,这里直接用url调用)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/9/24 10:10:55</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="meta">@FeignClient(</span></span><br><span class="line"><span class="meta"> name = "DEMO1",</span></span><br><span class="line"><span class="meta"> url = "http://localhost:8081",</span></span><br><span class="line"><span class="meta"> path = "/api",</span></span><br><span class="line"><span class="meta"> fallback = TestClientFallBack.class</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TestClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)</span></span><br><span class="line"> <span class="function">ResponseDTO <span class="title">test</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="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/9/24 10:13:30</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="meta">@RestController</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApi</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Resource</span></span><br><span class="line"> TestClient testClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping("/get")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">get</span><span class="params">()</span></span>{</span><br><span class="line"> log.info(<span class="string">"开始发送请求"</span>);</span><br><span class="line"> ResponseDTO test = testClient.test();</span><br><span class="line"> log.info(<span class="string">"请求返回参数:{}"</span>, test);</span><br><span class="line"> log.info(<span class="string">"返回结果类型:{}"</span>, test.getData().getClass());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="请求结果"><a href="#请求结果" class="headerlink" title="请求结果"></a>请求结果</h1><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">2022-09-24 11:45:27.122 INFO 15656 --- [nio-8082-exec-1] com.example.demo2.Api.TestApi : 请求返回参数:ResponseDTO(code=200, msg=null, data={name=黑牛, age=18, test=[]})</span><br><span class="line">2022-09-24 11:45:27.122 INFO 15656 --- [nio-8082-exec-1] com.example.demo2.Api.TestApi : 返回结果类型:class java.util.LinkedHashMap</span><br><span class="line">2022-09-24 11:46:38.278 INFO 15656 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration</span><br><span class="line">2022-09-24 11:51:38.287 INFO 15656 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration</span><br></pre></td></tr></table></figure><p>可以看到,这时候ResponseDTO中的<code>data</code>类型变成了<code>LinkedHashMap</code></p><h1 id="原因剖析"><a href="#原因剖析" class="headerlink" title="原因剖析"></a>原因剖析</h1><p>由于Data的数据类型是一个泛型,而我们使用的时候又没有指定泛型,所以被直接转换成了LinkedHashMap。</p><p>Feign在服务相互调用时,会出现传递一个复杂对象的情况,这种时候,返回值无法解析,就会被Java默认转换成了<code>LinkedHashMap</code>,如果我们想传递原对象,最好的方法就是带上泛型。</p><p>至于为什么是<code>LinkedHashMap</code>,继续往下看</p><p>Spring中有一个类叫做<code>ModelMap</code>,在源码中也有相应的介绍</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">This class serves as generic model holder for Servlet MVC but is not tied to it. Check out the Model interface for an interface variant.</span><br></pre></td></tr></table></figure><p>它是MVC的通用模型持有者,换句话说,任何一个接口返回对象都能够转换成ModelMap,而它继承了<code>LinkedHashMap</code></p><p><strong>注意</strong>:LinkedHashMap是没有泛型的,不管返回的是一个多复杂的对象,什么类型的Map,都能够直接用<code>ModelMap</code>来接收他的结果。</p>]]></content>
<summary type="html"><p>近期在工作中遇到了一个类型转换问题,排查下来发现是由于OpenFeign进行远程调用时,将返回类型变成了LinkedHashMap</p>
<h1 id="问题复现"><a href="#问题复现" class="headerlink" title="问题复现"></a>问</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
</entry>
<entry>
<title>《正则表达式》</title>
<link href="https://jiangliujl.github.io/2022/08/28/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<id>https://jiangliujl.github.io/2022/08/28/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</id>
<published>2022-08-27T16:00:00.000Z</published>
<updated>2022-08-31T06:36:36.108Z</updated>
<content type="html"><![CDATA[<h1 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h1><p>今天来聊一聊让程序员又爱又恨的正则表达式</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>正则表达式是一种进行模式匹配和文本操作的复杂而又强大的工具,虽然比固定字符匹配要慢,但是胜在灵活,本文中以<code>Go</code>做介绍</p><h2 id="语法规则"><a href="#语法规则" class="headerlink" title="语法规则"></a>语法规则</h2><h3 id="字符"><a href="#字符" class="headerlink" title="字符"></a>字符</h3><table><thead><tr><th>语法</th><th>说明</th><th>表达式示例</th><th>匹配结果</th></tr></thead><tbody><tr><td>一般字符</td><td>匹配自身</td><td>abc</td><td>abc</td></tr><tr><td>.</td><td>匹配任意除换行符”\n”外的字符, 在 DOTALL 模式中也能匹配换行符</td><td>a.c</td><td>abc</td></tr><tr><td>\</td><td>转义字符,使后一个字符改变原来的意思; 如果字符串中有字符 * 需要匹配,可以使用 * 或者字符集[*]。</td><td>a.c a\c</td><td>a.c a\c</td></tr><tr><td>[…]</td><td>字符集(字符类),对应的位置可以是字符集中任意字符。 字符集中的字符可以逐个列出,也可以给出范围,如 [abc] 或 [a-c], 第一个字符如果是 ^ 则表示取反,如 [^abc] 表示除了abc之外的其他字符。</td><td>a[bcd]e</td><td>abe 或 ace 或 ade</td></tr><tr><td>\d</td><td>数字:[0-9]</td><td>a\dc</td><td>a1c</td></tr><tr><td>\D</td><td>非数字:[^\d]</td><td>a\Dc</td><td>abc</td></tr><tr><td>\s</td><td>空白字符:[<空格>\t\r\n\f\v]</td><td>a\sc</td><td>a c</td></tr><tr><td>\S</td><td>非空白字符:[^\s]</td><td>a\Sc</td><td>abc</td></tr><tr><td>\w</td><td>单词字符:[A-Za-z0-9]</td><td>a\wc</td><td>abc</td></tr><tr><td>\W</td><td>非单词字符:[^\w]</td><td>a\Wc</td><td>a c</td></tr></tbody></table><h3 id="数量词"><a href="#数量词" class="headerlink" title="数量词"></a>数量词</h3><table><thead><tr><th>语法</th><th>说明</th><th>表达式示例</th><th>匹配结果</th></tr></thead><tbody><tr><td>*</td><td>匹配前一个字符 0 或无限次</td><td>abc*</td><td>ab 或 abccc</td></tr><tr><td>+</td><td>匹配前一个字符 1 次或无限次</td><td>abc+</td><td>abc 或 abccc</td></tr><tr><td>?</td><td>匹配前一个字符 0 次或 1 次</td><td>abc?</td><td>ab 或 abc</td></tr><tr><td>{m}</td><td>匹配前一个字符 m 次</td><td>ab{2}c</td><td>abbc</td></tr><tr><td>{m,n}</td><td>匹配前一个字符 m 至 n 次,m 和 n 可以省略,若省略 m,则匹配 0 至 n 次; 若省略 n,则匹配 m 至无限次</td><td>ab{1,2}c</td><td>abc 或 abbc</td></tr></tbody></table><h3 id="边界匹配"><a href="#边界匹配" class="headerlink" title="边界匹配"></a>边界匹配</h3><table><thead><tr><th>语法</th><th>说明</th><th>表达式示例</th><th>匹配结果</th></tr></thead><tbody><tr><td>^</td><td>匹配字符串开头,在多行模式中匹配每一行的开头</td><td>^abc</td><td>abc</td></tr><tr><td>$</td><td>匹配字符串末尾,在多行模式中匹配每一行的末尾</td><td>abc$</td><td>abc</td></tr><tr><td>\A</td><td>仅匹配字符串开头</td><td>\Aabc</td><td>abc</td></tr><tr><td>\Z</td><td>仅匹配字符串末尾</td><td>abc\Z</td><td>abc</td></tr><tr><td>\b</td><td>匹配 \w 和 \W 之间</td><td>a\b!bc</td><td>a!bc</td></tr><tr><td>\B</td><td>[^\b]</td><td>a\Bbc</td><td>abc</td></tr></tbody></table><h3 id="特殊构造"><a href="#特殊构造" class="headerlink" title="特殊构造"></a>特殊构造</h3><table><thead><tr><th>语法</th><th>说明</th><th>表达式示例</th><th>匹配结果</th></tr></thead><tbody><tr><td>(?:…)</td><td>(…) 的不分组版本,用于使用 “|” 或后接数量词</td><td>(?:abc){2}</td><td>abcabc</td></tr><tr><td>(?iLmsux)</td><td>iLmsux 中的每个字符代表一种匹配模式,只能用在正则表达式的开头,可选多个</td><td>(?i)abc</td><td>AbC</td></tr><tr><td>(?#…)</td><td># 后的内容将作为注释被忽略。</td><td>abc(?#comment)123</td><td>abc123</td></tr><tr><td>(?=…)</td><td>之后的字符串内容需要匹配表达式才能成功匹配</td><td>a(?=\d)</td><td>后面是数字的 a</td></tr><tr><td>(?!…)</td><td>之后的字符串内容需要不匹配表达式才能成功匹配</td><td>a(?!\d)</td><td>后面不是数字的 a</td></tr><tr><td>(?<=…)</td><td>之前的字符串内容需要匹配表达式才能成功匹配</td><td>(?<=\d)a</td><td>前面是数字的a</td></tr><tr><td>(?<!…)</td><td>之前的字符串内容需要不匹配表达式才能成功匹配</td><td>(?<!\d)a</td><td>前面不是数字的a</td></tr></tbody></table><h3 id="Go演示"><a href="#Go演示" class="headerlink" title="Go演示"></a>Go演示</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"regexp"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">buf := <span class="string">"abc azc a7c aac 888 a9c tac"</span></span><br><span class="line">regex := regexp.MustCompile(<span class="string">`a.c`</span>)</span><br><span class="line">resutl := regex.FindAllStringSubmatch(buf, <span class="number">-1</span>)</span><br><span class="line">fmt.Println(resutl)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[[abc] [azc] [a7c] [aac] [a9c]]</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h1><p>今天来聊一聊让程序员又爱又恨的正则表达式</p>
<h2 id="简介"><a href="#简介" class="</summary>
<category term="每天写点啥" scheme="https://jiangliujl.github.io/tags/%E6%AF%8F%E5%A4%A9%E5%86%99%E7%82%B9%E5%95%A5/"/>
</entry>
<entry>
<title>《Java流操作》——读书笔记</title>
<link href="https://jiangliujl.github.io/2022/08/10/Java%E6%B5%81%E6%93%8D%E4%BD%9C/"/>
<id>https://jiangliujl.github.io/2022/08/10/Java%E6%B5%81%E6%93%8D%E4%BD%9C/</id>
<published>2022-08-09T16:00:00.000Z</published>
<updated>2022-09-05T08:41:15.138Z</updated>
<content type="html"><![CDATA[<h1 id="从迭代到流的操作"><a href="#从迭代到流的操作" class="headerlink" title="从迭代到流的操作"></a>从迭代到流的操作</h1><p>处理集合时,我们常常会遍历他们的元素,然后对其中的元素做一些操作,例如我们如果想从一个文件中读取一个字符串,然后用非字母对他们进行分割,我们一般会这么干</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">String contents = Files.readString(Paths.get(<span class="string">"D:/001_program/JavaProgram/JavaBase/src/main/java/核心技术卷II/JavaSe8的流库/从迭代到流的操作/alice.txt"</span>));</span><br><span class="line"><span class="comment">// 非字母分隔符</span></span><br><span class="line">List<String> words = Arrays.asList(contents.split(<span class="string">"\\PL+"</span>));</span><br><span class="line">System.out.println(words);</span><br><span class="line"><span class="keyword">for</span> (String word : words) {</span><br><span class="line"> System.out.println(word);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,我们可以尝试用流的操作替代循环</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> String contents = Files.readString(Paths.get(<span class="string">"D:/001_program/JavaProgram/JavaBase/src/main/java/核心技术卷II/JavaSe8的流库/从迭代到流的操作/alice.txt"</span>));</span><br><span class="line"> <span class="comment">// 非字母分隔符</span></span><br><span class="line"> List<String> words = Arrays.asList(contents.split(<span class="string">"\\PL+"</span>));</span><br><span class="line"> System.out.println(words);</span><br><span class="line"><span class="comment">// for (String word : words) {</span></span><br><span class="line"><span class="comment">// System.out.println(word);</span></span><br><span class="line"><span class="comment">// }</span></span><br><span class="line"> <span class="keyword">long</span> count1 = words.stream().filter(Objects::nonNull).count();</span><br><span class="line"> System.out.println(count1);</span><br></pre></td></tr></table></figure><p>就像这样,这比循环更加简洁</p><h2 id="stream和parallelStream"><a href="#stream和parallelStream" class="headerlink" title="stream和parallelStream"></a>stream和parallelStream</h2><p>在Java中,这两种都是流操作,但是它们两个有一些区别</p><ul><li>stream是串行流,也就是说stream流中的元素是一个一个顺序执行的</li><li>而parallelStream是并行流,可以以并行方式来进行过滤和计数</li></ul><p>至于并行流,在后面会详细介绍</p><p>流操作表面上和集合是非常相似的,但是它们有着非常显著的差异:</p><ul><li>流操作并不存储元素。这些元素储存在底层的集合中按需生成。</li><li>流操作并不会修改数据源,而是产生一个新的流</li><li>流的操作是尽可能惰性执行的。比如我们想要查找前五个长单词而不是所有长单词,那么流就会在匹配到第五个长单词时停止过滤,因此按理说我们可以操作无限流。</li></ul><h2 id="章节总结"><a href="#章节总结" class="headerlink" title="章节总结"></a>章节总结</h2><p>流操作API:</p><ul><li>filter(Predicate<? super T> P):产生一个流,其中包含所有满足P的元素</li><li>count():计算当前流中元素的数量</li></ul><p>流的种类:</p><ul><li>stream:串行流</li><li>parallelStream:并行流</li></ul><h1 id="流的创建"><a href="#流的创建" class="headerlink" title="流的创建"></a>流的创建</h1><p>我们已经知道用Collection类的stream方法可以将一个集合转化为流,当然,不止集合,数组也可以</p><p>对于一个数组,我们可以使用Stream.of方法将数组转变为流</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>[] a = <span class="keyword">new</span> <span class="keyword">int</span>[]{<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">8</span>};</span><br><span class="line">Stream<<span class="keyword">int</span>[]> a1 = Stream.of(a);</span><br></pre></td></tr></table></figure><p>of方法用的是可变参数,所以我们可以传入任意长度的数组</p><p>但是更加推荐Array.stream(array, from, to)方法,可以从数组的from到to创建一个流对象</p><p>如果想产生一个空的流,可以使用<code>Stream.empty()</code>方法</p><h2 id="为什么推荐Array-stream"><a href="#为什么推荐Array-stream" class="headerlink" title="为什么推荐Array.stream"></a>为什么推荐Array.stream</h2><p>对于对象类型的数组,Array.stream和Stream.of虽然有同样的返回,但是对于基本类型的数组,Stream.of.count返回的数组永远是1</p><p>对于这点可以自行验证。</p><h2 id="无限流"><a href="#无限流" class="headerlink" title="无限流"></a>无限流</h2><p>在Java8的流库中,有两个创建无限流的方法,generate和iterator</p><p>详细可以从另一篇博客《Java流中的generate与iterator》了解</p><h3 id="iterator"><a href="#iterator" class="headerlink" title="iterator"></a>iterator</h3><p>iterator需要我们传入两个参数,<code>seed</code>和<code>initial element seed</code>,一个是初始值,一个是产生无限流的依据</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Stream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)).limit(<span class="number">100</span>);</span><br><span class="line">System.out.println(Arrays.toString(stream.filter(n -> n.compareTo(<span class="keyword">new</span> BigInteger(String.valueOf(<span class="number">170L</span>))) < <span class="number">0</span>).toArray()));</span><br></pre></td></tr></table></figure><h3 id="generate"><a href="#generate" class="headerlink" title="generate"></a>generate</h3><p>generate则需要我们传入一个Supplier对象,里面可以定义规则,比iterator更加灵活</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Stream<Integer> stream1 = Stream.generate(<span class="keyword">new</span> Supplier<Integer>() {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">int</span> a = <span class="number">0</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> Integer <span class="title">get</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a++;</span><br><span class="line"> }</span><br><span class="line"> }).limit(<span class="number">20</span>);</span><br><span class="line"> System.out.println(Arrays.toString(stream1.toArray()));</span><br></pre></td></tr></table></figure><p>或者说,generate可以根据多个元素制定规则,而iterator只能根据一个元素</p><p>在Java中,产生流的方式还有很多,比如Pattern.splitStream()、Files.lines()</p><p>Pattern.splitStream可以根据正则表达式来分割字符串形成一个流</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Pattern.complie(<span class="string">"\\PL+"</span>).splitAsStream(content)</span><br></pre></td></tr></table></figure><p>而Files.lines(path)方法则可以返回一个包含文件中所有行的流</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>(Stream<String> lines = Files.lines(path)){</span><br><span class="line"> <span class="comment">// Process line</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="章节总结-1"><a href="#章节总结-1" class="headerlink" title="章节总结"></a>章节总结</h2><p>操作流API:</p><ul><li>of(T… values):根据给定数组产生一个流</li><li>empty():产生一个空的流</li><li>generate():产生一个无限流</li><li>iterator():产生一个无限流</li></ul><p>java.util.Arrays:</p><ul><li>stream(T[] Arrays, int start, int end):根据数组创建一个流</li></ul><p>java.util.regex.Pattern:</p><ul><li>splitAsStream(CharSequence input):根据input产生一个流</li></ul><p>java.nio.file.Files:</p><ul><li>stream(Path path, [Charset c]):将指定文件中的行转化为流,并且可以设置指定字符集</li></ul><p>java.util.function.Supplier:</p><ul><li>get():提供一个值,用于产生无限流</li></ul><h1 id="filter、map和flatMap"><a href="#filter、map和flatMap" class="headerlink" title="filter、map和flatMap"></a>filter、map和flatMap</h1><h2 id="filter"><a href="#filter" class="headerlink" title="filter"></a>filter</h2><p>filter可以从一个流中转换出一个流,其中的元素遵循某种规则,可以在<code>filter()</code>括号中定义这个规则,比如</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">List<String> words = ...;</span><br><span class="line">Stream<String> longWords = wordList.stream().filter(w -> w.length > <span class="number">12</span>)</span><br></pre></td></tr></table></figure><p>这样这个流中就会只包含长度大于12的单词</p><p>filter更像是从一个流中筛选元素,组成另一个流</p><h2 id="map"><a href="#map" class="headerlink" title="map"></a>map</h2><p>相比于filter,map虽然也是产生一个新的流,但是map是将原来的流中的元素进行转换,比如将words中的单词全部小写</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stream<String> words = wordList.stream().map(String::toLowerCase);</span><br></pre></td></tr></table></figure><p>或者你可以自定义一个函数,如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> 核心技术卷II.JavaSe8的流库.流方法;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.Collectors;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.Stream;</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> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/8/10 21:24:52</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">map</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>{</span><br><span class="line"> List<String> words = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> words.add(<span class="string">"Abc"</span>);</span><br><span class="line"> words.add(<span class="string">"Bcd"</span>);</span><br><span class="line"> words.add(<span class="string">"Java"</span>);</span><br><span class="line"> words.add(<span class="string">"GoLang"</span>);</span><br><span class="line"> words.add(<span class="string">"Rust"</span>);</span><br><span class="line"> List<String> collect = words.stream().map(String::toLowerCase).collect(Collectors.toList());</span><br><span class="line"> System.out.println(collect);</span><br><span class="line"></span><br><span class="line"> List<List<String>> collect1 = words.stream().map(w -> myMap(w).collect(Collectors.toList())).collect(Collectors.toList());</span><br><span class="line"> System.out.println(collect1);</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">static</span> Stream<String> <span class="title">myMap</span><span class="params">(String s)</span></span>{</span><br><span class="line"> List<String> list = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < s.length(); i++){</span><br><span class="line"> list.add(s.substring(i, i + <span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> list.stream();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">[abc, bcd, java, golang, rust]</span><br><span class="line">[[A, b, c], [B, c, d], [J, a, v, a], [G, o, L, a, n, g], [R, u, s, t]]</span><br></pre></td></tr></table></figure><p>但是这样每个单词都是一个List,我们可能并不想出现这种情况,这时候flatMap方法就派上用场了</p><h2 id="flatMap"><a href="#flatMap" class="headerlink" title="flatMap"></a>flatMap</h2><p>flatMap可以将当前流中的所有元素拼接到一起返回</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List<String> collect2 = words.stream().flatMap(w -> myMap(w)).collect(Collectors.toList());</span><br><span class="line"> System.out.println(collect2);</span><br><span class="line">[A, b, c, B, c, d, J, a, v, a, G, o, L, a, n, g, R, u, s, t]</span><br></pre></td></tr></table></figure><p>返回一个流,其中包含将<strong>该流的每个元素</strong>替换为将提供的映射函数应用到每个元素所产生的映射流的内容的结果。每个被映射的流在其内容被放置到这个流之后被关闭。</p><p>而map是单个元素</p><h2 id="章节总结-2"><a href="#章节总结-2" class="headerlink" title="章节总结"></a>章节总结</h2><p>java.util.Stream:</p><ul><li>filter:产生一个流,其中包含当前流中所有满足条件的元素</li><li>map(Function<? super T> mapper):产生一个流,其中包含将mapper应用于流中的每一个元素所产生的结果</li><li>flatMap(Function mapper):产生一个流,其中包含将mapper应用于流中每一个元素所产生的结果的组合</li></ul><h1 id="抽取子流和连接流"><a href="#抽取子流和连接流" class="headerlink" title="抽取子流和连接流"></a>抽取子流和连接流</h1><h2 id="抽取流"><a href="#抽取流" class="headerlink" title="抽取流"></a>抽取流</h2><p>在介绍无限流的时候,有一个API叫<code>limit()</code>,这个API会产生一个新的流,并且在流运行到第n个元素时结束,对裁剪无限流非常好用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">List<Double> collect = Stream.generate(Math::random).limit(<span class="number">5</span>).collect(Collectors.toList());</span><br><span class="line">System.out.println(collect);</span><br></pre></td></tr></table></figure><p>这个流就只包含五个随机数</p><p>而<code>stream.skip(long n)</code>API正好相反,这个API会在跳过前n个元素,对后面的元素进行截取。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">List<String> words = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> words.add(<span class="string">"Abc"</span>);</span><br><span class="line"> words.add(<span class="string">"Bcd"</span>);</span><br><span class="line"> words.add(<span class="string">"Java"</span>);</span><br><span class="line"> words.add(<span class="string">"GoLang"</span>);</span><br><span class="line"> words.add(<span class="string">"Rust"</span>);</span><br><span class="line"> List<String> collect1 = words.stream().skip(<span class="number">2</span>).collect(Collectors.toList());</span><br><span class="line"> System.out.println(collect1);</span><br><span class="line"></span><br><span class="line">[Java, GoLang, Rust]</span><br></pre></td></tr></table></figure><p>返回的流跳过了前两个元素</p><h2 id="连接流"><a href="#连接流" class="headerlink" title="连接流"></a>连接流</h2><p>如果想要将两个Luis连接起来,可以使用<code>stream.contact(Stream a, Stream b)</code>将两个流进行连接</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">List<Object> collect2 = Stream.concat(collect.stream(), collect1.stream()).collect(Collectors.toList());</span><br><span class="line"> System.out.println(collect2);</span><br><span class="line"></span><br><span class="line">[<span class="number">0.19283510014488758</span>, <span class="number">0.986608060762175</span>, <span class="number">0.07406377892420113</span>, <span class="number">0.6737128935974602</span>, <span class="number">0.5210312651857187</span>, Java, GoLang, Rust]</span><br></pre></td></tr></table></figure><h2 id="章节总结-3"><a href="#章节总结-3" class="headerlink" title="章节总结"></a>章节总结</h2><p>本章一共介绍了三个API,两个抽取子流,一个拼接流</p><p>java.util.Stream:</p><ul><li>limit(long maxSize):抽取流中最初的maxSize个元素,并返回一个新的流</li><li>skip(long n):抽取流中除了前n个元素之前的元素,并返回一个新的流</li><li>contact(Stream a, Stream b):拼接两个流,并返回一个新的流</li></ul><h1 id="其他流的转换"><a href="#其他流的转换" class="headerlink" title="其他流的转换"></a>其他流的转换</h1><p><code>distinct()</code>函数能够帮助我们从一个流中返回一个新的流,并且没有重复元素</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/8/29 21:05:21</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">StreamTrans</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>{</span><br><span class="line"> Stream<String> a = Stream.of(<span class="string">"merrily"</span>, <span class="string">"merrily"</span>, <span class="string">"merrily"</span>, <span class="string">"gently"</span>);</span><br><span class="line"> <span class="comment">// 流只能使用一次!!!!</span></span><br><span class="line"><span class="comment">// System.out.println(Arrays.toString(a.toArray()));</span></span><br><span class="line"> System.out.println(Arrays.toString(a.distinct().toArray()));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>与数组一样,我们可以使用<code>sorted()</code>对流进行排序</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(<span class="string">"=========流排序========"</span>);</span><br><span class="line"> String[] str = <span class="keyword">new</span> String[]{<span class="string">"Go"</span>, <span class="string">"Java"</span>, <span class="string">"C/C++"</span>};</span><br><span class="line"> Stream<String> str1 = Stream.of(str);</span><br><span class="line"> Object[] objects = str1.sorted(Comparator.comparing(String::length)).toArray();</span><br><span class="line"> System.out.println(Arrays.toString(objects));</span><br></pre></td></tr></table></figure><p>当然,我们也可以使用数组进行排序,但是当排序方法是流管道的一部分时,sorted函数就显得非常有用</p><p>最后还有一个<code>peek</code>函数,这个函数和<code>map</code>有一些类似,都是产生一个新的流,并且对元素进行一些处理,但是<code>map</code>是处理元素值,<code>peek</code>是执行函数</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> String[] str = <span class="keyword">new</span> String[]{<span class="string">"Go"</span>, <span class="string">"Java"</span>, <span class="string">"C/C++"</span>};</span><br><span class="line">System.out.println(<span class="string">"=========peek========"</span>);</span><br><span class="line"> Stream<String> str2 = Stream.of(str);</span><br><span class="line"> Stream<String> peek = str2.peek(System.out::println);</span><br><span class="line"> System.out.println(Arrays.toString(peek.toArray()));</span><br></pre></td></tr></table></figure><p>这样就会将数组逐个输出。</p><h2 id="章节总结-4"><a href="#章节总结-4" class="headerlink" title="章节总结"></a>章节总结</h2><h3 id="java-util-stream-Stream"><a href="#java-util-stream-Stream" class="headerlink" title="java.util.stream.Stream"></a>java.util.stream.Stream</h3><ul><li>Stream<T> distinct():产生一个新的流,剔除当前流中的重复元素</li><li>Stream<T> sorted()</li><li>Stream<T> sorted(Comparator<? super T> comparator):产生一个新的流并且进行排序</li><li>Stream<T> peek(Consumer<? super T> action):产生一个新的流,与当前元素相同,并且在获取其中每个元素时都会传递给action</li></ul><h1 id="简单约简"><a href="#简单约简" class="headerlink" title="简单约简"></a>简单约简</h1><p>我们已经看到了如何创建和转换流,终于可以来一点有意思的东西了:从数据流中获取答案,这种方法被称为约简。这是一种<code>终结操作</code>,他们<strong>会将流约简成可以在程序中使用的非流值</strong>。</p><p>之前我们已经使用过<code>count</code>,这就是一种简单约简,同样的还有<code>max</code>和<code>min</code>,这些方法的返回值是一个<code>Optional<T></code>,它会在其中包装答案,或者什么都不做(流值为空),在以前这种操作容易造成空指针异常,在下一节中我们会详细讨论。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="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"> String[] words = <span class="keyword">new</span> String[]{<span class="string">"Go"</span>, <span class="string">"Java"</span>, <span class="string">"C/C++"</span>, <span class="string">"Rust"</span>};</span><br><span class="line"> Stream<String> words1 = Stream.of(words);</span><br><span class="line"> Optional<String> max = words1.max(String::compareToIgnoreCase);</span><br><span class="line"> System.out.println(max);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>这个方法展示了如何获取流的最大值,但是结果和我们想象的不太一样</p><p><code>Optional[Rust]</code></p><p>还有找到第一个以G开头的单词</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(<span class="string">"=====findFirst===="</span>);</span><br><span class="line">Optional<String> g = Stream.of(words).filter(e -> e.startsWith(<span class="string">"G"</span>)).findFirst();</span><br><span class="line">System.out.println(g);</span><br></pre></td></tr></table></figure><p>如果不强制匹配第一个,那么可以使用<code>findAny</code>,这个方法在处理并行流时很有效</p><p>或者只是想知道有没有存在某个值符合,可以使用<code>anyMatch</code>来进行匹配,这个函数会返回一个boolean,而不是具体的值,同时,这个方法需要接受一个断言引元,而不是filter,就像这样</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">boolean</span> g2 = Stream.of(words).anyMatch(e -> e.startsWith(<span class="string">"G"</span>));</span><br><span class="line">System.out.println(g2);</span><br></pre></td></tr></table></figure><p>还有<code>allMatch</code>和<code>noneMatch</code>,分别会在全部符合或者没有符合时返回<code>true</code></p><h2 id="章节总结-5"><a href="#章节总结-5" class="headerlink" title="章节总结"></a>章节总结</h2><h3 id="java-util-stream-Stream-1"><a href="#java-util-stream-Stream-1" class="headerlink" title="java.util.stream.Stream"></a>java.util.stream.Stream</h3><ul><li>Optional<T> max(Comparator<? super T> comparator)</li><li>Optional<T> min(Comparator<? super T> comparator):这两个方法分别会返回最大元素和最小远古三</li><li>Optional<T> findFirst()</li><li>Optional<T> findAny():分别产生这个流的第一个元素和任意一个元素</li><li>Optional<T> anyMatch(Predicate<? super T> predicate)</li><li>Optional<T> allMatch(Predicate<? super T> predicate)</li><li>Optional<T> noneMatch(Predicate<? super T> predicate):分别在这个流中任意元素、所有元素和没有任何元素匹配时返回<code>true</code></li></ul><h1 id="Optional类型"><a href="#Optional类型" class="headerlink" title="Optional类型"></a>Optional类型</h1><p>上一节中我们使用到了这个对象,这节来自习介绍一下</p><p>Optional类型是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象,用于解决空指针异常问题。</p><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p>Optional是一个很好用的东西,如果值不存在的状态下,它会自动产生一个替代的值</p><p>第一种情况:</p><p>如果某个值不存在,我们想要用一个空字符串去代替他</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">String s = a.orElse(<span class="string">""</span>);</span><br><span class="line">System.out.println(s);</span><br></pre></td></tr></table></figure><p>或者调用一个方法去计算</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">s = a.orElseGet(() -> {</span><br><span class="line"> <span class="keyword">return</span> String.valueOf(System.currentTimeMillis());</span><br><span class="line">});</span><br><span class="line">System.out.println(s);</span><br></pre></td></tr></table></figure><p>又或者是抛出一个异常</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String s1 = a.orElseThrow(IllegalAccessError::<span class="keyword">new</span>);</span><br></pre></td></tr></table></figure><p>这些方法在不存在任何值时会产生一个相应的替代物;还有一个函数可以选择只有在值存在的情况下才进行消费。</p><p><code>ifPresent</code>只会在值存在的情况下才会接受一个函数,否则不会发生任何事情。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Optional<String> b = Optional.of(<span class="string">"123"</span>);</span><br><span class="line">b.ifPresent(System.out::println);</span><br></pre></td></tr></table></figure><p>当我们调用<code>isPresent</code>时,这个函数不会返回任何值,如果想要获取处理的结果,可以使用<code>map</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List<String> result = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">Optional<Boolean> aBoolean = b.map(result::add);</span><br><span class="line">System.out.println(aBoolean);</span><br></pre></td></tr></table></figure><p><strong>注意</strong>:这里的aBoolean有三种值,<code>true</code>和<code>false</code>所对应的Option,和空值所对应的Option</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><h4 id="java-util-Optional"><a href="#java-util-Optional" class="headerlink" title="java.util.Optional"></a>java.util.Optional</h4><ul><li>T orElsr(T other):产生这个Optional的值,在值为空时,返回other</li><li>T orElseGet(Supplier<? extends T> other):产生这个Optional的值,在值为空时,返回other这个<strong>方法</strong>所返回的结果</li><li><X extends Throwable> T orElseThrow(Supplier<? extends X> action):返回Optional的值,值为空时,抛出action返回的结果</li><li>void isPresent(Consumer<? super T> consumer):如果值不为空,就将值传递给consumer这个<strong>方法</strong> </li><li><U> Optional<U> map(Function<? super T, ?extends U> mapper):将值传递给mapper并产生返回结果,如果值为空,返回也是一个空Optional</li></ul><h2 id="不适合使用Optional值的方式"><a href="#不适合使用Optional值的方式" class="headerlink" title="不适合使用Optional值的方式"></a>不适合使用Optional值的方式</h2><p>如果没有正确使用Optional的值,那么相比较其他获得<code>null</code>的方式,并没有任何区别。</p><p><code>get</code>方法会在Optional值存在IDE情况下获得其中包装的元素,或者在不存在的情况下抛出一个<code>NoSuchElementException</code>对象,所以</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Optional<T> optional = ...;</span><br><span class="line">optional.get().method();</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">T a = <span class="keyword">new</span> T();</span><br><span class="line">a.method();</span><br></pre></td></tr></table></figure><p>以上两种用法并没有太大区别。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Optional<T> optional = ...;</span><br><span class="line"><span class="keyword">if</span> (optional.isPresent()){</span><br><span class="line"> optional.get().method()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">T value = ...;</span><br><span class="line"><span class="keyword">if</span> (value != <span class="keyword">null</span>) value.method();</span><br></pre></td></tr></table></figure><p>这两种方法也没有什么区别</p><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><h4 id="java-util-Optional-1"><a href="#java-util-Optional-1" class="headerlink" title="java.util.Optional"></a>java.util.Optional</h4><ul><li>T get():产生这个Optional的值,或者在值为空时,抛出一个<code>NoSuchElementException</code>异常</li><li>boolean isPresent():如果值不为空,返回true</li></ul><h2 id="创建Optional值"><a href="#创建Optional值" class="headerlink" title="创建Optional值"></a>创建Optional值</h2><p>现在我们已经知道了如何使用Optional值,但是对于如何创建我们一无所知</p><p>有多个方法可以用于创建Optional,例如:</p><ul><li>Optional.of(T value)</li><li>Optional.empty()</li></ul><p>或者如果不确定有没有这个value,也可以使用<code>Optional.ofNullable(T value)</code>,这个函数会根据value是否存在去调用<code>Optional.empty</code>或是<code>Optional.of()</code></p><h3 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h3><h4 id="java-util-Optional-2"><a href="#java-util-Optional-2" class="headerlink" title="java.util.Optional"></a>java.util.Optional</h4><ul><li>static <T> Optional<T> of(T value)</li><li>static <T> Optional<T> empty():产生一格具有给定值的Optional,如果值为空,of会抛出一个NullPointerException异常,empty会返回一个空Optional</li><li>static <T> Optional<T> ofNullable(T value):如果value存在调用of,不存在调用empty</li></ul><h2 id="用flatMap构建Optional值的函数"><a href="#用flatMap构建Optional值的函数" class="headerlink" title="用flatMap构建Optional值的函数"></a>用flatMap构建Optional值的函数</h2><p>考虑这样一种场景,你有一个能够生成<code>Optional<T></code>对象的方法<code>f</code>,<code>T</code>又有一个能够返回<code>Optional<U></code>的方法<code>g</code>,如果想要通过f方法创建<code>Optional<U></code>对象,可以使用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Optional<U> result = s.f().flatMap(T::g);</span><br></pre></td></tr></table></figure><p>如果<code>s.f()</code>的值存在,那么<code>g</code>就可以应用到它上面,否则会产生一个空Optional</p><p>通过这种方式我们可以疯狂使用<code>flatMap</code>构建Optional,从而构建由这些步骤组成的管道,当所有步骤成功时,该管道才会成功</p><p>在之前我们见过<code>Stream.flatMap()</code>,但是这里的flatMap和stream的不太一样。</p><h3 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h3><h4 id="java-util-Optional-3"><a href="#java-util-Optional-3" class="headerlink" title="java.util.Optional"></a>java.util.Optional</h4><ul><li><U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper):产生将mapper应用于当前的Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional</li></ul><h1 id="收集结果"><a href="#收集结果" class="headerlink" title="收集结果"></a>收集结果</h1><p>当处理完流之后,如果想要查看其中的元素,我们可以使用<code>iterator</code>迭代器,或者<code>foreach</code>将某个函数应用到流中的每一个元素上去。</p><p>当然,在并行流上<code>foreach</code>方法会导致访问顺序不一致,如果想要按照流原来的顺序进行访问,需要使用<code>forEachOrderd</code>方法,同时这个方法也会丧失并行流的部分甚至全部优势。</p><p>或者我们可以使用<code>stream.toArray()</code>将流转化为Array类型。</p><p>或者<code>stream</code>提供了一个<code>collect</code>方法,它接受一个<code>Collector</code>实例,可以直接将流收集到列表或集合中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Stream<String> a = Stream.of(<span class="string">"123"</span>, <span class="string">"456"</span>);</span><br><span class="line">List<String> collect = a.collect(Collectors.toList());</span><br><span class="line">Map<String, String> collect1 = a.collect(Collectors.toMap(e -> e, e -> e));</span><br><span class="line">Set<String> collect2 = a.collect(Collectors.toSet());</span><br></pre></td></tr></table></figure><p>比如这三个方法可以将stream转换成集合形式。</p><h2 id="总结-4"><a href="#总结-4" class="headerlink" title="总结"></a>总结</h2><h3 id="java-util-stream-BaseStream"><a href="#java-util-stream-BaseStream" class="headerlink" title="java.util.stream.BaseStream"></a>java.util.stream.BaseStream</h3><ul><li>Iterator<T> iterator():产生一个用于获取当前流中各个元素的迭代器。这是一种终结操作。</li></ul><h1 id="收集到映射表中"><a href="#收集到映射表中" class="headerlink" title="收集到映射表中"></a>收集到映射表中</h1><p>假设有一个对象的流<code>Stream<People></code>,如果想要根据ID获取对应信息,可以使用<code>toMap</code>,但是toMap接受两个参数,键和值,值一般情况下是元素本身,可以使用<code>Function.identity()</code>,当然还要遵循Map的规则,键不能相同,会抛出一个<code>IllegalStateException</code>异常。</p><p>如果担心出现这种问题,我们可以对值进行选择</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a.collect(Collectors.toMap(e -> e, e -> e, (old, newValue) -> newValue));</span><br></pre></td></tr></table></figure><p>这样就代表,如果出现了主键重复的情况,选择新的value进行覆盖</p><p>或者如果你想指定产生的<code>Map</code>为<code>TreeMap</code>,可以使用如下方式:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a.collect(Collectors.toMap(e -> e, e -> e, (old, newValue) -> newValue, TreeMap::<span class="keyword">new</span>));</span><br></pre></td></tr></table></figure><h1 id="群组和分区"><a href="#群组和分区" class="headerlink" title="群组和分区"></a>群组和分区</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());</span><br><span class="line">Map<String, Set<String>> collect = locales.collect(Collectors.toMap(</span><br><span class="line"> Locale::getDisplayCountry,</span><br><span class="line"> l -> Collections.singleton(l.getDisplayLanguage()),</span><br><span class="line"> (old, newValue) -> {</span><br><span class="line"> Set<String> union = <span class="keyword">new</span> HashSet<>(old);</span><br><span class="line"> union.addAll(newValue);</span><br><span class="line"> <span class="keyword">return</span> union;</span><br><span class="line"> }));</span><br><span class="line">System.out.println(collect);</span><br></pre></td></tr></table></figure><p>这段代码可以手机给定国家的所有语言,但是有点过于冗长了。</p><p>在<code>Java</code>中,将具有相同特性的值群聚成组是很常见的,我们可以使用<code>groupingBy</code>进行这个操作</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Map<String, List<Locale>> collect1 = locales.collect(Collectors.groupingBy(Locale::getDisplayCountry));</span><br><span class="line">System.out.println(collect1);</span><br></pre></td></tr></table></figure><p>现在我们可以对这个Map进行操作,比如查找中国所用的语言</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">List<Locale> Ch = collect1.get(<span class="string">"中国"</span>);</span><br><span class="line">System.out.println(Ch);</span><br></pre></td></tr></table></figure><p>如果<code>groupingBy</code>中的函数返回值是一个boolean类型,那我们可以使用<code>partitioningBy</code>进行分组,在这种情况下,他要比<code>groupingBy</code>更加高效</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">locales = Stream.of(Locale.getAvailableLocales());</span><br><span class="line">Map<Boolean, List<Locale>> map = locales.collect(Collectors.partitioningBy(</span><br><span class="line"> l -> l.getLanguage().equals(<span class="string">"en"</span>)</span><br><span class="line">));</span><br></pre></td></tr></table></figure><p>比如这段代码能够筛选是否使用英语的国家</p><h2 id="总结-5"><a href="#总结-5" class="headerlink" title="总结"></a>总结</h2><h3 id="java-util-stream-Collector"><a href="#java-util-stream-Collector" class="headerlink" title="java.util.stream.Collector"></a>java.util.stream.Collector</h3><ul><li>static<T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<> super T, ? extends K> classifier)</li><li>groupingCurrentMap:产生一个收集器,它会产生一个映射表或并发映射表,键是<code>classifier</code>应用于所有元素上所产生的结果,而值是具有相同键的元素构成的一个个列表</li><li>partitioningBy:产生一个收集器,它会产生一个映射表,键是<code>true</code>或<code>false</code>,而值是由断言的段素构成的列表</li></ul><h1 id="下游收集器"><a href="#下游收集器" class="headerlink" title="下游收集器"></a>下游收集器</h1><p>如果想要返回的值不是一个<code>List</code>而是一个<code>Set</code>,可以使用<code>Collectors.groupingBy(Function, toSet())</code></p><p>不仅如此,Java还提供了很多将群组元素约简的方法:</p><ul><li><p>countings():对元素个数进行计数</p></li><li><p>summing(int | long | double):将函数应用到下游元素中,并计算他们的和</p></li><li><p>maxBy和minBy:产生一个比较器,并产生最大或最小元素</p></li><li><p>mapping:产生将函数应用到下游结果上的收集器,并传递给另一个收集器</p></li><li><p>```java<br>Map<String, Optional<String>> stateToLongestCityName = </p><pre><code>cities.collect( groupingBy(City::getStat), mapping(City::getName, maxBy(Comparator.comparing(String::length))));</code></pre><figure class="highlight mathematica"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="type">#</span> 约简操作</span><br><span class="line"></span><br><span class="line">在前面介绍过,约简操作是一种终结操作,能够将流转换成非流值,这节中来介绍更多的约简操作:</span><br><span class="line"></span><br><span class="line"><span class="variable">reduce</span>是一种从流中计算某个值的通用操作。比如下面这种操作能够计算<span class="operator">`</span><span class="built_in">List</span><span class="operator"><</span><span class="built_in">Integer</span><span class="operator">>`</span>集合中的总和</span><br><span class="line"></span><br><span class="line"><span class="operator">```</span><span class="variable">JAVA</span></span><br><span class="line"><span class="built_in">Integer</span><span class="punctuation">[</span><span class="punctuation">]</span> <span class="variable">a</span> <span class="operator">=</span> <span class="variable">new</span> <span class="built_in">Integer</span><span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">{</span><span class="number">1</span><span class="operator">,</span> <span class="number">2</span><span class="operator">,</span> <span class="number">3</span><span class="operator">,</span> <span class="number">4</span><span class="operator">,</span> <span class="number">5</span><span class="operator">,</span> <span class="number">6</span><span class="operator">,</span> <span class="number">7</span><span class="operator">,</span> <span class="number">8</span><span class="operator">,</span> <span class="number">9</span><span class="punctuation">}</span><span class="operator">;</span></span><br><span class="line"><span class="built_in">List</span><span class="operator"><</span><span class="built_in">Integer</span><span class="operator">></span> <span class="variable">list</span> <span class="operator">=</span> <span class="built_in">Arrays</span><span class="operator">.</span><span class="variable">asList</span><span class="punctuation">(</span><span class="variable">a</span><span class="punctuation">)</span><span class="operator">;</span></span><br><span class="line"><span class="built_in">Optional</span><span class="operator"><</span><span class="built_in">Integer</span><span class="operator">></span> <span class="variable">reduce</span> <span class="operator">=</span> <span class="variable">list</span><span class="operator">.</span><span class="variable">stream</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="operator">.</span><span class="variable">reduce</span><span class="punctuation">(</span><span class="built_in">Integer</span><span class="string">::sum</span><span class="punctuation">)</span><span class="operator">;</span></span><br><span class="line"><span class="variable">System</span><span class="operator">.</span><span class="variable">out</span><span class="operator">.</span><span class="variable">println</span><span class="punctuation">(</span><span class="variable">reduce</span><span class="punctuation">)</span><span class="operator">;</span></span><br></pre></td></tr></table></figure></li></ul><p>在reduce中,还有一项操作非常重要,就是<code>op</code>,这个约简操作会产生<code>v0 op v1 op v2...</code>,其中我们将函数<code>op(v1, v2)</code>写作<code>v1 op v2</code>。这项操作是可结合的,比如将切换顺序,他们的结果应当相同,也就是<code>(v1 op v2) op v3</code>应当与<code>v1 op (v2 op v3)</code>相同。</p><p>在reduce操作中,我们经常会使用一个<code>幺元值</code>,这个幺元值能够帮助我们在列表为空时不需要使用<code>Optional</code>进行判空,如果列表为空,那么就会返回这个幺元值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List<Integer> b = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">Integer reduce1 = b.stream().reduce(<span class="number">1</span>, Integer::sum);</span><br><span class="line">System.out.println(reduce1);</span><br></pre></td></tr></table></figure><p>就像这样,<code>reduce1</code>的值应该是<code>1</code></p><p>现在我们需要对一个字符串数组属性进行求和,求出每个字符串长度之和,就不能使用简单的<code>reduce</code>,而是要使用<code>(T1 + T2 ) -> T</code>形式</p><p>在实践中,我们更习惯去调用<code>words.mapToInt(String::length).sum()</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">str = <span class="keyword">new</span> String[]{<span class="string">"GoLang"</span>, <span class="string">"Java"</span>, <span class="string">"Rust"</span>, <span class="string">"C/C++"</span>};</span><br><span class="line"><span class="keyword">int</span> sum = Arrays.asList(str).stream().mapToInt(String::length).sum();</span><br><span class="line">System.out.println(sum);</span><br></pre></td></tr></table></figure><p>在我们使用并行流时,如果使用reduce可能会产生多个类型的计算,我们需要将结果进行合并,可以在reduce中加入第三个方法,表示结果进行合并</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a = <span class="keyword">new</span> Integer[]{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>};</span><br><span class="line"> Integer reduce3 = Arrays.asList(a).parallelStream().reduce(<span class="number">0</span>, (x, y) -> x + y, (sum1, sum2) -> sum1 + sum2);</span><br><span class="line"> System.out.println(reduce3);</span><br></pre></td></tr></table></figure><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>有时候<code>reduce</code>对我们来说并不够用,比如我们想要搜集<code>BitSet</code>中的结果。如果收集操作是并行的,那么我们就不能将元素放入单个<code>BitSet</code>中,因为<code>BitSet</code>对象并非线程安全的。因此我们不能使用<code>reduce</code>,因为每个部分都需要以自己的空集开始,并且<code>reduce</code>只能提供一个幺元值。在这时候我们更应该使用<code>collect</code>,它能够接受单个引元:</p><ul><li>一个提供者,它会创建目标类型的新实例,例如散列集的构造器</li><li>一个累积器,它会将一个元素添加到一个实例上,例如<code>add</code>方法</li><li>一个组合器,能够将两个实例合并成一个,例如<code>addAll</code></li></ul><p>下面会展示<code>collect</code>如何操作位集</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Stream<Integer> stream = Stream.of();</span><br><span class="line">BitSet collect = stream.collect(BitSet::<span class="keyword">new</span>, BitSet::set, BitSet::or);</span><br></pre></td></tr></table></figure><h1 id="基本流类型"><a href="#基本流类型" class="headerlink" title="基本流类型"></a>基本流类型</h1><p>到目前为止,我们都是将对象收集到<code>Stream<T></code>中,对于整数类型,我们一般使用<code>Stream<Integer></code>,但是这样效率是比较低的,在流库中,有专门的类:</p><ul><li>IntStream:用于存储short、char、byte和boolean</li><li>LongStream</li><li>DoubleStream:存储float类型</li></ul><p>用于存储基本类型值,无需使用包装器。</p><p>为了创建IntStream,我们可以使用<code>of</code>或<code>Arrays.stream()</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>[] numbers = <span class="keyword">new</span> <span class="keyword">int</span>[]{<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>};</span><br><span class="line">IntStream intStream = IntStream.of(numbers);</span><br><span class="line">intStream = Arrays.stream(numbers);</span><br></pre></td></tr></table></figure><p>和对象流一样,IntStream和LongStream也可以通过<code>generate</code>或者<code>iterator</code>创建无限流;此外,还有两个静态方法<code>range</code>和<code>rangeClose</code>,可以用于生成步长为1的整数范围</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">IntStream.rangeClosed(<span class="number">0</span>, <span class="number">100</span>); </span><br><span class="line">IntStream.range(<span class="number">0</span>, <span class="number">100</span>);</span><br></pre></td></tr></table></figure><p><code>CharSequence</code>接口有<code>codePoints()</code>和<code>chars</code>方法,可以生成由字符的<code>Unicode</code>码或<code>UTF-16</code>编码所组成的IntStream</p><p>假如我们现在已经有了一个对象流,想将这个对象流转换成基本类型流,可以通过<code>mapToInt/Long/Double</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Stream<String> stream = Stream.of(<span class="string">"Java"</span>, <span class="string">"GoLang"</span>);</span><br><span class="line">IntStream intStream2 = stream.mapToInt(String::length);</span><br><span class="line">System.out.println(Arrays.toString(intStream2.toArray()));</span><br></pre></td></tr></table></figure><p>比如这串代码能够将字符串的长度作为元素放入<code>IntStream</code></p><p>如果想将基本类型流转换成对象流,可以使用<code>boxed</code>方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">IntStream intStream3 = IntStream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">45</span>, <span class="number">6</span>);</span><br><span class="line">Stream<Integer> boxed = intStream3.boxed();</span><br></pre></td></tr></table></figure><p>基本类型流基本上的方法和对象流类似,最主要的差异有:</p><ul><li><code>toArray</code>方法会返回基本类型数组</li><li>产生可选结果的方法会返回<code>OptionalInt/Long/Double</code>,这些方法与<code>Optional</code>类似,但是有<code>getAsInt/Long/Double</code>方法,而不是get</li><li>具有返回总和、平均值、最大值、最小值的<code>sum</code>、<code>average</code>、<code>max</code>、<code>min</code>方法,而对象流没有这些</li><li><code>summaryStatistics</code>方法会产生一个类型为<code>Int/Long/DoubleSummaryStatistics</code>对象,他们可以同时报告流的总和、平均值、最大值和最小值。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">IntSummaryStatistics intSummaryStatistics = intStream3.summaryStatistics();</span><br><span class="line">System.out.println(intSummaryStatistics);</span><br><span class="line"></span><br><span class="line"><span class="comment">//IntSummaryStatistics{count=5, sum=57, min=1, average=11.400000, max=45}</span></span><br></pre></td></tr></table></figure><h2 id="总结-6"><a href="#总结-6" class="headerlink" title="总结"></a>总结</h2><p>这章所涉及的API比较多</p><h3 id="java-util-stream-IntStream"><a href="#java-util-stream-IntStream" class="headerlink" title="java.util.stream.IntStream"></a>java.util.stream.IntStream</h3><ul><li>range()</li><li>rangeClosed():产生一个由给定范围内的整数所构成的<code>IntStream</code>,区别在于<code>rangeClosed</code>包含最后的结束节点,range不包含</li><li>of(int… values):产生由给定元素组成的流</li><li>toArray():将当前流转换成数组</li><li>sum()</li><li>max()</li><li>min()</li><li>average()</li><li>summaryStatistics():产生当前流中元素的总和、最大值、最小值、平均值,或是包含着四种值的对象</li><li>boxed():将基本类型流转换成对象流</li></ul><h3 id="java-util-stream-LongStream"><a href="#java-util-stream-LongStream" class="headerlink" title="java.util.stream.LongStream"></a>java.util.stream.LongStream</h3><h3 id="java-util-stream-DoubleStream"><a href="#java-util-stream-DoubleStream" class="headerlink" title="java.util.stream.DoubleStream"></a>java.util.stream.DoubleStream</h3><p>用法和IntStream一致</p><h3 id="java-lang-CharSequence"><a href="#java-lang-CharSequence" class="headerlink" title="java.lang.CharSequence"></a>java.lang.CharSequence</h3><p>String类型实现了<code>CharSequence</code>接口</p><ul><li>codePoints():将当前字符串中所有的<code>Unicode</code>码点构成流</li></ul><h3 id="java-util-Random"><a href="#java-util-Random" class="headerlink" title="java.util.Random"></a>java.util.Random</h3><ul><li>ints()</li><li>ints(int begin, int end)</li><li>ints(long size)</li><li>ints(long size, int begin, int end)</li><li>longs()…..</li><li>doubles()……</li><li>产生随机数流,如果提供了size,这个刘就是具有给定元素数量的有限流,当提供了边界时,其元素位于<code>begin</code>和<code>end</code>之间</li></ul><h3 id="java-util-Optional-Int-Long-Double"><a href="#java-util-Optional-Int-Long-Double" class="headerlink" title="java.util.Optional(Int/Long/Double)"></a>java.util.Optional(Int/Long/Double)</h3><ul><li>of(T value):用提供的基本数据类型产生一个可选对象</li><li>getAsT():产生当前可选对象的值,如果不存在抛出<code>NoSuchElementException</code>异常</li><li>orElse(value):返回可选对象值,如果不存在返回value</li><li>orElseGet(Supplier other):产生当前可选对象的值,如果不存在,产生可替换的值</li><li>isPresent(Consumer c):如果当前可选对象不为空,将值传递给c</li></ul><h3 id="java-util-Int-Long-Double-SummaryStatistics"><a href="#java-util-Int-Long-Double-SummaryStatistics" class="headerlink" title="java.util.(Int/Long/Double) SummaryStatistics"></a>java.util.(Int/Long/Double) SummaryStatistics</h3><ul><li>getCount()</li><li>getSum()</li><li>getAverage()</li><li>getMax()</li><li>getMin()</li><li>产生收集到的元素个数、总和、平均值、最大值、最小值</li></ul><h1 id="并行流"><a href="#并行流" class="headerlink" title="并行流"></a>并行流</h1><p>流使得并行处理块操作变得很容易,但是需要遵循一些规则,首先是产生一个并行流,有两种方式可以产生并行流:</p><ul><li>parallelStream():可以从任何集合中获取一个并行流</li><li>parallel():将流转换成并行流</li></ul><p>只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化</p><p>当流操作并行运行时,其目标时要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以以任意顺序执行。</p><p>来看这样一段代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Stream<String> stream = Stream.of(<span class="string">"Java"</span>, <span class="string">"Golang"</span>, <span class="string">"C/C++"</span>, <span class="string">"Rust"</span>);</span><br><span class="line"><span class="keyword">int</span>[] shrtWords = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">12</span>];</span><br><span class="line">stream.parallel().forEach(e -> {</span><br><span class="line"> <span class="keyword">if</span> (e.length() < <span class="number">12</span>) {</span><br><span class="line"> shrtWords[e.length()]++;</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line">System.out.println(shrtWords);</span><br></pre></td></tr></table></figure><p><strong>注意</strong>:这是一种很糟糕的代码,传递给forEach的函数会在多个并发线程中运行,每个都会更新共享的数组。当数据量稍微大那么一点点时,运行出来的数据就会产生不同的结果。</p><p>我们并不需要错误的结果,就需要保证传递给并行流的操作安全的并行执行,而达到这个目的的最佳方式是原理易变状态。</p><p>在本例中,我们可以用长度将字符串群组,然后分别对他们进行计数,就可以安全的并行化这项计算。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Map<Integer, Long> collect =</span><br><span class="line"> stream.parallel().filter(s -> s.length() < <span class="number">12</span>)</span><br><span class="line"> .collect(Collectors.groupingBy(String::length, Collectors.counting()));</span><br><span class="line">System.out.println(collect);</span><br></pre></td></tr></table></figure><p>默认情况下,从有序集合、范围、生成器和迭代产生的流,或者通过调用<code>Stream.sorted</code>产生的流,都是有序的。</p><p>排序并不排斥高效的并行处理,例如,当计算<code>stream.map(fun)</code>时,流可以被划分为n个部分,他们会被并行处理,然后按照顺序重新组装起来。</p><p>当我们放弃排序需求时,有些操作可以被更加高效的并行化。通过在流上游调用<code>unordered</code>方法,可以标明我们对排序不感兴趣。<code>distinct</code>就是从这种方式中获益的一种操作,在有序流中,<code>distinct</code>会保留所有元素中的第一个,这是对并行化的一种阻碍。在所有线程都处理完之前,我们不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行处理。</p><p>还有放弃排序来提高<code>limit</code>速度。</p><p>我们需要知道,合并映射表的代价是非常高的,正是这个原因,<code>Collectors.groupingByConcurrent</code>方法使用了共享的并发映射表。为了从并行化中受益,映射表中值的顺序不会与流中的顺序相同。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ConcurrentMap<Integer, List<String>> collect1 = stream.parallel().collect(Collectors.groupingByConcurrent(String::length));</span><br></pre></td></tr></table></figure><h2 id="警告"><a href="#警告" class="headerlink" title="警告"></a>警告</h2><p>不要试图将一个集合生成流之后,再修改这个集合,此时流不会收集他们的元素,准确来说,流操作都是惰性的,直到终结操作时才对集合进行修改才是可行的。</p><p>为了让并行流正常工作,需要满足以下条件:</p><ul><li>数据应该存在内存中,必须等数据到达是非常低效的</li><li>流可以被高效分为若干个子部分,由数组或平衡二叉树支撑的流都可以工作的很好,但是<code>iterate</code>返回的结果不行</li><li>流操作的工作量应该具有较大的规模,如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。</li><li>流操作不应该被阻塞。</li></ul><p>换句话说,不要讲所有的流操作都转化成并行流,只有在对已经位于内存中,并且数据执行大量计算操作时,才应该使用并行流。</p><h2 id="总结-7"><a href="#总结-7" class="headerlink" title="总结"></a>总结</h2><h3 id="java-util-stream-BaseStream-1"><a href="#java-util-stream-BaseStream-1" class="headerlink" title="java.util.stream.BaseStream"></a>java.util.stream.BaseStream</h3><ul><li>parallel():产生一个与当前流中元素相同的并行流</li><li>unordered():产生一个与当前流中元素相同的无序流</li></ul><h3 id="java-util-Collection"><a href="#java-util-Collection" class="headerlink" title="java.util.Collection"></a>java.util.Collection</h3><ul><li>parallelStream():使用当前集合中的元素产生一个并行流</li></ul>]]></content>
<summary type="html"><h1 id="从迭代到流的操作"><a href="#从迭代到流的操作" class="headerlink" title="从迭代到流的操作"></a>从迭代到流的操作</h1><p>处理集合时,我们常常会遍历他们的元素,然后对其中的元素做一些操作,例如我们如果想从一个文件中</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
<category term="Java流" scheme="https://jiangliujl.github.io/tags/Java%E6%B5%81/"/>
</entry>
<entry>
<title>《Go随机数》——学习笔记</title>
<link href="https://jiangliujl.github.io/2022/08/09/go%E9%9A%8F%E6%9C%BA%E6%95%B0/"/>
<id>https://jiangliujl.github.io/2022/08/09/go%E9%9A%8F%E6%9C%BA%E6%95%B0/</id>
<published>2022-08-08T16:00:00.000Z</published>
<updated>2022-08-09T13:10:22.638Z</updated>
<content type="html"><![CDATA[<p>在GO语言中,提供了随机数的核心方法<code>rand</code>,但是go的随机数其实并不随机,是一个伪随机数,简单来说就是Go的随机数生成需要依赖种子值,对于相同的种子值产生的随机数顺序是相同的</p><p>为了产生一个随机数,我们需要给rand设置一个不重复种子,最好的选择当然就是时间</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rand.Seed(time.Now().UnixNano())</span><br></pre></td></tr></table></figure><p><strong>注意</strong>:Seed接收的是一个int64类型的数字,需要用Unix将time转换成int64类型</p><p>然后就可以使用rand.Intn(max int64)来进行取随机数了</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rand.Seed(time.Now().UnixNano())</span><br><span class="line">randomInt := int64(rand.Intn(<span class="number">100</span>))</span><br><span class="line">fmt.Println(randomInt)</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>在GO语言中,提供了随机数的核心方法<code>rand</code>,但是go的随机数其实并不随机,是一个伪随机数,简单来说就是Go的随机数生成需要依赖种子值,对于相同的种子值产生的随机数顺序是相同的</p>
<p>为了产生一个随机数,我们需要给rand设置一个不重复种子</summary>
<category term="GoLang" scheme="https://jiangliujl.github.io/tags/GoLang/"/>
</entry>
<entry>
<title>《Java序列化与反序列化》</title>
<link href="https://jiangliujl.github.io/2022/08/04/%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"/>
<id>https://jiangliujl.github.io/2022/08/04/%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</id>
<published>2022-08-03T16:00:00.000Z</published>
<updated>2022-08-04T14:32:50.841Z</updated>
<content type="html"><![CDATA[<p>这是一个新的系列,每天写一点自己的想法。</p><p>突然想起来之前在面试的时候,面试官问过我这么一个问题:Java中创建类实例有哪几种方法</p><ul><li>new</li><li>Java反射的newInstance</li><li>clone</li><li>反序列化</li></ul><p>然后面试官又问我:反序列化你用过吗</p><p>我就答不上来了,只知道有这个概念,具体怎么实现还真没去看过,今天来试一试</p><h2 id="编写实体类"><a href="#编写实体类" class="headerlink" title="编写实体类"></a>编写实体类</h2><p>这里随便来写一个,但是记得实现序列化接口<code>Serializable</code>,没啥技术含量</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> lombok.AllArgsConstructor;</span><br><span class="line"><span class="keyword">import</span> lombok.Data;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.Serializable;</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> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/8/4 15:38:24</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="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">People</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</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> age;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="编写序列化类"><a href="#编写序列化类" class="headerlink" title="编写序列化类"></a>编写序列化类</h2><p>这里主要用到的就是IO流,反序列化的处理可能不是那么妥当,但是又一直报警告,明天再处理一下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.*;</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> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/8/4 15:39:56</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">SerializableUtil</span><<span class="title">T</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">public</span> <span class="keyword">void</span> <span class="title">serialize</span><span class="params">(T obj, String fileName)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> OutputStream out = <span class="keyword">new</span> FileOutputStream(fileName);</span><br><span class="line"> ObjectOutputStream outputStream = <span class="keyword">new</span> ObjectOutputStream(out);</span><br><span class="line"> outputStream.writeObject(obj);</span><br><span class="line"> <span class="comment">// 用完记得关闭</span></span><br><span class="line"> outputStream.close();</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="function"><span class="keyword">public</span> T <span class="title">deSerialize</span><span class="params">(String fileName)</span> <span class="keyword">throws</span> IOException, ClassNotFoundException </span>{</span><br><span class="line"> InputStream in = <span class="keyword">new</span> FileInputStream(fileName);</span><br><span class="line"> ObjectInputStream inputStream = <span class="keyword">new</span> ObjectInputStream(in);</span><br><span class="line"> Object a = <span class="keyword">null</span>;</span><br><span class="line"> a = inputStream.readObject();</span><br><span class="line"> <span class="keyword">return</span> (T) a;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="编写main方法"><a href="#编写main方法" class="headerlink" title="编写main方法"></a>编写main方法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</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> JiangLiu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2022/8/4 15:38:06</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">main</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>{</span><br><span class="line"> <span class="comment">// 造几个People</span></span><br><span class="line"> List<People> people = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> people.add(<span class="keyword">new</span> People(<span class="string">"张三"</span>, <span class="number">15</span>));</span><br><span class="line"> people.add(<span class="keyword">new</span> People(<span class="string">"李四"</span>, <span class="number">18</span>));</span><br><span class="line"> String fileName = <span class="string">"test.txt"</span>;</span><br><span class="line"> SerializableUtil<List<People>> s = <span class="keyword">new</span> SerializableUtil<>();</span><br><span class="line"> <span class="comment">// 对象序列化</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> s.serialize(people, fileName);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 反序列化</span></span><br><span class="line"> List<People> list = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> list = s.deSerialize(fileName);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line"> }</span><br><span class="line"> System.out.println(list);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="运行结果"><a href="#运行结果" class="headerlink" title="运行结果"></a>运行结果</h2><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[People(name=张三, age=15), People(name=李四, age=18)]</span><br></pre></td></tr></table></figure><p>完美</p>]]></content>
<summary type="html"><p>这是一个新的系列,每天写一点自己的想法。</p>
<p>突然想起来之前在面试的时候,面试官问过我这么一个问题:Java中创建类实例有哪几种方法</p>
<ul>
<li>new</li>
<li>Java反射的newInstance</li>
<li>clone</li>
</summary>
<category term="每天写点啥" scheme="https://jiangliujl.github.io/tags/%E6%AF%8F%E5%A4%A9%E5%86%99%E7%82%B9%E5%95%A5/"/>
</entry>
<entry>
<title>《RabbitMQ学习笔记》——读书笔记</title>
<link href="https://jiangliujl.github.io/2022/07/27/RabbitMQ%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<id>https://jiangliujl.github.io/2022/07/27/RabbitMQ%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
<published>2022-07-26T16:00:00.000Z</published>
<updated>2022-07-27T04:43:52.047Z</updated>
<content type="html"><![CDATA[<h1 id="什么是MQ"><a href="#什么是MQ" class="headerlink" title="什么是MQ"></a>什么是MQ</h1><p>MQ是<code>Message Queue</code>的简称,就是一个消息队列,队列嘛,FIFO先进先出,与普通队列的区别就是,MQ中存放的是消息,并且它是一种跨进程的通信机制,用于上下游传递消息,能够实现上下游之间的解耦</p><h1 id="MQ在SpringBoot中的配置"><a href="#MQ在SpringBoot中的配置" class="headerlink" title="MQ在SpringBoot中的配置"></a>MQ在SpringBoot中的配置</h1><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr">rabbitmq:</span></span><br><span class="line"><span class="attr">host:</span> <span class="string">//</span> <span class="string">rabbitmq的地址</span></span><br><span class="line"><span class="attr">port:</span> <span class="string">//</span> <span class="string">mq的端口</span></span><br><span class="line"><span class="attr">username:</span> <span class="string">//</span> <span class="string">mq的用户名</span></span><br><span class="line"><span class="attr">password:</span> <span class="string">//</span> <span class="string">mq的密码</span></span><br><span class="line"><span class="attr">virtual-host:</span> <span class="string">//</span> <span class="string">虚拟消息服务器</span></span><br><span class="line"><span class="attr">publisher-confirms:</span> <span class="string">//</span> <span class="string">是否开启发送确认</span></span><br><span class="line"><span class="attr">publisher-returns:</span> <span class="string">//</span> <span class="string">是否开启发送失败退回</span></span><br><span class="line"><span class="attr">template:</span></span><br><span class="line"><span class="attr">mandatory:</span> <span class="string">//</span> <span class="string">生产者是否启用强制消息</span></span><br><span class="line"><span class="attr">retry:</span></span><br><span class="line"><span class="attr">enable:</span> <span class="string">//</span> <span class="string">生产者是否开启重启</span></span><br><span class="line"><span class="attr">listener:</span></span><br><span class="line"><span class="attr">acknowledge-mode:</span> <span class="string">//</span> <span class="string">消费者ack模式</span></span><br><span class="line"><span class="attr">retry:</span></span><br><span class="line"><span class="attr">enable:</span> <span class="string">//</span> <span class="string">消费者是否重试</span></span><br><span class="line"><span class="attr">max-attempts:</span> <span class="string">//</span> <span class="string">消费者重试次数</span></span><br></pre></td></tr></table></figure><h2 id="配置详解"><a href="#配置详解" class="headerlink" title="配置详解"></a>配置详解</h2><h3 id="virtual-host"><a href="#virtual-host" class="headerlink" title="virtual-host"></a>virtual-host</h3><p><code>virtualHost</code>虚拟消息服务器,每个virtualHost相当于一个独立的MQ服务器,每个VirtualHost之间消息是隔离的,exchange、queue、message不能互通</p><h3 id="publisher-confirms"><a href="#publisher-confirms" class="headerlink" title="publisher-confirms"></a>publisher-confirms</h3><p>这个配置是为了在MQ和生产者之间的消息能够可靠传输,是MQ的扩展</p><p>生产者推送消息到消息队列后,会触发两个回调函数<code>ConfirmCallback</code>和<code>ReturnCallback</code>,从消息推送的结果来看,一共有四种组合:</p><ul><li>消息推送到server,但是在server里找不到交换机</li><li>消息推送到server,找到了交换机但是找不到队列</li><li>消息推送到server了,交换机和队列都没找到</li><li>消息推送成功</li></ul><p>生产者和消费者确认详见后文</p><h1 id="生产者和消费者确认"><a href="#生产者和消费者确认" class="headerlink" title="生产者和消费者确认"></a>生产者和消费者确认</h1><p>由于MQ的传输协议方法无法确认生产者和消费者是否成功发布或者消费信息,所以生产者和消费者都需要一种传递和处理确认的机制</p><h2 id="消费者确认"><a href="#消费者确认" class="headerlink" title="消费者确认"></a>消费者确认</h2><h3 id="自动ACK"><a href="#自动ACK" class="headerlink" title="自动ACK"></a>自动ACK</h3><p>在MQ中有一种自动确认模式机制,消息发送成功后立即被视为传递成功,这种模式以更高的吞吐量来降低交付和消费者处理的安全性为代价,如果消费者的TCP连接或通道在消息发送成功之前关闭,那么消息就会丢失,所以这种方法被视为是不安全的。</p><p>在这个模式中,当方法没有异常执行完毕后,会对MQ发出ACK,若方法出现异常,会对MQ发出nack,消息重回队列。</p><h3 id="手动ACK"><a href="#手动ACK" class="headerlink" title="手动ACK"></a>手动ACK</h3><p>常用API:</p><ul><li>channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false/true)<ul><li>消息确认,第一个参数是队列名称,第二个参数是multiple<ul><li>multiple:是否一次性ack所有deleveryTag的消息</li></ul></li></ul></li><li>channel.basicReject(deleveryTag, requeue)<ul><li>拒绝消息<ul><li>requeue:bool类型,false表示将这条消息丢弃,true表示消息重回队列</li></ul></li></ul></li><li>channel.basicNack(deliveryTag, multiple, requeue)<ul><li>拒绝消息<ul><li>deliveryTag:队列名称</li><li>multiple:是否拒绝deliveryTag的所有消息</li><li>requeue:是否返回队列</li></ul></li></ul></li></ul>]]></content>
<summary type="html"><h1 id="什么是MQ"><a href="#什么是MQ" class="headerlink" title="什么是MQ"></a>什么是MQ</h1><p>MQ是<code>Message Queue</code>的简称,就是一个消息队列,队列嘛,FIFO先进先出,与普通</summary>
<category term="读书笔记" scheme="https://jiangliujl.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<category term="SpringBoot" scheme="https://jiangliujl.github.io/tags/SpringBoot/"/>
<category term="RabbitMQ" scheme="https://jiangliujl.github.io/tags/RabbitMQ/"/>
</entry>
<entry>
<title>《Java流中的generate与iterator》——读书笔记</title>
<link href="https://jiangliujl.github.io/2022/07/26/stream%E4%B8%AD%E7%9A%84generate%E4%B8%8Eiterator/"/>
<id>https://jiangliujl.github.io/2022/07/26/stream%E4%B8%AD%E7%9A%84generate%E4%B8%8Eiterator/</id>
<published>2022-07-25T16:00:00.000Z</published>
<updated>2022-07-26T14:54:20.632Z</updated>
<content type="html"><![CDATA[<h1 id="Java流中的generate与iterator"><a href="#Java流中的generate与iterator" class="headerlink" title="Java流中的generate与iterator"></a>Java流中的generate与iterator</h1><p>在Java流中,有两个创建无限流的方法:</p><ul><li>stream().generate()</li><li>stream().iterator()</li></ul><h2 id="iterator"><a href="#iterator" class="headerlink" title="iterator"></a>iterator</h2><p>从源码中给的解释来看</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.</span><br><span class="line">The first element (position 0) in the Stream will be the provided seed. For n > 0, the element at position n, will be the result of applying the function f to the element at position n - 1.</span><br><span class="line">The action of applying f for one element happens-before the action of applying f for subsequent elements. For any given element the action may be performed in whatever thread the library chooses.</span><br><span class="line">形参:</span><br><span class="line">seed – the initial element f – a function to be applied to the previous element to produce a new element</span><br><span class="line">返回值:</span><br><span class="line">a new sequential Stream</span><br></pre></td></tr></table></figure><p>iterator创建的无限流是根据<code>seed</code>与<code>initial element seed</code>来创建的,简单来说就是一个起始元素seed,一个创建的规则</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Stream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE)).limit(<span class="number">10</span>);</span><br><span class="line"> System.out.println(Arrays.toString(stream.filter(n -> n.compareTo(<span class="keyword">new</span> BigInteger(String.valueOf(<span class="number">1794952398L</span>))) < <span class="number">0</span>).toArray()));</span><br></pre></td></tr></table></figure><p>这里用limit来限制一下产生的无限流,否则无法正常输出</p><h2 id="generate"><a href="#generate" class="headerlink" title="generate"></a>generate</h2><p>同样还是来看源码中给的解释</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Returns an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for generating constant streams, streams of random elements, etc.</span><br><span class="line">形参:</span><br><span class="line">s – the Supplier of generated elements</span><br><span class="line">返回值:</span><br><span class="line">a new infinite sequential unordered Stream</span><br></pre></td></tr></table></figure><p>对于generate来说,只提供给我们一个参数<code>Supplier</code>,翻译过来叫 供应商,里面存放着产生供应流的规则</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Stream<Integer> stream1 = Stream.generate(<span class="keyword">new</span> Supplier<Integer>() {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">int</span> a = <span class="number">0</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> Integer <span class="title">get</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a++;</span><br><span class="line"> }</span><br><span class="line"> }).limit(<span class="number">20</span>);</span><br><span class="line"> System.out.println(Arrays.toString(stream1.toArray()));</span><br></pre></td></tr></table></figure><p>这里同样用limit限制一下</p><p>至于具体的使用场景,后续会继续更新</p>]]></content>
<summary type="html"><h1 id="Java流中的generate与iterator"><a href="#Java流中的generate与iterator" class="headerlink" title="Java流中的generate与iterator"></a>Java流中的generat</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
<category term="Java流" scheme="https://jiangliujl.github.io/tags/Java%E6%B5%81/"/>
</entry>
<entry>
<title>《MyBatis 和 MyBatis Plus冲突问题》——解决模块</title>
<link href="https://jiangliujl.github.io/2022/07/25/mybatisplus%E5%92%8Cmybatis%E5%86%B2%E7%AA%81%E9%97%AE%E9%A2%98/"/>
<id>https://jiangliujl.github.io/2022/07/25/mybatisplus%E5%92%8Cmybatis%E5%86%B2%E7%AA%81%E9%97%AE%E9%A2%98/</id>
<published>2022-07-24T16:00:00.000Z</published>
<updated>2022-07-25T04:45:02.722Z</updated>
<content type="html"><![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p>在工作的时候,遇到了一个奇怪的问题,使用 MyBatis Plus 的IService模板中的list对数据库进行操作时,报了个<code>Invalid bound statement (not found)</code></p><h1 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h1><p>搜了半天,主要是以下几个问题:</p><ul><li>xml 的 namespace 不正确</li><li>Mapper.java 中的方法在 Mapper.xml 中不存在</li><li>xml 返回类型配置错误</li><li>没有构建成功</li></ul><p>但是这些问题都检查了,没问题,用 Maven Helper 查看了一下依赖冲突,看到项目中同时引入了mybatis和mybatisplus,具体冲突的包有三个:</p><ul><li>mapper-spring-boot-starter</li><li>mybatis-spring-boot-starter</li><li>mybatis-plus-extension</li></ul><p>首先,<code>mybatis-spring-boot-starter</code>包是用来连接mybatis和springboot的中间件,这个 mybatis-plus-boot-starter能够代替,冲突了,去掉</p><p>然后是 <code>mapper-spring-boot-starter</code>包,这个包是用来导入公共mapper模板的,具体作用暂时不知道,但是不去掉也不能运行</p><p>最后是<code>mybatis-plus-extension</code>,这个东西具体作用没查到,只知道他是mybatisplus的扩展插件,但是去掉之后service层的函数全都无法调用了</p>]]></content>
<summary type="html"><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p>在工作的时候,遇到了一个奇怪的问题,使用 MyBatis Plus 的IService模板中的list对数据库进行操作时,报了个<code></summary>
<category term="MyBatis Plus" scheme="https://jiangliujl.github.io/tags/MyBatis-Plus/"/>
<category term="SpringBoot" scheme="https://jiangliujl.github.io/tags/SpringBoot/"/>
<category term="问题解决" scheme="https://jiangliujl.github.io/tags/%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"/>
</entry>
<entry>
<title>《Java常用注解》</title>
<link href="https://jiangliujl.github.io/2022/07/15/java%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/"/>
<id>https://jiangliujl.github.io/2022/07/15/java%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-07-24T08:18:35.379Z</updated>
<content type="html"><![CDATA[<h1 id="PostConstruct"><a href="#PostConstruct" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h1><p>从Java EE 5 之后,Servlet增加了两个影响Servlet生命周期的注解:</p><ul><li>@PostConstruct</li><li>@PreConstruct</li></ul><h2 id="PostConstruct-1"><a href="#PostConstruct-1" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h2><p>被这个注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,被PostConstruct修饰的方法会在构造函数之后,init之前运行</p><h2 id="PreConstruct"><a href="#PreConstruct" class="headerlink" title="@PreConstruct"></a>@PreConstruct</h2><p>被这个注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会调用一次,类似于destroy</p>]]></content>
<summary type="html"><h1 id="PostConstruct"><a href="#PostConstruct" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h1><p>从Java EE 5 之后,Servlet增加了</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
<category term="注解" scheme="https://jiangliujl.github.io/tags/%E6%B3%A8%E8%A7%A3/"/>
</entry>
<entry>
<title>《lambda表达式的语法》——读书笔记</title>
<link href="https://jiangliujl.github.io/2022/07/15/lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<id>https://jiangliujl.github.io/2022/07/15/lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-07-24T08:15:27.345Z</updated>
<content type="html"><![CDATA[<h1 id="lambda表达式的语法"><a href="#lambda表达式的语法" class="headerlink" title="lambda表达式的语法"></a>lambda表达式的语法</h1><p>了解过javax.swing.Timer函数和Comparator比较器的可以发现,这两个例子有一些共同点,都是将某一段代码块传到某个对象,如果可以直接传入一段代码块,那代码会变得非常简洁,但是Java并不支持这种方法,因为这会让Java语言变得一团糟</p><p>在 java 8 后,加入了lambda表达式,这是一个可传递的代码块,可以让某个接口不写实现类而直接使用</p><p>new Timer中需要传入一个ActionListener接口,实际上只是调用这个接口中的actionPerformed函数,Comparator也是同理</p><p>案例:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">lambdaTest</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>{</span><br><span class="line"> String[] a = <span class="keyword">new</span> String[]{ <span class="string">"Mercury"</span> , <span class="string">"Venus"</span> , <span class="string">"Earth"</span> , <span class="string">"Mars"</span> ,</span><br><span class="line"> <span class="string">"Jupiter"</span> , <span class="string">"Saturn"</span> , <span class="string">"Uranus"</span> , <span class="string">"Neptune"</span>};</span><br><span class="line"> Arrays.sort(a, (left, right) -> {</span><br><span class="line"> <span class="keyword">return</span> left.length() - right.length();</span><br><span class="line"> });</span><br><span class="line"> System.out.println(a);</span><br><span class="line"></span><br><span class="line"> Timer t = <span class="keyword">new</span> Timer(<span class="number">100</span>, event ->{</span><br><span class="line"> System.out.println(<span class="keyword">new</span> Date());</span><br><span class="line"> });</span><br><span class="line"> t.start();</span><br><span class="line"> JOptionPane.showMessageDialog(<span class="keyword">null</span>, <span class="string">"Quit"</span>);</span><br><span class="line"> System.exit(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="函数式接口"><a href="#函数式接口" class="headerlink" title="函数式接口"></a>函数式接口</h1><p>Java中已经有了很多封装代码块地接口,如AactionListener、Comparator,lambda与这些接口是兼容的</p><p>对于只有一个抽象方法的接口,需要用到这种接口的对象时,可以使用lambda表达式,这种接口成为函数式接口</p><ul><li>为什么Comparator接口也能成为函数式接口,明明有compare、equals两个抽象函数<ul><li>对于接口重写Object的公共方法是不算入函数式接口中的,也就是说Comparator只有compare一个非公共抽象函数</li></ul></li></ul><p>以Arrays.sort为例,在底层,sort方法会接收Comparator的某个类的对象,在这个对象上再调用compare方法执行lambda表达式的方法体。</p><p>lambda表达式可以转换成接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Timer t = <span class="keyword">new</span> Timer(<span class="number">100</span>, event ->{</span><br><span class="line"> System.out.println(<span class="keyword">new</span> Date());</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>与原来的写法相比,这个可读性要高得多</p><p>实际上在Java中 lambda 表达式的作用非常有限,也只能转换为函数式接口,在其他语言中,可以声明函数类型、声明这些类型的变量,还可以使用变量保存函数表达式。</p><h1 id="方法引用"><a href="#方法引用" class="headerlink" title="方法引用"></a>方法引用</h1><p>有时候我们希望可已经有现成的方法可以完成你想要传递到其他代码的某个动作,比如希望定时器事件打印这个事件对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Timer t = <span class="keyword">new</span> Timer(<span class="number">1000</span>, event -> System.out.println(event)):</span><br></pre></td></tr></table></figure><p>但是入股哟能直接把print方法传递到Timer构造器就更简洁了,lambda</p><p>表达式也能够做到</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Timer t = <span class="keyword">new</span> Timer(<span class="number">100</span>, System.out::println);</span><br></pre></td></tr></table></figure><p><img src="/img/image-20220720173122252.png" alt="image-20220720173122252"></p>]]></content>
<summary type="html"><h1 id="lambda表达式的语法"><a href="#lambda表达式的语法" class="headerlink" title="lambda表达式的语法"></a>lambda表达式的语法</h1><p>了解过javax.swing.Timer函数和Compara</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
<category term="读书笔记" scheme="https://jiangliujl.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>《MyBatisPlus 常用注解》</title>
<link href="https://jiangliujl.github.io/2022/07/15/mybatisplus%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/"/>
<id>https://jiangliujl.github.io/2022/07/15/mybatisplus%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-11-17T01:15:09.686Z</updated>
<content type="html"><![CDATA[<h1 id="TableName"><a href="#TableName" class="headerlink" title="@TableName"></a>@TableName</h1><p>用法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@TableName("sys_user")</span></span><br></pre></td></tr></table></figure><p>描述:表名注解,标识实体类对应表</p><p>使用位置:实体类类注解</p><p>属性:</p><ul><li>value:表名</li><li>schema:用来指定模式名称,如果使用的是mysql,则指定数据库名称,如果使用oracle,则为schema</li><li>keepGlobalPrefix:是否保持使用全局的tablePrefix的值</li><li>resultMap:xml中resultMap的id</li><li>autoResultMap:是否自动构建ResultMap</li><li>excludeProperty:需要排除的属性名</li></ul><h1 id="TableId"><a href="#TableId" class="headerlink" title="@TableId"></a>@TableId</h1><p>用法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@TableId(value="user_id", type=IdType.ASSIGN_UUID)</span></span><br></pre></td></tr></table></figure><p> 描述:主键属性</p><p>使用位置:实体类主键字段</p><p>属性:</p><ul><li>value:主键字段名</li><li>type:指定主键类型</li></ul><h2 id="Type属性值"><a href="#Type属性值" class="headerlink" title="Type属性值"></a>Type属性值</h2><ul><li>AUTO:数据库ID自增</li><li>NONE:无状态,未设置主键类型(跟随全局,全局默认为INPUT)</li><li>INPUT:insert前自行设置</li><li>ASSIGN_ID:分配ID,使用接口<code>IdentifierGenerator</code>的<code>nextId</code>,实现类默认为雪花算法</li><li>ASSIGN_UUID:分配UUID</li></ul><h2 id="TableField"><a href="#TableField" class="headerlink" title="@TableField"></a>@TableField</h2><p>用法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@TableName("sys_user")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>{</span><br><span class="line"> <span class="meta">@TableId</span></span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"> <span class="meta">@TableField("nickname")</span></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"> <span class="keyword">private</span> String email;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>描述:字段注解(非主键)</p><p>属性:</p><ul><li>value:数据库字段名</li><li>exist:是否为数据库字段</li></ul>]]></content>
<summary type="html"><h1 id="TableName"><a href="#TableName" class="headerlink" title="@TableName"></a>@TableName</h1><p>用法</p>
<figure class="highlight java"><t</summary>
<category term="注解" scheme="https://jiangliujl.github.io/tags/%E6%B3%A8%E8%A7%A3/"/>
<category term="MyBatis Plus" scheme="https://jiangliujl.github.io/tags/MyBatis-Plus/"/>
</entry>
<entry>
<title>《事务传播行为》</title>
<link href="https://jiangliujl.github.io/2022/07/15/%E4%BA%8B%E5%8A%A1%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA/"/>
<id>https://jiangliujl.github.io/2022/07/15/%E4%BA%8B%E5%8A%A1%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-07-24T08:43:36.116Z</updated>
<content type="html"><![CDATA[<h1 id="什么是事务传播行为"><a href="#什么是事务传播行为" class="headerlink" title="什么是事务传播行为"></a>什么是事务传播行为</h1><p>我们在Spring中使用事务时,经常会在一个事务中调用另外一个事务,这种事务嵌套的控制方式就是事务传播行为</p><h1 id="事务传播行为的七种方式"><a href="#事务传播行为的七种方式" class="headerlink" title="事务传播行为的七种方式"></a>事务传播行为的七种方式</h1><ul><li>propagation_required<ul><li>事务传播的默认形式,如果当前没有事务,就新建一个事务,如果已经存在事务,就加入到这个事务中</li></ul></li><li>propagation_supports<ul><li>支持当前事务,如果当前没有事务,就以非事务方式执行</li></ul></li><li>propagation_mandatory<ul><li>使用当前事务,如果当前没有事务,就抛出异常</li></ul></li><li>propagation_requires_new<ul><li>新建事务,如果当前存在事务,就把当前事务挂起</li></ul></li><li>propagation_not_supported<ul><li>以非事务方式执行操作,如果当前存在事务,就把当前事务挂起</li></ul></li><li>propagation_never<ul><li>以非事务方式执行,如果当前存在事务,就抛出异常</li></ul></li><li>propagation_nested<ul><li>如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行propagation_required类似的操作</li></ul></li></ul><p>总结</p><p>如果事务嵌套,子事务回滚,如果子事务没有将父事务挂起,父事务也会回滚,不管父事务中是否有对子事务进行异常捕获</p>]]></content>
<summary type="html"><h1 id="什么是事务传播行为"><a href="#什么是事务传播行为" class="headerlink" title="什么是事务传播行为"></a>什么是事务传播行为</h1><p>我们在Spring中使用事务时,经常会在一个事务中调用另外一个事务,这种事务嵌套的控</summary>
<category term="SpringBoot" scheme="https://jiangliujl.github.io/tags/SpringBoot/"/>
<category term="事务" scheme="https://jiangliujl.github.io/tags/%E4%BA%8B%E5%8A%A1/"/>
<category term="MySQL" scheme="https://jiangliujl.github.io/tags/MySQL/"/>
</entry>
<entry>
<title>《事务失效》</title>
<link href="https://jiangliujl.github.io/2022/07/15/%E4%BA%8B%E5%8A%A1%E5%A4%B1%E6%95%88%E7%9A%84%E5%87%A0%E7%A7%8D%E6%83%85%E5%86%B5%E4%B8%8E%E5%8E%9F%E5%9B%A0/"/>
<id>https://jiangliujl.github.io/2022/07/15/%E4%BA%8B%E5%8A%A1%E5%A4%B1%E6%95%88%E7%9A%84%E5%87%A0%E7%A7%8D%E6%83%85%E5%86%B5%E4%B8%8E%E5%8E%9F%E5%9B%A0/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-07-24T08:47:07.815Z</updated>
<content type="html"><![CDATA[<h1 id="事务失效的几种情况与原因"><a href="#事务失效的几种情况与原因" class="headerlink" title="事务失效的几种情况与原因"></a>事务失效的几种情况与原因</h1><ul><li>service没有托管给spring</li><li>抛出受检异常</li><li>业务自己捕获了异常</li><li>切面顺序导致</li><li>非public方法</li><li>父子容器</li><li>方法被final修饰</li><li>方法被static修饰</li><li>调用本类方法</li><li>多线程调用</li><li>错误的传播行为</li><li>使用了不支持事务的存储引擎</li><li>数据源没有配置事务管理器</li><li>被代理的类过早实例化</li></ul><h2 id="service没有托管给spring"><a href="#service没有托管给spring" class="headerlink" title="service没有托管给spring"></a>service没有托管给spring</h2><p>事务的前提是service必须是一个bean对象</p><h2 id="抛出受检异常"><a href="#抛出受检异常" class="headerlink" title="抛出受检异常"></a>抛出受检异常</h2><p>spring默认回滚的是runtimeException,如果要触发其他异常的回滚,可以通过rollbackFor进行配置</p><h2 id="业务自己捕获了异常"><a href="#业务自己捕获了异常" class="headerlink" title="业务自己捕获了异常"></a>业务自己捕获了异常</h2><p>spring只有捕捉到了业务抛出的异常时,才会进行后续处理,如果业务自己捕获了异常并进行处理,事务无法感知</p><h2 id="切面顺序导致"><a href="#切面顺序导致" class="headerlink" title="切面顺序导致"></a>切面顺序导致</h2><p><img src="/img/image-20220713172742115.png" alt="image-20220713172742115"></p><p>因为spring事务本质上也是一个切面,自定义切面捕捉到了异常但是没有往外抛出,事务切面捕获不到异常</p><h2 id="非public方法"><a href="#非public方法" class="headerlink" title="非public方法"></a>非public方法</h2><p>spring事务默认生效的方法权限都必须为public</p><p>解决办法:</p><ul><li>修改方法为public</li><li>修改TransactionAttributeSource,将publicMethodsOnly修改为false</li><li>开启AspectJ代理</li></ul><h2 id="父子容器"><a href="#父子容器" class="headerlink" title="父子容器"></a>父子容器</h2><p>原因:子容器扫描范围过大,将未加事务配置的service扫描进来</p><p>这个一般用于spring整合springmvc中,springboot没有父子容器</p><h2 id="方法用final修饰"><a href="#方法用final修饰" class="headerlink" title="方法用final修饰"></a>方法用final修饰</h2><p>spring事务是用动态代理实现的,如果方法使用了final修饰,代理类无法对目标类进行重写,就无法实现事务</p><h2 id="方法用static修饰"><a href="#方法用static修饰" class="headerlink" title="方法用static修饰"></a>方法用static修饰</h2><p>原因和final一样</p><h2 id="调用本类方法"><a href="#调用本类方法" class="headerlink" title="调用本类方法"></a>调用本类方法</h2><p>调用本类方法不经过代理,就无法进行增强</p><h2 id="多线程调用"><a href="#多线程调用" class="headerlink" title="多线程调用"></a>多线程调用</h2><p>原因:spring的事务是通过数据库连接来实现的,而数据库连接spring是放在threadLocal里面的,同一个事务只能用同一个数据库连接。而多线程场景下,拿到的数据库连接不同,即属于不同事务</p><h2 id="错误的传播行为"><a href="#错误的传播行为" class="headerlink" title="错误的传播行为"></a>错误的传播行为</h2><p>详情看 事务传播行为</p><h2 id="使用了不支持事务的存储引擎"><a href="#使用了不支持事务的存储引擎" class="headerlink" title="使用了不支持事务的存储引擎"></a>使用了不支持事务的存储引擎</h2><p>比如mysql中的MyISAM就不支持事务</p><h2 id="数据源没有配置事务管理器"><a href="#数据源没有配置事务管理器" class="headerlink" title="数据源没有配置事务管理器"></a>数据源没有配置事务管理器</h2><p>springboot中默认开启事务管理器</p><h2 id="被代理的类被过早实例化"><a href="#被代理的类被过早实例化" class="headerlink" title="被代理的类被过早实例化"></a>被代理的类被过早实例化</h2><p>具体应该要看源码</p>]]></content>
<summary type="html"><h1 id="事务失效的几种情况与原因"><a href="#事务失效的几种情况与原因" class="headerlink" title="事务失效的几种情况与原因"></a>事务失效的几种情况与原因</h1><ul>
<li>service没有托管给spring</li>
</summary>
<category term="SpringBoot" scheme="https://jiangliujl.github.io/tags/SpringBoot/"/>
<category term="事务" scheme="https://jiangliujl.github.io/tags/%E4%BA%8B%E5%8A%A1/"/>
<category term="MySQL" scheme="https://jiangliujl.github.io/tags/MySQL/"/>
</entry>
<entry>
<title>《常用类与接口》</title>
<link href="https://jiangliujl.github.io/2022/07/15/%E5%B8%B8%E7%94%A8%E7%B1%BB%E4%B8%8E%E6%8E%A5%E5%8F%A3/"/>
<id>https://jiangliujl.github.io/2022/07/15/%E5%B8%B8%E7%94%A8%E7%B1%BB%E4%B8%8E%E6%8E%A5%E5%8F%A3/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-08-17T01:52:37.974Z</updated>
<content type="html"><![CDATA[<h1 id="Comparator接口"><a href="#Comparator接口" class="headerlink" title="Comparator接口"></a>Comparator接口</h1><p>遇到的问题:在开发中需要对一个含有实体类的泛型数组进行排序</p><p>comparator接口可以实现这个功能</p><p>简单用法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Collections_</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>{</span><br><span class="line"> List list = <span class="keyword">new</span> ArrayList();</span><br><span class="line"> list.add(<span class="string">"tom"</span>);</span><br><span class="line"> list.add(<span class="string">"smith"</span>);</span><br><span class="line"> list.add(<span class="string">"king"</span>);</span><br><span class="line"> list.add(<span class="string">"king"</span>);</span><br><span class="line"> list.add(<span class="string">"king"</span>);</span><br><span class="line"> list.add(<span class="string">"milan"</span>);</span><br><span class="line"></span><br><span class="line"> Collections.sort(list, <span class="keyword">new</span> Comparator() {</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">compare</span><span class="params">(Object o1, Object o2)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (o1 <span class="keyword">instanceof</span> String && o2 <span class="keyword">instanceof</span> String){</span><br><span class="line"> <span class="keyword">return</span> (((String) o1).length() - ((String) o2).length());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> System.out.println(list);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Cloneable接口"><a href="#Cloneable接口" class="headerlink" title="Cloneable接口"></a>Cloneable接口</h1><p>克隆接口,这个接口中提供了一个安全的clone方法</p><h1 id="DefaultIdentifierGenerator类"><a href="#DefaultIdentifierGenerator类" class="headerlink" title="DefaultIdentifierGenerator类"></a>DefaultIdentifierGenerator类</h1><p>位于<code>com.baomidou.mybatisplus.core</code>包中,用于生成雪花算法ID</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> id = <span class="keyword">new</span> DefaultIdentifierFenerator().nextId(<span class="keyword">new</span> Objec);</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="Comparator接口"><a href="#Comparator接口" class="headerlink" title="Comparator接口"></a>Comparator接口</h1><p>遇到的问题:在开发中需要对一个含有实体类的泛型数组进行排序<</summary>
<category term="Java" scheme="https://jiangliujl.github.io/tags/Java/"/>
</entry>
<entry>
<title>《SpringBoot常用注解》</title>
<link href="https://jiangliujl.github.io/2022/07/15/SpringBoot%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/"/>
<id>https://jiangliujl.github.io/2022/07/15/SpringBoot%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3/</id>
<published>2022-07-14T16:00:00.000Z</published>
<updated>2022-08-03T08:39:17.129Z</updated>
<content type="html"><![CDATA[<h1 id="Springboot常用注解"><a href="#Springboot常用注解" class="headerlink" title="Springboot常用注解"></a>Springboot常用注解</h1><ul><li>Value:属性赋值</li><li>Component:与业务层、dao层、控制层不相关的类需要在spring容器中创建使用</li><li>Mapper:注解当前类为mapper类</li><li>MapperScan:如果想要每个接口都变成实现类,那么需要在每个接口上添加Mapper注解,比较麻烦,可以使用MapperScan进行扫描</li><li>Service:表示当前层为Service层</li><li>Controller:控制层对象的创建</li><li>RestController:Controller与ResponseBody的结合,让当前类下web请求返回数据而不是视图</li><li>Autowired:根据类型自动注入</li><li>Resouce:根据名称自动注入</li></ul><h2 id="SpringBootApplication注解"><a href="#SpringBootApplication注解" class="headerlink" title="@SpringBootApplication注解"></a>@SpringBootApplication注解</h2><p>这个注解包含了三个注解,分别是:</p><p>@SpringBootConfiguration:自动扫描添加了@Configuration注解的类,读取其中的配置信息</p><p>@EnableAutoConfiguration:开启自动配置告诉Springboot基于所添加的依赖去猜测你想要如何配置spring,比如说我们引入了spring-boot-starter-web,而这个启动器中帮我们添加了tomcat、SpringMVC的依赖,此时自动配置就只要你是要开发一个web应用,就会帮我们去完成web以及springMVC的默认配置。</p><p>@ComponentScan:配置组件扫描</p><h2 id="Transaction"><a href="#Transaction" class="headerlink" title="@Transaction"></a>@Transaction</h2><p>事务注解</p><h3 id="失效场景"><a href="#失效场景" class="headerlink" title="失效场景"></a>失效场景</h3><p>具体看我的另一篇博客——《事务失效》</p><h2 id="EnableScheduling"><a href="#EnableScheduling" class="headerlink" title="@EnableScheduling"></a>@EnableScheduling</h2><p>开启定时任务,配合@Schedule注解使用,使这个注解功能可用</p><h2 id="Bean"><a href="#Bean" class="headerlink" title="@Bean"></a>@Bean</h2><p>告诉方法产生一个bean对象,然后将这个bean交给spring进行管理,在产生bean的时候这个方法会调用一次,然后将产生的bean对象放入spring容器中</p><h2 id="PostConstruct"><a href="#PostConstruct" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h2><p>在spring中,有一个接口叫<code>InitializationBean</code>,这个接口允许bean在合适的时机通过设置注解的初始化属性从而调用初始化方法,并且在这个接口中有一个定义好的初始化方法<code>afterPropertiesSet</code></p><p><strong>但是</strong>,spring并不推荐使用这种方法来调用初始化,它会将不必要的代码耦合到spring</p><p>相比于<code>InitializationBean</code>,spring更推荐我们使用<code>@PostConstruct</code>注解</p><p>至于为什么推荐使用<code>@PostConstruct</code>:</p><ul><li>InitializationBean是直接执行方法来进行初始化的,会耦合进Spring项目</li><li>@PostConstruct注解是通过反射机制来初始化的</li></ul><h2 id="ConfigurationProperties"><a href="#ConfigurationProperties" class="headerlink" title="@ConfigurationProperties"></a>@ConfigurationProperties</h2><p>在SpringBoot中,如果我们想要获取到配置文件中某个属性的值,有两个方法:</p><ul><li>@Value</li><li>@ConfigurationProperties</li></ul><p>这里我们只介绍第二个</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">config:</span></span><br><span class="line"><span class="attr">username:</span> <span class="string">JiangLiu</span></span><br><span class="line"><span class="attr">password:</span> <span class="number">123</span></span><br></pre></td></tr></table></figure><p>如果我们想要获取username,只需要这样</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(prefix = "config")</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestBean</span></span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> String username;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Qualifier"><a href="#Qualifier" class="headerlink" title="@Qualifier"></a>@Qualifier</h2><p><code>@Autowired</code>注解可以帮助我们进行spring依赖注入,但是很多场景下只用这个注解,spring并不知道我们需要注入哪个bean,比如B、C两个类同时继承A接口,这时我们注入A,spring就会抛出<code>NoUniqueBeanDefinitionException</code>异常,这时就需要使用@Qualifier注解</p>]]></content>
<summary type="html"><h1 id="Springboot常用注解"><a href="#Springboot常用注解" class="headerlink" title="Springboot常用注解"></a>Springboot常用注解</h1><ul>
<li>Value:属性赋值</li>
</summary>
<category term="注解" scheme="https://jiangliujl.github.io/tags/%E6%B3%A8%E8%A7%A3/"/>
<category term="SpringBoot" scheme="https://jiangliujl.github.io/tags/SpringBoot/"/>
</entry>
</feed>