-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrss2.xml
More file actions
622 lines (301 loc) · 381 KB
/
rss2.xml
File metadata and controls
622 lines (301 loc) · 381 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
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>92Hz</title>
<link>https://jongmin92.github.io/</link>
<image>
<url>https://www.gravatar.com/avatar/8b7f06e12e9f3c555a0587f51748ac7f</url>
<title>92Hz</title>
<link>https://jongmin92.github.io/</link>
</image>
<atom:link href="https://jongmin92.github.io/rss2.xml" rel="self" type="application/rss+xml"/>
<description></description>
<pubDate>Mon, 22 Mar 2021 13:29:53 GMT</pubDate>
<generator>http://hexo.io/</generator>
<item>
<title>코루틴 이해하기</title>
<link>https://jongmin92.github.io/2021/03/21/Kotlin/coroutines/</link>
<guid>https://jongmin92.github.io/2021/03/21/Kotlin/coroutines/</guid>
<pubDate>Sat, 20 Mar 2021 15:17:42 GMT</pubDate>
<description><p>코루틴은 Co + Routines의 합성어로 “여러개의 루틴이 함께 협력한다”라는 의미를 가진다. <strong>지연과 재개를 할 수 있다는 특징과 비선점 멀티태스킹의 서브루틴 형태로 협력형 태스크와 비슷하게 동작한다.</strong></p></description>
<content:encoded><![CDATA[<p>코루틴은 Co + Routines의 합성어로 “여러개의 루틴이 함께 협력한다”라는 의미를 가진다. <strong>지연과 재개를 할 수 있다는 특징과 비선점 멀티태스킹의 서브루틴 형태로 협력형 태스크와 비슷하게 동작한다.</strong></p><span id="more"></span><h1 id="코루틴이란"><a href="#코루틴이란" class="headerlink" title="코루틴이란"></a>코루틴이란</h1><p>코루틴은 아래의 키워드를 이용해서 정의할 수 있다.</p><ol><li><code>비선점 멀티태스킹(non-preemptive)</code>의 <code>서브루틴(subroutine)</code> 형태</li><li><code>지연(suspend)</code>과 <code>재개(resume)</code>가 가능함</li><li><strong>협력형(cooperative) 멀티태스크</strong>와 비슷하게 동작함</li></ol><p>하나씩 살펴보자.</p><h2 id="비선점-멀티태스킹"><a href="#비선점-멀티태스킹" class="headerlink" title="비선점 멀티태스킹"></a>비선점 멀티태스킹</h2><p>OS의 스케줄링을 공부하다보면 선점(preemptive)이라는 단어를 마주하게 된다. CPU와 같은 컴퓨터의 한정적인 자원을 이용해서 여러 개의 태스크(프로세스 혹은 스레드)를 동시에 실행하기 위해서는 자원을 어떻게 분할하여 사용할지에 대한 정책이 필요하다. 스케줄링은 이러한 정책을 정의한다. 즉 처리할 일들의 진행순서를 정하는 것이다.</p><p>스케줄링은 적용 시점에 따라서도 선점형과 비선점형 2가지로 구분할 수 있다.</p><ul><li>선점형 스케줄링(preemptive): <strong>어떤 프로세스가 CPU를 할당받아 실행 중에 있어도 스케줄러가 실행 중인 프로세스를 강제로 중지하고 다른 프로세스에게 CPU를 할당 할 수 있다.</strong> 모든 프로세스에게 CPU 사용 시간을 동일하게 부여할 수 있다. (ex. 시분할 스케줄링)</li><li><code>비선점형 스케줄링(non-preemptive)</code>: <strong>어떤 프로세스가 CPU를 할당 받으면 그 프레소스가 종료되거나 입출력 요구가 발생하여 자발적으로 중지될 때까지 계속 실행되도록 보장한다.</strong> 순서대로 처리되는 공정성이 있고 다음에 처리해야 할 프로세스와 관계없이 응답 시간을 예상할 수 있으며 선점 방식보다 스케줄러 호출 빈도가 낮고 <strong>문맥 교환(Context Switching)에 의한 오버헤드가 적다.</strong></li></ul><blockquote><p>비선점 멀티태스킹은 코루틴을 이해하는데 있어 중요한 키워드이다. 위의 정리된 내용에 이해가지 않는 부분이 있다면 다른 자료들을 참고해서 꼭 이해하고 넘어가자.</p></blockquote><h2 id="서브루틴"><a href="#서브루틴" class="headerlink" title="서브루틴"></a>서브루틴</h2><p>서브루틴은 반복되는 특정 기능을 별도로 묶어 놓아 이름을 붙인 것으로 보통 함수나 메서드라고 불린다. 서브루틴은 호출될 때마다 저장된 메모리로 이동했다가 return 을 통해 다시 원래 호출자의 위치로 돌아가게된다.</p><h2 id="지연과-재개"><a href="#지연과-재개" class="headerlink" title="지연과 재개"></a>지연과 재개</h2><p>코루틴이 비선점 멀티태스킹의 서브루틴 형태라는 것을 이해한다면 지연과 재개의 필요성은 자연스럽게 이해할 수 있다.<br>여러 스레드가 CPU 스케줄링의 관리 아래에서 협력적으로 동작한다면, <strong>코루틴은 스레드 내에서 자발적인 지연과 재개를 통해 협력적으로 동작한다.</strong> CPU 스케줄링에 직접적인 영향을 받지 않기 때문에 지연과 재개가 필요한 것이다.</p><blockquote><p><strong>CPS (Continuation Passing Style)</strong><br>스케줄링에 의해서 현재 CPU를 할당받아 실행되고 있는 스레드가 다른 스레드로 교체될 때는 문맥 교환이 발생하지만, 코루틴은 스레드 내에서 CPS를 이용해서 지연과 재개가 가능하다.</p></blockquote><h1 id="코루틴-vs-스레드"><a href="#코루틴-vs-스레드" class="headerlink" title="코루틴 vs 스레드"></a>코루틴 vs 스레드</h1><p>지금까지의 내용을 바탕으로 코틀린 코루틴과 스레드를 다음과 같이 비교할 수 있다.</p><ul><li>코틀린 코루틴<ul><li>기본적으로는 협력형이며 병행적으로 동작하지 않는다. (옵션을 통해서 여러개의 스레드로 분할하여 병행적으로 동작하게 할 수 있다.)</li><li>스케줄러가 실행시점을 결정하는 것이 아닌 프로그래머나 이벤트에 의해 실행 및 지연(Suspend), 재개(Resume) 시점이 결정된다.</li><li>비선점형 멀티태스크, 동시성(Concurrency)을 제공한다.</li><li>독립적으로 스택을 가질 수도 있으나 일반적으로 스택을 갖지 않는다.</li></ul></li><li>스레드<ul><li>CPU 수에 따라서 완전히 병행적으로 동작할 수 있다.</li><li>OS의 스케줄러가 실행 시점을 결정한다. (스케줄러가 선점한다.)</li><li>선점형 멀티태스크, 멀티프로세싱, 병행성을 제공한다.</li><li>스레드별 독립적인 스택을 가진다.</li></ul></li></ul><h1 id="결과"><a href="#결과" class="headerlink" title="결과"></a>결과</h1><p>일반적으로 스레드 한개를 생성하는데 필요한 메모리는 1M 정도로, 2000개 정도의 스레드를 생성하기 위해서는 1.5GB 이상의 메모리가 필요하다. 그러나 100만 개의 코루틴은 700MB 미만의 메모리면 충분하다.</p><p><strong>결과적으로 컨텍스트 스위칭과 같은 비용이 많이 드는 작업 없이 지연과 재개를 이용한 비선점 멀티태스킹 방식으로 동시성을 프로그래밍을 가능하게 한다.</strong></p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Kotlin/">Kotlin</category>
<category domain="https://jongmin92.github.io/tags/Kotlin/">Kotlin</category>
<category domain="https://jongmin92.github.io/tags/Coroutines/">Coroutines</category>
<comments>https://jongmin92.github.io/2021/03/21/Kotlin/coroutines/#disqus_thread</comments>
</item>
<item>
<title>Unit Test에서 AssertThat을 사용하자</title>
<link>https://jongmin92.github.io/2020/03/31/Java/use-assertthat/</link>
<guid>https://jongmin92.github.io/2020/03/31/Java/use-assertthat/</guid>
<pubDate>Mon, 30 Mar 2020 15:44:27 GMT</pubDate>
<description><p>Junit 4.4부터 <code>assertThat</code> 메서드가 추가됐다. 이 메서드는 <a href="http://hamcrest.org/JavaHamcrest/">hamcrest</a> 라이브러리의 사용을 통합하며 assertions을 작성하는데 있어 더 나은 방법을 제공한다. hamcrest가 static 메서드로 제공하는 여러 matcher를 사용할 수 있고 이러한 static 메서드는 체이닝할 수 있어서 기존 assertXXX 메서드보다 더 많은 유연성을 제공한다. 그 외에도 assertThat을 사용했을 때 어떤 이점이 있는지 알아보자.</p></description>
<content:encoded><![CDATA[<p>Junit 4.4부터 <code>assertThat</code> 메서드가 추가됐다. 이 메서드는 <a href="http://hamcrest.org/JavaHamcrest/">hamcrest</a> 라이브러리의 사용을 통합하며 assertions을 작성하는데 있어 더 나은 방법을 제공한다. hamcrest가 static 메서드로 제공하는 여러 matcher를 사용할 수 있고 이러한 static 메서드는 체이닝할 수 있어서 기존 assertXXX 메서드보다 더 많은 유연성을 제공한다. 그 외에도 assertThat을 사용했을 때 어떤 이점이 있는지 알아보자.</p><span id="more"></span><blockquote><p>해당 포스팅에서 사용한 코드는 <a href="https://github.com/jongmin92/code-examples/tree/master/java/assert-that">https://github.com/jongmin92/code-examples/tree/master/java/assert-that</a> 에서 확인할 수 있습니다.</p></blockquote><h1 id="가독성"><a href="#가독성" class="headerlink" title="가독성"></a>가독성</h1><p><strong>assertThat이 assertXXX를 사용할 때 보다 더 읽기 쉽다.</strong><br>두 값(객체)을 비교할 때 주로 사용하는 assertEquals를 먼저 살펴보자.</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">assertEquals(expected, actual);</span><br></pre></td></tr></table></figure><p>assertEquals를 사용할 때 마다 expected와 actual의 위치에 대해서 헷갈릴 때가 많다. assertThat을 사용해서 작성하면 그 의미를 더 분명히 할 수 있다.</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">assertThat(actual, is(equalTo(expected)));</span><br><span class="line"><span class="comment">// is(equalTo)는 is로 대체할 수 있다.</span></span><br><span class="line">assertThat(actual, is(expected));</span><br></pre></td></tr></table></figure><p>assertThat을 사용하면 expected와 actual이 들어갈 위치가 조금 더 명확히 보인다. “actual이 expected와 같다(= 실제 값이 예상하는 값과 같다)” 라는 식으로 문장으로 읽히기도 한다.</p><p>하나의 예를 더 살펴보자. assertNotEquals가 없기 때문에 두 변수에 대한 equals를 수행하고 assertFalse를 사용해 검증해야한다.</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">assertFalse(expected.equals(actual));</span><br></pre></td></tr></table></figure><p>이를 assertThat으로 변경하면 의미가 더 분명하고 읽기 쉽게 사용할 수 있다.</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">asserThat(actual, is(not(equalTo(expected))));</span><br><span class="line"><span class="comment">// is(equalTo)는 is로 대체할 수 있다.</span></span><br><span class="line">asserThat(actual, is(not(expected)));</span><br></pre></td></tr></table></figure><h1 id="Failure-메시지"><a href="#Failure-메시지" class="headerlink" title="Failure 메시지"></a>Failure 메시지</h1><p><strong>assertThat은 더 나은 에러 메시지를 제공한다.</strong> </p><p>먼저 assertTrue를 사용하는 예와 실패 메시지를 살펴보자.</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">assertTrue(expected.contains(actual));</span><br><span class="line"></span><br><span class="line">--- 실행 결과 ---</span><br><span class="line">java.lang.AssertionError</span><br><span class="line">at org.junit.Assert.fail(Assert.java:<span class="number">86</span>)</span><br><span class="line">at org.junit.Assert.assertTrue(Assert.java:<span class="number">41</span>)</span><br><span class="line">at org.junit.Assert.assertTrue(Assert.java:<span class="number">52</span>)</span><br></pre></td></tr></table></figure><p>특정 string을 포함하는지 확인하기 위해서는 assertStringContains와 같은 메서드가 없기 때문에 contains 메서드를 사용하고 assertTrue을 이용해 검증해야 한다. 여기서 문제는 assertion error는 expected 값과 actual 값에 대해 알려주지 않는다는 것이다.</p><p>assertThat을 사용하도록 변경해보자.</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">assertThat(actual, containsString(expected));</span><br><span class="line"></span><br><span class="line">--- 실행 결과 ---</span><br><span class="line">Expected: a string containing <span class="string">"expected"</span></span><br><span class="line"> but: was <span class="string">"actual"</span></span><br><span class="line">java.lang.AssertionError: </span><br><span class="line">Expected: a string containing <span class="string">"expected"</span></span><br><span class="line"> but: was <span class="string">"actual"</span></span><br><span class="line">at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:<span class="number">20</span>)</span><br></pre></td></tr></table></figure><p>expected 값과 actual 값 모두 에러 메시지에 반환된다. 원인을 찾기 위해 별도의 디버깅 필요없이 에러 메시지 만으로 잘못된 부분을 바로 파악할 수 있다.</p><h1 id="Type-안정성"><a href="#Type-안정성" class="headerlink" title="Type 안정성"></a>Type 안정성</h1><p><strong>assertThat을 사용하면 Type에 대한 안정성도 얻을 수 있다.</strong></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">assertEquals(<span class="string">"abc"</span>, <span class="number">123</span>); <span class="comment">// 컴파일에는 성공하지만, 실행시 실패한다.</span></span><br></pre></td></tr></table></figure><p>assertEquals의 구현은 다음과 같다. (Object로 인자를 받고 있다.)</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="function"><span class="keyword">static</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title">assertEquals</span><span class="params">(Object expected, Object actual)</span> </span>{</span><br><span class="line"> assertEquals(<span class="keyword">null</span>, expected, actual);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>반면에 assertThat은 위와 같은 경우 컴파일을 허용하지 않는다.</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">assertThat(<span class="number">123</span>, is(<span class="string">"abc"</span>)); <span class="comment">// 컴파일 실패</span></span><br></pre></td></tr></table></figure><p>assertThat의 구현은 다음과 같다. (Generic을 사용하고 있어 Type이 체크된다.)</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">public</span> <span class="keyword">static</span> <T> <span class="function"><span class="keyword">void</span> <span class="title">assertThat</span><span class="params">(T actual, Matcher<? <span class="keyword">super</span> T> matcher)</span> </span>{</span><br><span class="line"> assertThat(<span class="string">""</span>, actual, matcher);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="유연성"><a href="#유연성" class="headerlink" title="유연성"></a>유연성</h1><p><strong>hamcrest는 <code>anyOf</code>와 <code>allOf</code>와 같은 논리 matcher도 제공한다.</strong></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">assertThat(<span class="string">"test"</span>, allOf(is(<span class="string">"test2"</span>), containsString(<span class="string">"te"</span>)));</span><br></pre></td></tr></table></figure><p>allOf matcher는 and 논리 연산자처럼 동작한다. 따라서 제공된 모든 matcher에 통과해야한다. 실패하는 경우 다음과 같이 실패한 matcher에 대해서 에러 메시지를 반환한다.</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">Expected: (a string containing <span class="string">"te"</span> and is <span class="string">"test2"</span>)</span><br><span class="line"> but: is <span class="string">"test2"</span> was <span class="string">"test"</span></span><br><span class="line">java.lang.AssertionError: </span><br><span class="line">Expected: (a string containing <span class="string">"te"</span> and is <span class="string">"test2"</span>)</span><br><span class="line"> but: is <span class="string">"test2"</span> was <span class="string">"test"</span></span><br><span class="line">at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:<span class="number">20</span>)</span><br></pre></td></tr></table></figure><h1 id="Custom-Matchers"><a href="#Custom-Matchers" class="headerlink" title="Custom Matchers"></a>Custom Matchers</h1><p>custom assert 메서드를 만들 수 있는 것처럼 다음과 같이 custom matcher를 생성할 수 있다.</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.hamcrest.Description;</span><br><span class="line"><span class="keyword">import</span> org.hamcrest.TypeSafeMatcher;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomMatcher</span> <span class="keyword">extends</span> <span class="title">TypeSafeMatcher</span><<span class="title">String</span>> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String expected;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">CustomMatcher</span><span class="params">(String expected)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.expected = expected;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">matchesSafely</span><span class="params">(String item)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> expected.equals(item);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">describeTo</span><span class="params">(Description description)</span> </span>{</span><br><span class="line"> description.appendValue(expected);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h1><p>assert 메서드와 그에 상응하는 assertThat(hamcrest 1.3 기준) matcher에 대해서 알아보자.</p><blockquote><p>assertThat은 org.junit.Assert.assertThat를 static import해서 사용한다.</p></blockquote><table><thead><tr><th>assert method</th><th>assertThat</th><th>static Import</th></tr></thead><tbody><tr><td>assertEquals(“expected”, “actual”);</td><td>assertThat(“actual”, is(“expected”));</td><td>org.hamcrest.core.Is.is</td></tr><tr><td>assertArrayEquals(new String[]{“test1”, “test2”}, new String[]{“test3”, “test4”});</td><td>assertThat(new String[]{“test1”, “test2”}, is(new String[]{“test3”, “test4”}));</td><td>org.hamcrest.core.Is.is</td></tr><tr><td>assertTrue(value);</td><td>assertThat(actual, is(true));</td><td>org.hamcrest.core.Is.is</td></tr><tr><td>assertFalse(value);</td><td>assertThat(actual, is(false));</td><td>org.hamcrest.core.Is.is</td></tr><tr><td>assertNull(value);</td><td>assertThat(actual, nullValue);</td><td>org.hamcrest.core.IsNull.nullValue</td></tr><tr><td>assertNotNull(value);</td><td>assertThat(actual, notNullValue);</td><td>org.hamcrest.core.IsNull.notNullValue</td></tr><tr><td>assertSame(expected, actual);</td><td>assertThat(actual, sameInstance(expected));</td><td>org.hamcrest.core.IsSame.sameInstance</td></tr><tr><td>assertNotSame(expected, actual);</td><td>assertThat(actual, not(sameInstance(expected)));</td><td>org.hamcrest.core.IsNot.not, org.hamcrest.core.IsSame.sameInstance</td></tr><tr><td>assertTrue(1 > 3);</td><td>assertThat(1, greaterThan(3));</td><td>org.hamcrest.number.OrderingComparison.greaterThan</td></tr><tr><td>assertTrue(“abc”.contains(“d”));</td><td>assertThat(“abc”, containsString(“d”));</td><td>org.hamcrest.core.StringContains.containsString</td></tr></tbody></table><p>이 외에도 더 많은 matcher가 있다.</p><ul><li>org.hamcrest.beans<ul><li>HasProperty</li><li>HasPropertyWithValue</li><li>SamePropertyValuesAs</li></ul></li><li>org.hamcrest.collection<ul><li>IsArray</li><li>IsArrayContaining</li><li>IsArrayContainingInAnyOrder</li><li>IsArrayContainingInOrder</li><li>IsArrayContainingWithSize</li><li>IsCollectionWithSize</li><li>IsEmptyCollection</li><li>IsEmptyIterable</li><li>IsIn</li><li>IsIterableContainingInAnyOrder</li><li>IsIterableContainingInOrder</li><li>IsIterableWithSize</li><li>IsMapContaining</li></ul></li><li>org.hamcrest.number<ul><li>BigDecimalCloseTo</li><li>IsCloseTo</li><li>OrderingComparison</li></ul></li><li>org.hamcrest.object<ul><li>HasToString</li><li>IsCompatibleType</li><li>IsEventFrom</li></ul></li><li>org.hamcrest.text<ul><li>IsEmptyString</li><li>IsEqualIgnoringCase</li><li>IsEqualIgnoringWhiteSpace</li><li>StringContainsInOrder</li></ul></li></ul>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<comments>https://jongmin92.github.io/2020/03/31/Java/use-assertthat/#disqus_thread</comments>
</item>
<item>
<title>Encryption - RSA</title>
<link>https://jongmin92.github.io/2020/01/02/Java/rsa/</link>
<guid>https://jongmin92.github.io/2020/01/02/Java/rsa/</guid>
<pubDate>Thu, 02 Jan 2020 13:29:00 GMT</pubDate>
<description><p>이전에 <a href="https://jongmin92.github.io/2019/12/18/Java/hash/">&lt;Hash - MD5와 SHA256&gt;</a>에서 해시(Hash)에 대해 설명하며 암호화(Encryption)와 다른점에 대해 간략히 알아보았다.<br>이번에는 암호화에 대해서 조금 더 자세히 알아보자.</p></description>
<content:encoded><![CDATA[<p>이전에 <a href="https://jongmin92.github.io/2019/12/18/Java/hash/"><Hash - MD5와 SHA256></a>에서 해시(Hash)에 대해 설명하며 암호화(Encryption)와 다른점에 대해 간략히 알아보았다.<br>이번에는 암호화에 대해서 조금 더 자세히 알아보자.</p><span id="more"></span><h1 id="암호화-Encryption"><a href="#암호화-Encryption" class="headerlink" title="암호화 (Encryption)"></a>암호화 (Encryption)</h1><p><strong><code>암호화(Encryption)</code>은 <code>평문(PlanText)</code>을 <code>암호문(CipherText)</code>으로 변환하는 과정을 말한다. 반대로 <code>복호화(Decryption)</code>은 암호문을 평문으로 변환하는 과정을 말한다.</strong></p><ul><li>암호화: 평문 -> 암호문</li><li>복호화: 암호문 -> 평문</li></ul><p><img src="/images/post/2019-12-18/encryption.png" alt="encryption"></p><p><strong>이 과정에서 암호화와 복호화에 사용하는 키(Key)를 같은 것을 사용하는 경우를 <code>대칭키 암호화(Symmetric Key Encryption)</code>라 부르고, 암호화와 복호화에 서로 다른 키(공개키, 비공개키)를 사용하는 경우는 <code>비대칭키 암호화(Asymmetric Key Encryption)</code>라고 부른다.</strong></p><ul><li>대칭키 암호화: 암호화와 복호화에 같은 키를 사용</li><li>비대칭키 암호화: 암호화와 복호화에 서로 다른 키를 사용 (공개캐, 비공개키)</li></ul><h1 id="대칭키-암호화-Symmetric-Key-Encryption"><a href="#대칭키-암호화-Symmetric-Key-Encryption" class="headerlink" title="대칭키 암호화 (Symmetric Key Encryption)"></a>대칭키 암호화 (Symmetric Key Encryption)</h1><p>대칭키 암호화는 오래전부터 사용되어온 방식이다. 암호화와 복호화에 같은 키를 사용하는데 보통 PSK(Pre-Shared Key)라고도 표현한다. 영문 그대로 미리 공유된 키를 말한다. 대칭키 암호화를 위해서는 <strong>평문을 암호화해서 전달하는 송신자와 암호문을 받아 평문으로 복호화하려는 수신자가 서로 동일한 키를 암호화가 시작되기 전에 서로 공유해야 한다.</strong></p><p>대칭키 암호화에는 <a href="https://ko.wikipedia.org/wiki/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%95%94%ED%98%B8%ED%99%94_%ED%91%9C%EC%A4%80">DES(Data Encryption Standard)</a>, <a href="https://ko.wikipedia.org/wiki/%EA%B3%A0%EA%B8%89_%EC%95%94%ED%98%B8%ED%99%94_%ED%91%9C%EC%A4%80">AES(Advanced Encryption Standard)</a>와 같은 알고리즘이 대표적으로 사용된다.</p><p>그러나 대칭키 암호화 방식에는 문제가 있다. <strong>처음 서로 키를 공유하는 과정에서 만약 누군가 중간에서 이 키를 가로챌 수 있다면, 그 후에 해당 키를 이용해 암호화되어 전달되는 암호문들은 중간에서 쉽게 복호화되어 그 내용이 전부 노출될 수 있다.</strong></p><p>이런 문제를 근본적으로 해결하고 암호키를 쉽게 전달하기 위해 나온 방법이 비대칭키 암호화이다.</p><h1 id="비대칭키-암호화-Asymmetric-Key-Encryption"><a href="#비대칭키-암호화-Asymmetric-Key-Encryption" class="headerlink" title="비대칭키 암호화 (Asymmetric Key Encryption)"></a>비대칭키 암호화 (Asymmetric Key Encryption)</h1><p>비대칭키 암호화는 암호화와 복호화에 서로 다른 키(공개캐, 비공개키)를 사용 한다. 비대칭키 알고리즘에는 대표적으로 <a href="https://ko.wikipedia.org/wiki/RSA_%EC%95%94%ED%98%B8">RSA(Rivest–Shamir–Adleman)</a>가 사용된다.</p><h2 id="RSA-Rivest–Shamir–Adleman"><a href="#RSA-Rivest–Shamir–Adleman" class="headerlink" title="RSA (Rivest–Shamir–Adleman)"></a>RSA (Rivest–Shamir–Adleman)</h2><p>RSA는 수학적인 기법을 통해 한 쌍의 키를 생성한다. 보통 한 쌍의 키를 각각 공개키와 비공개키라고 부르며 다음과 같은 용도로 사용된다.</p><ul><li>공개키(Publick Key): 누구에게나 공개될 수 있으며 데이터를 보내는 발신자는 공개키를 통해 정보를 암호화한다.</li><li>비공개키(Private Key, 비밀키): 수신자는 비밀키를 암호화된 메세지를 복호화 하는데 사용한다. 외부에 노출되지 않도록 안전하게 보관해야 한다.</li></ul><p>이와 같이 RSA를 이용한 공개키 암호화 방식은 비공개키를 외부에 노출할 위험이 없어 기존의 대칭키 암호화 방식의 문제를 해결하고 있다.</p><p>일반적으로는 공개키는 암호화에만 사용되고 비밀키는 복호화에만 사용된다고 잘못 알려져 있다. <strong>공개키로 암호화된 것을 비밀키로 복호화할 수 있을 뿐만 아니라, 비밀키로 암호화된 것을 공개키를 이용해서도 복호화할 수 있다.</strong> 이러한 한 쌍의 암호화 키를 이용하는 방식이 RSA 알고리즘인 것이다.</p><p>RSA가 사용되고 있는 사례는 주변에서 생각보다 쉽게 찾을 수 있다. <strong>SSL/TLS가 통신을 암호화하기 위해 사용하는 대칭키를 교환하기 위해서 RSA(비대칭키)를 사용하고 있다.</strong></p><p>SSL/TLS에서 암호화에 사용하려는 대칭키를 전달(공유) 할 때, 이 대칭키를 RSA의 공개키로 암호화 하여 상대방에게 전달하고, 비밀키를 가지고 있는 상대방만 이 암호화 내용을 복호화 하여 대칭키를 획득할 수 있게 하는 것이 SSL/TLS에서 RSA를 통해 대칭키를 공유하는 방식인 것이다. 이후로는 이렇게 공유된 대칭키를 이용하여 송수신자는 암호화 통신을 하게 된다.</p><p>한 가지 의문이 있을 수 있다. 처음부터 대칭키가 아닌 비대칭키 이용해서 모든 통신을 암복호화 하여 통신하면 간단하고 좋겠지만, <strong>RSA와 같은 비대칭키 암호화 방식은 복잡한 수학적 원리로 이루어져 있어 많은 CPU 리소스를 소모하게 된다.</strong> 따라서 대칭키를 공유 할 때만 비대칭키 암호화 방식으로 대칭키를 공유하고, 그 후로는 공유된 대칭키를 이용해서 CPU 리소스를 덜 소모하는 암호화 방식을 사용해서 통신하는 것이다.</p><blockquote><p><strong>RSA는 암호화뿐만 아니라 <a href="https://ko.wikipedia.org/wiki/%EC%A0%84%EC%9E%90%EC%84%9C%EB%AA%85">전자서명</a>이 가능한 최초의 알고리즘으로 알려져 있다.</strong> RSA가 갖는 전자서명 기능은 인증을 요구하는 전자 상거래와 같은 곳에서 사용된다.</p></blockquote><h1 id="예제"><a href="#예제" class="headerlink" title="예제"></a>예제</h1><p>kotlin을 이용해 간단히 RSA(public key, private key) 키를 생성하고, 이를 이용해 암복호화를 해보자.</p><h2 id="키-생성"><a href="#키-생성" class="headerlink" title="키 생성"></a>키 생성</h2><p><a href="https://docs.oracle.com/javase/7/docs/api/java/security/KeyPairGenerator.html">java.security.KeyPairGenerator</a>를 이용하면 쉽게 RSA 키를 생성할 수 있다.</p><blockquote><p>The <strong>KeyPairGenerator</strong> class is used to generate pairs of public and private keys. Key pair generators are constructed using the getInstance factory methods (static methods that return instances of a given class).<br>A Key pair generator for a particular algorithm creates a public/private key pair that can be used with this algorithm. It also associates algorithm-specific parameters with each of the generated keys.<br>There are two ways to generate a key pair: in an algorithm-independent manner, and in an algorithm-specific manner. The only difference between the two is the initialization of the object:</p></blockquote><p>공개키는 <a href="https://ko.wikipedia.org/wiki/X.509">X.509</a> 형식으로 생성된다.</p><blockquote><p><strong>X.509</strong>는 공개키 인증서와 인증알고리즘의 표준 가운데에서 <strong>공개 키 기반(PKI)의 ITU-T 표준</strong>이다.</p></blockquote><p>비공개키(개인키)는 <a href="https://ko.wikipedia.org/wiki/%EA%B3%B5%EA%B0%9C_%ED%82%A4_%EC%95%94%ED%98%B8_%ED%91%9C%EC%A4%80">PKCS #8</a>(Public-Key Cryptography Standard) 형식으로 생성된다.</p><blockquote><p><strong>PKCS #8</strong>는 <strong>개인키 정보 문법 표준</strong>으로 RFC 5208에 기술되었다. 공개키 암호에서 사용되는 비밀키 값에 대한 문법을 정의한다.</p></blockquote><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RSAKeyPairGenerator</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> privateKey: PrivateKey</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> publicKey: PublicKey</span><br><span class="line"></span><br><span class="line"> <span class="keyword">init</span> {</span><br><span class="line"> <span class="keyword">val</span> keyGen = KeyPairGenerator.getInstance(<span class="string">"RSA"</span>)</span><br><span class="line"> keyGen.initialize(<span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">val</span> keyPair = keyGen.generateKeyPair()</span><br><span class="line"> <span class="keyword">this</span>.privateKey = keyPair.<span class="keyword">private</span></span><br><span class="line"> <span class="keyword">this</span>.publicKey = keyPair.<span class="keyword">public</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">writeToFile</span><span class="params">(path: <span class="type">String</span>, key: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> file = File(path)</span><br><span class="line"> file.parentFile.mkdir();</span><br><span class="line"></span><br><span class="line"> file.bufferedWriter().use {</span><br><span class="line"> it.write(key)</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">fun</span> <span class="title">getPrivateKey</span><span class="params">()</span></span>: PrivateKey {</span><br><span class="line"> <span class="keyword">return</span> privateKey</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">getPublicKey</span><span class="params">()</span></span>: PublicKey {</span><br><span class="line"> <span class="keyword">return</span> publicKey</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">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> keyPairGenerator = RSAKeyPairGenerator()</span><br><span class="line"> <span class="keyword">val</span> publicKey = keyPairGenerator.getPublicKey().encoded</span><br><span class="line"> <span class="keyword">val</span> privateKey = keyPairGenerator.getPrivateKey().encoded</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> base64PublicKey = Base64.getEncoder().encodeToString(publicKey)</span><br><span class="line"> <span class="keyword">val</span> base64PrivateKey = Base64.getEncoder().encodeToString(privateKey)</span><br><span class="line"></span><br><span class="line"> keyPairGenerator.writeToFile(<span class="string">"RSA/publicKey"</span>, base64PublicKey)</span><br><span class="line"> keyPairGenerator.writeToFile(<span class="string">"RSA/privateKey"</span>, base64PrivateKey)</span><br><span class="line"></span><br><span class="line"> println(<span class="string">"public key: <span class="variable">$base64PublicKey</span>"</span>)</span><br><span class="line"> println(<span class="string">"private key: <span class="variable">$base64PrivateKey</span>"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="privateKey"><a href="#privateKey" class="headerlink" title="privateKey"></a>privateKey</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKxfz0f6BTZVergW5tZxHgUIbVmApXb01ozq11fukFsJx2WbfvxddyxU3P6NEZjoSYCM5NPjvtscMtvfErL93qa3cVrKgBXumclfMAe7ijHeesiC/0RUnSoV7ytdz9AXn0TvjzIxAibGL8HxdGGMgAtYERM7/NYJ/JU7o7jYWhKFAgMBAAECgYBIiwXUF8+rvyunX9QEOZTVr2c9vJtmRcIpigfYtMjB14q4I0m88aTe3lQnOL1IKbINTL5cwkMnOWXaDLZ058yUkfDdahjaCxg9z9/jY2cxREvMR/oWtY5FwrodDKvoTURC8Hz+TZg4a9S1NofKY8W4YBqxpW3PKvBkIVl6beECfQJBAOEW9VdkwT/zgXwUXvivZAqlbFkOkr9zBE+tvQ+QEY6rLMgIoEa1NK/xx1gWt+c6xLMjE8f7GS7HEkCbQOtlvXcCQQDEC6MnswfK7arpv02odhc92ViHCGI1W8rvFlG4SL0RHI+VxAIBaEL+1RN9k4aCSWLrCLtLzaSa+8ArFp2r0f7jAkEA3Ck+k9qjAtBEqH6sXgX/jkI7dehBNS1k3CKNt/kskyVuycFWM5LuE+IjH1ApVOwwlR8MLCC4gv6IJdU1bIm5BQJBAIr/PUSueL32WJG2Y1cnsz7U1SGYXhk65d0yU+p3GCYDvAIRoOJii+2mIVWNvXaulYXTAQiz2xtPl2Z1eIEUOMUCQEdR7nswIOfV71fp1sTngUM3GpLDrT3uA9M8/2ND3QnfrVDc+gFWkj+OmKeDsLx7Rhs+liVP9qgFppC9IJxcmyI=</span><br></pre></td></tr></table></figure><h3 id="publicKey"><a href="#publicKey" class="headerlink" title="publicKey"></a>publicKey</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsX89H+gU2VXq4FubWcR4FCG1ZgKV29NaM6tdX7pBbCcdlm378XXcsVNz+jRGY6EmAjOTT477bHDLb3xKy/d6mt3FayoAV7pnJXzAHu4ox3nrIgv9EVJ0qFe8rXc/QF59E748yMQImxi/B8XRhjIALWBETO/zWCfyVO6O42FoShQIDAQAB</span><br></pre></td></tr></table></figure><h2 id="암복호화"><a href="#암복호화" class="headerlink" title="암복호화"></a>암복호화</h2><p>X.509 형식의 데이터를 다시 RSA 공개키로 변환하기 위해서는 <a href="https://docs.oracle.com/javase/7/docs/api/java/security/spec/X509EncodedKeySpec.html">X509EncodedKeySpec</a>을 사용한다.<br>PKCS #8 형식의 데이터를 RSA 비공개키로 변환하기 위해서는 <a href="https://docs.oracle.com/javase/7/docs/api/java/security/spec/PKCS8EncodedKeySpec.html">PKCS8EncodedKeySpec</a>를 사용한다.</p><ul><li>공개키: X.509 -> X509EncodedKeySpec</li><li>비공개키: PKCS #8 -> PKCS8EncodedKeySpec</li></ul><figure class="highlight kotlin"><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="keyword">object</span> RSAUtil {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> keyFactory = KeyFactory.getInstance(<span class="string">"RSA"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">getPublicKey</span><span class="params">(base64PublicKey: <span class="type">String</span>)</span></span>: PublicKey {</span><br><span class="line"> <span class="keyword">val</span> keySpec = X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey.toByteArray()))</span><br><span class="line"> <span class="keyword">return</span> keyFactory.generatePublic(keySpec)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">getPrivateKey</span><span class="params">(base64PrivateKey: <span class="type">String</span>)</span></span>: PrivateKey {</span><br><span class="line"> <span class="keyword">val</span> keySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey.toByteArray()))</span><br><span class="line"> <span class="keyword">return</span> keyFactory.generatePrivate(keySpec)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">encrypt</span><span class="params">(<span class="keyword">data</span>: <span class="type">String</span>, base64PublicKey: <span class="type">String</span>)</span></span>: ByteArray {</span><br><span class="line"> <span class="keyword">val</span> cipher = Cipher.getInstance(<span class="string">"RSA/ECB/PKCS1Padding"</span>)</span><br><span class="line"> cipher.<span class="keyword">init</span>(Cipher.ENCRYPT_MODE, getPublicKey(base64PublicKey))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> cipher.doFinal(<span class="keyword">data</span>.toByteArray())</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">decrypt</span><span class="params">(<span class="keyword">data</span>: <span class="type">ByteArray</span>, privateKey: <span class="type">PrivateKey</span>)</span></span>: String {</span><br><span class="line"> <span class="keyword">val</span> cipher = Cipher.getInstance(<span class="string">"RSA/ECB/PKCS1Padding"</span>)</span><br><span class="line"> cipher.<span class="keyword">init</span>(Cipher.DECRYPT_MODE, privateKey)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> String(cipher.doFinal(<span class="keyword">data</span>))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">decrypt</span><span class="params">(<span class="keyword">data</span>: <span class="type">String</span>, base64PrivateKey: <span class="type">String</span>)</span></span>: String {</span><br><span class="line"> <span class="keyword">return</span> decrypt(Base64.getDecoder().decode(<span class="keyword">data</span>.toByteArray()), getPrivateKey(base64PrivateKey))</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">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> publicKey =</span><br><span class="line"> <span class="string">"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCya66h9+mG6z3/3lSV/IjfXFxH/Y0CPgc+YLIjui5JmbNgPC2EQ4GJPsT0CbxjcMTnrSvyj7JzBbJhzmsTBD+HuKEiQOOlGNhPS0HIDvpk+J4DDyuGoTVHnWkuxNC9+IlbkWZQMWHcQ42VCFJwvduESOhs01vSFQCRBYNzYL54HQIDAQAB"</span></span><br><span class="line"> <span class="keyword">val</span> privateKey =</span><br><span class="line"> <span class="string">"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALJrrqH36YbrPf/eVJX8iN9cXEf9jQI+Bz5gsiO6LkmZs2A8LYRDgYk+xPQJvGNwxOetK/KPsnMFsmHOaxMEP4e4oSJA46UY2E9LQcgO+mT4ngMPK4ahNUedaS7E0L34iVuRZlAxYdxDjZUIUnC924RI6GzTW9IVAJEFg3NgvngdAgMBAAECgYEAmAd/Z03ac/dQ/gxRYPgtHL4Td9hJ5eY6v+EfCahkNpy8Jr1AP5pR70NICXWeS9FURuDdOLNO6AmrpQGBZVPSWQODDXq47CJO4bHMk55DXzPue31jva7Kk7l2ydmm2K6h3s9b+4pkYOtN3f/8tnQsbundqIXI2Uz6E8LP8ksGilUCQQDjZt8sMa9nkZ7RC5SqF8QecMKXErsf5iZroYHt+2HHaakoV+Exy+eXBgREPO7cZMC+BIglTciHFD5grCrdwQBLAkEAyNvgViJI0XUsiLlsg9RHA0DciErEkjzXMd6LcND1KspXFM6y484uN6B07SDeqG1Dyn/C+pQarpt5xER9UEU4NwJBAIiqe7fYyH0rJFKobhlnnSNaS2h2BmYecLrA3xCCwvoQw2wOnLXLwQyfvhKwuDFWkAvjN1uMCtc70F1TO5P4eU8CQGJof8ATqhOdUgVmu4jXPzeT1rib0TVIw7I2M6FBb2zYl9Ok9bZw9OniHodzfEOOzRDwianVWEFGAWGsoKzsTP8CQQDZrzW16AnCQrmr1ENCQoQonEZTAAS5FI1XhfIH4VvGq+yCyYMdB7WtQ0kUS3Qm20VfDhim55NjlZd2BPpPBmxL"</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> text = <span class="string">"Please encrypt this data"</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> encryptedString = Base64.getEncoder().encodeToString(RSAUtil.encrypt(text, publicKey))</span><br><span class="line"> println(<span class="string">"encryptedString: <span class="variable">$encryptedString</span>"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> decryptedString = RSAUtil.decrypt(encryptedString, privateKey)</span><br><span class="line"> println(<span class="string">"decryptedString: <span class="variable">$decryptedString</span>"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Encryption/">Encryption</category>
<category domain="https://jongmin92.github.io/tags/RSA/">RSA</category>
<comments>https://jongmin92.github.io/2020/01/02/Java/rsa/#disqus_thread</comments>
</item>
<item>
<title>2년차 LINE 서버 개발자의 2019년 회고</title>
<link>https://jongmin92.github.io/2019/12/26/Programming/2019-retrospect/</link>
<guid>https://jongmin92.github.io/2019/12/26/Programming/2019-retrospect/</guid>
<pubDate>Thu, 26 Dec 2019 13:23:00 GMT</pubDate>
<description><p>벌써 LINE에 입사한지 2년이 다 되어간다. 올해는 정말 시간이 어떻게 지나갔는지 모르겠다. 정신 차리고 보니 2020년이 코앞이다. 올해는 작년에 하지 못했던 회고를 해보려한다.</p></description>
<content:encoded><![CDATA[<p>벌써 LINE에 입사한지 2년이 다 되어간다. 올해는 정말 시간이 어떻게 지나갔는지 모르겠다. 정신 차리고 보니 2020년이 코앞이다. 올해는 작년에 하지 못했던 회고를 해보려한다.</p><span id="more"></span><h1 id="블로그-포스팅"><a href="#블로그-포스팅" class="headerlink" title="블로그 포스팅"></a>블로그 포스팅</h1><p>어떻게 회고를 시작할까 생각하다가 올해 포스팅한 글들을 확인해보았다. 올해는 26개의 글을 포스팅을 했다. 올해 작성한 글들을 통해서 어떤 일들이 있었고 무엇을 느끼고 배우게 되었는지 정리해보려 한다.<br><img src="/images/post/2019-12-26/2019-article.png" alt="2019-article"></p><h1 id="Non-blocking-Asynchronous-관련-기술-공부"><a href="#Non-blocking-Asynchronous-관련-기술-공부" class="headerlink" title="Non-blocking / Asynchronous 관련 기술 공부"></a>Non-blocking / Asynchronous 관련 기술 공부</h1><p>사내에서 팀원분들의 PR을 리뷰하면서 Non-blocking/Asynchronous에 관련된 내용을 많이 접하게 되었다. Webflux나 Armeria와 같은 프레임워크들을 처음 공부하면서 <strong>Reactive라는 용어와 함께 그 기반이 되는 Non-blocking/Asynchronous에 대한 내용을 접하게 되었고, Netty를 거쳐 Java NIO와 멀티플렉싱까지 관심을 갖고 공부하게된 계기가 되었다.</strong></p><p>개인적으로 무언가 새로운 기술을 접하고 사용하게 될 때, 그 기술이 만들어지게 된 배경과 기반이 되는 기술 또한 공부하는 것을 매우 중요하게 생각한다.<br>실제로는 하나의 흐름으로 이어질 수 있는 내용들인데 매번 새로운 기술이 등장할때면 그 기술 자체에 집중하느라 흐름을 따라 파악하기가 쉽지 않은 것 같다. 또한 이러한 부분을 정리하는 것도 갈수록 어려워지는것 같다.</p><p>결과적으로 이와 관련된 내용은 후에 팀내에서 Webflux 관련 스터디를 진행하면서도 개인적으로 큰 도움이 되었다.</p><h1 id="오픈소스-첫-컨트리뷰트"><a href="#오픈소스-첫-컨트리뷰트" class="headerlink" title="오픈소스 첫 컨트리뷰트"></a>오픈소스 첫 컨트리뷰트</h1><p><strong>올해는 처음으로 오픈소스에 컨트리뷰트를 해보았다.</strong> <a href="https://github.com/line/armeria">Armeria</a>는 Java 개발자라면 누구나 한번 쯤 들어보았을 <a href="https://github.com/netty/netty">Netty</a>를 만드신 희승님께서 개발하고 계신 오픈소스이다. 어느날 사내에서 Armeria Sprint라는 행사를 진행하게 되었고 이 행사에 참여해 처음으로 오픈소스에 기여하는 경험을 하게 되었다. (자세한 내용이 궁금하다면 <a href="https://jongmin92.github.io/2019/05/18/Etc/open-source-experience/"><오픈소스 컷 컨트리뷰트 경험기></a>를 보면 좋을 것 같다!)</p><p>평소 오픈소스에 컨트리뷰트를 해보고 싶었지만 어떻게 해야하는 것인지, 내가 할 수 있을까에 대한 막연한 두려움이 있었다면, 이번 첫 컨트리뷰트를 통해 이러한 부분들을 많이 해소할 수 있었던 것 같다.</p><p>첫 컨트리뷰트 이후로도 꾸준히 해보겠다고 다짐했지만 실행에 옮기지는 못했다. 내년에는 꾸준히 하지는 못하더라도 오픈소스에 관심 갖고 기여하는 시간을 조금 더 가져보려고 한다.</p><p><img src="/images/post/2019-12-26/armeria.png" alt="armeria"></p><h1 id="공부-방식의-변화"><a href="#공부-방식의-변화" class="headerlink" title="공부 방식의 변화"></a>공부 방식의 변화</h1><p>작년에는 32개의 글을 포스팅을 했는데, 올해 작성했던 글들은 작년에 작성했던 글들과 조금 차이가 있다.</p><p>작년에 작성했던 글들은 대부분이 회사에 입사해 처음으로 접하는 기술들을 공부하며 관련된 내용을 정리하는 내용의 글들이었다. 말로만 듣다가 처음 접하게 된 Spring과 학부생 수업때 이후로 사용하지 않던 Java 뿐만 아니라 Gradle, Jenkins, SonarQube와 같은 것들에 대해서 전반적으로 다루었다.</p><p>각각에 대해서 깊은 내용을 다루기 보다는 당장 회사에서 사용하는 기술들에 대해 파악하고 업무를 진행할 수 있는 정도로 공부하고 정리하며 글을 작성했던것 같다.</p><p>이에 반해, 올해 작성했던 글들은 회사에서 업무를 진행하며 겪었던 문제에 대한 내용 혹은 PR review에서 받았던 코멘트들에 대한 내용을 위주로 작성했다. 앞으로도 업무를 하면서 새로 알게된 내용들 위주로 작성하게 될 것 같다.</p><p>또한, 한가지 더 변한 것이 있다면 작년에는 기술 서적을 최대한 많이 읽기 위해 노력했지만 <strong>올해는 조금이라도 직접 코드를 작성하는 시간을 더 많이 갖기위해 노력하고 있다.</strong> 확실히 책을 여러번 아무리 많이 보아도 직접 코딩하는게 학습에는 제일 효과가 좋은 것 같다.</p><h1 id="실무에서-배운-것"><a href="#실무에서-배운-것" class="headerlink" title="실무에서 배운 것"></a>실무에서 배운 것</h1><p>근무하고 있는 LINE의 개발 문화에 대해서는 동기가 잘 작성한 글로 대신한다. <a href="https://engineering.linecorp.com/ko/blog/new-server-developer-at-line/">(2년차 서버 개발자가 바라본 LINE의 개발 문화)</a></p><p>올해는 기존의 feature 하나를 새롭게 리뉴얼하면서(설계부터 릴리즈까지) 정말 너무나도 많은 것들을 배웠다. 아주 간략히 정리하면 아래와 같다.</p><ul><li>기존 legacy 코드의 문제점을 파악하고 개선</li><li>다른 부서(기획팀, 클라이언트 개발팀, 서버팀)와의 협업</li><li>이미 서비스 되고 있는 client에 대해서는 어떻게 다룰 것인지<ul><li>backward compatibility (하위 호환성) 지원</li></ul></li><li>릴리즈 후 모니터링</li></ul><p>개발 후 릴리즈까지가 끝이라고 생각했지만 모니터링 또한 매우 중요했다. feature가 문제없이 잘 동작하고 있는지, 그리고 장애 상황을 미리 감지할 수 있도록 의미있는 metric들을 잘 정의하고 대시보드로 만드는 것까지 함으로써 feature에 대한 개발을 마무리 할 수 있었다.</p><p>릴리즈 후 모니터링을 하면서 몇일간 기존의 API에서 리뉴얼 된 API로 사용량이 증가하면서 이로 인해 변경된 지표들을 확인할 수 있었고, API 디자인의 변경으로 인해 애플리케이션 지표에 미치는 영향들 또한 확인할 수 있었다. 어디가서 쉽게 해볼 수 없는 값진 경험이었다.</p><p>그러나 많은 준비를 했음에도 불구하고 릴리즈 후 예상하지 못한 부분에서 장애가 발생했었고 <a href="https://engineering.linecorp.com/ko/blog/line-failure-reporting-and-follow-up-process-culture/">LINE의 장애 보고와 후속 절차 문화</a>에 따라 어떻게 하면 앞으로 같은 장애를 겪지 않을 수 있을지에 대해서도 생각해보게 되었다.</p><p>약 6개월간 진행했던 feature 리뉴얼을 진행하며 배운것을 아주 간략히 정리해보았다. 지나고나니 아쉬웠던 부분들도 생각이 난다. 다음번에는 이런 부분들도 잘 고려해서 문제없이 마무리할 수 있도록 노력하자.</p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/tags/Retrospect/">Retrospect</category>
<comments>https://jongmin92.github.io/2019/12/26/Programming/2019-retrospect/#disqus_thread</comments>
</item>
<item>
<title>HMAC을 이용한 무결성 보장</title>
<link>https://jongmin92.github.io/2019/12/23/Programming/hmac/</link>
<guid>https://jongmin92.github.io/2019/12/23/Programming/hmac/</guid>
<pubDate>Mon, 23 Dec 2019 14:37:00 GMT</pubDate>
<description><p>지난번에 <a href="https://jongmin92.github.io/2019/12/18/Java/hash/">Hash</a>에 대한 내용을 살펴보았다. 이번에는 Hash의 개념을 이용한 HMAC에 대해서 알아보자.</p></description>
<content:encoded><![CDATA[<p>지난번에 <a href="https://jongmin92.github.io/2019/12/18/Java/hash/">Hash</a>에 대한 내용을 살펴보았다. 이번에는 Hash의 개념을 이용한 HMAC에 대해서 알아보자.</p><span id="more"></span><h1 id="HMAC-Hash-based-Message-Authentication-Code"><a href="#HMAC-Hash-based-Message-Authentication-Code" class="headerlink" title="HMAC(Hash-based Message Authentication Code)"></a>HMAC(Hash-based Message Authentication Code)</h1><p><strong><code>메시지 인증 코드(Message Authentication Code, MAC)</code>는 메시지의 인증에 쓰이는 정보(코드)이다. 메시지의 무결성 및 신뢰성을 보장하는 용도로 MAC을 사용한다.</strong></p><blockquote><p>무결성이란, 서버 입장에서 클라이언트로부터 API 요청을 받았을 때, 이 요청이 신뢰할 수 있는 것인지 (정보가 중간에 변경없이 그대로 전달된 것인지)에 대한 성질을 말한다.</p></blockquote><p>HMAC은 인증을 위한 <code>Secret Key</code>와 임의의 길이를 가진 <code>Message</code>를 해시 함수(알고리즘)을 사용해서 생성한다. 해시 함수로는 MD5, SHA-256과 같은 일반적인 해시 함수를 그대로 사용할 수 있으며 각 알고리즘에 따라 다른 고정길이의 MAC(Hash value)가 생성된다.</p><ul><li><strong>Secret Key</strong>: 서버와 클라이언트가 함께 알고 있는 외부로 유출되어서는 안되는 값.</li><li><strong>Message</strong>: 클라이언트가 전송하는 요청의 전체(Header + Body)가 될 수도 있고, URL만 될 수도 있다.</li></ul><blockquote><p>만약 Message를 변경하지 않고, 중간에 Message를 취득한 공격자가 변조 없이 동일한 요청을 계속 보낸다면, Message를 변조하지 않았기 때문에 서버는 이를 유효한 요청으로 인식할 것이다. 이것을 <strong>Replay Attack</strong>이라고 하는데 이를 방지하기 위해서 MAC을 생성할 때 <strong>timestamp</strong>를 추가해서 사용하는 방법이 있다.<br>이렇게 하면 서버는 이 Message가 생성된 시간을 알 수 있고, 생성된 시간부터 일정 시간내의 호출만 정상적인 호출로 사용하면 된다.</p></blockquote><p>전체 과정은 아래와 같다.</p><p><img src="/images/post/2019-12-23/hmac.png" alt="hmac"></p><ol><li><strong>해시 생성</strong>: 클라이언트는 Key와 Message를 이용해 해시함수로부터 MAC을 생성한다.</li><li><strong>데이터 전송</strong>: 생성된 MAC과 Message를 서버에게 전송한다. MAC은 HTTP Request Header 혹은 URL에 포함된다.</li><li><strong>해시 생성</strong>: 서버는 클라이언트로부터 전달받은 Message와 갖고 있던 Key를 이용해 해시함수로부터 MAC을 생성한다.</li><li><strong>해시 비교</strong>: 서버에서 생성된 MAC과 클라이언트로부터 전달받은 MAC의 값이 같은지 비교한다.</li></ol><p>사용되는 곳을 보자면, <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html">AWS</a>에서는 Authorization(HTTP Request Header)에 HMAC-SHA256으로 만들어진 값을 포함해 인증한다.</p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/tags/SHA256/">SHA256</category>
<category domain="https://jongmin92.github.io/tags/HMAC/">HMAC</category>
<comments>https://jongmin92.github.io/2019/12/23/Programming/hmac/#disqus_thread</comments>
</item>
<item>
<title>Hash - MD5와 SHA256</title>
<link>https://jongmin92.github.io/2019/12/18/Java/hash/</link>
<guid>https://jongmin92.github.io/2019/12/18/Java/hash/</guid>
<pubDate>Tue, 17 Dec 2019 15:23:00 GMT</pubDate>
<description><h1 id="해시-Hash-와-암호화-Encryption-의-차이"><a href="#해시-Hash-와-암호화-Encryption-의-차이" class="headerlink" title="해시(Hash)와 암호화(Encryption)의 차이"></a>해시(Hash)와 암호화(Encryption)의 차이</h1><p>먼저 혼동하기 쉬운 <code>해시</code>와 <code>암호화</code>의 차이에 대해서 알아보자.</p></description>
<content:encoded><![CDATA[<h1 id="해시-Hash-와-암호화-Encryption-의-차이"><a href="#해시-Hash-와-암호화-Encryption-의-차이" class="headerlink" title="해시(Hash)와 암호화(Encryption)의 차이"></a>해시(Hash)와 암호화(Encryption)의 차이</h1><p>먼저 혼동하기 쉬운 <code>해시</code>와 <code>암호화</code>의 차이에 대해서 알아보자.</p><span id="more"></span><h2 id="해시"><a href="#해시" class="headerlink" title="해시"></a>해시</h2><p><img src="/images/post/2019-12-18/hash.png" alt="hash"><br><strong>해시는 가변 길이의 데이터를 해시 함수를 사용해 고정 길이의 해시 값을 만들어 내는 방법이다.</strong> 만약 해시 값이 다르면 그 해시 값을 만들기 위해 사용했던 원래의 데이터도 달라야한다. 또한 해시 값에서 원래의 데이터를 구하는 것은 매우 어렵다.</p><h2 id="암호화"><a href="#암호화" class="headerlink" title="암호화"></a>암호화</h2><p><img src="/images/post/2019-12-18/encryption.png" alt="encryption"><br><strong>암호화는 복호화(Decryption)을 할 수 있는 Key를 소유하고 있는 사람은 제외하고는 원래의 데이터를 읽어볼 수 없도록 암호화 알고리즘을 이용해 데이터를 전달하는 것이다.</strong> 암호화된 정보는 복호화하여 다시 원래의 데이터를 구할 수 있다.</p><p><strong>즉, 암호화를 통해 생성된 값은 복호화를 통해 원래의 데이터를 알 수 있지만, 해시를 통해 만들어진 고정된 길이의 해시 값의 경우는 원래의 데이터를 알 수 없다.</strong></p><h1 id="해시-1"><a href="#해시-1" class="headerlink" title="해시"></a>해시</h1><p>이제 해시에 대해서 조금 더 자세히 알아보자. <strong>해시 함수는 가변 길이의 데이터를 이용해 고정 길이의 해시 값을 생성하는데, 같은 입력 값에 대해서는 같은 출력 값을 보장한다.</strong></p><p>해시는 보안 분야에서도 널리 사용된다. 해시 함수를 통해 생성된 해시 값을 이용해서 원래의 데이터를 알 수 없도록 만든다는 특성을 갖고 있기 때문이다. 해시 함수의 결과물은 고정된 길이의 숫자이므로, 원래의 정보는 손실된다. 또한 이런 특성 때문에 하나의 원본 데이터는 하나의 해시 값만 가지지만, 하나의 해시 값을 만들어낼 수 있는 원본 데이터는 매우 많다. 그렇기 때문에 <strong>해시 값만 가지고는 원본 데이터를 복원해내는 것은 불가능하다. 따라서 비밀번호, 전자서명, 전자투표, 전자상거래와 같은 민감한 입력의 무결성을 검증할 때 사용된다.</strong></p><p>비밀번호: 보통 유저의 비밀번호를 암호화해서 저장한다고 한다. 그러나 암호화의 경우는 복호화를 통해 실제 비밀번호를 알아낼 수 있으므로 암호화가 아닌 해시 함수를 이용해 생성된 해시 값을 저장해야한다.</p><h2 id="MD5"><a href="#MD5" class="headerlink" title="MD5"></a>MD5</h2><p><strong><code>MD5(Message-Digest algorithm 5)</code>는 128비트의 해시 값을 생성하는 해시 함수이다.</strong> 주로 프로그램이나 파일이 원본 그대로인지를 확인하는 무결성 검사 등에 사용된다. 이전에 쓰이던 MD4를 대체하기 위해 만들어졌다.</p><p>그러나 현재는 아래와 같은 결함이 발견되었기 때문에 사용하는 것을 권장하지 않는다.</p><blockquote><p>1996년에 설계상 결함이 발견되었다. 이것은 매우 치명적인 결함은 아니었지만, 암호학자들은 해시 용도로 SHA-1과 같이 다른 안전한 알고리즘을 사용할 것을 권장하기 시작했다. 2004년에는 더욱 심한 암호화 결함이 발견되었고. 2006년에는 노트북 컴퓨터 한 대의 계산 능력으로 1분 내에 해시 충돌을 찾을 정도로 빠른 알고리즘이 발표되기도 하였다. 현재는 MD5 알고리즘을 보안 관련 용도로 쓰는 것은 권장하지 않으며, 심각한 보안 문제를 야기할 수도 있다.</p></blockquote><p>kotlin으로 작성한 코드를 통해 간단히 확인해보자. java에서 제공하는 <code>MessageDigest</code>를 사용하면 쉽게 MD5 해시 함수를 통한 해시 값을 만들 수 있다.</p><p>아래 코드에서는 MD5 해시 함수를 통해 해시 값(128비트)을 생성하고 Hex string으로 변환하였다.</p><p><a href="https://www.md5hashgenerator.com/">Online MD5 Hash Generator</a>를 이용해서도 테스트할 수 있다.</p><figure class="highlight kotlin"><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="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> planText = <span class="string">"password1!2@3#"</span></span><br><span class="line"> <span class="keyword">val</span> md = MessageDigest.getInstance(<span class="string">"MD5"</span>)</span><br><span class="line"> md.update(planText.toByteArray())</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> digest = DatatypeConverter.printHexBinary(md.digest())</span><br><span class="line"> println(<span class="string">"planText: <span class="subst">${planText}</span>"</span>)</span><br><span class="line"> println(<span class="string">"MD5 encoding result: <span class="subst">${digest}</span>"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">----------실행 결과----------</span><br><span class="line">planText: password1!<span class="number">2</span>@<span class="number">3</span>#</span><br><span class="line">MD5 encoding result: E9C4736A0963EB81C0C85AF48CF2F3F2</span><br></pre></td></tr></table></figure><h2 id="SHA-256"><a href="#SHA-256" class="headerlink" title="SHA-256"></a>SHA-256</h2><p><strong><code>SHA-256</code>은 SHA(Secure Hash Algorithm) 알고리즘의 한 종류로서 256비트의 해시 값을 생성하는 해시 함수이다.</strong> SHA-256은 미국의 국립표준기술연구소(NIST; National Institute of Standards and Technology)에 의해 공표된 표준 해시 알고리즘인 SHA-2 계열 중 하나이며 블록체인에서 가장 많이 채택하여 사용하고 있다.</p><p>개인용 컴퓨터로 무차별 대입을 수행해 해시 충돌 사례를 찾으려고 할 때 많은 시간이 소요될 정도로 큰 숫자이므로 충돌로부터 비교적 안전하다고 평가된다.</p><p>아래 코드에서는 SHA-256 해시 함수를 통해 해시 값(256비트)을 생성하고 Hex string으로 변환하였다.</p><p><a href="https://www.cleancss.com/sha256-hash-generator">Online SHA-256 Hash Generator</a>를 이용해서도 테스트할 수 있다.</p><figure class="highlight kotlin"><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="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> planText = <span class="string">"password1!2@3#"</span></span><br><span class="line"> <span class="keyword">val</span> md = MessageDigest.getInstance(<span class="string">"SHA-256"</span>)</span><br><span class="line"> md.update(planText.toByteArray())</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> digest = DatatypeConverter.printHexBinary(md.digest())</span><br><span class="line"> println(<span class="string">"planText: <span class="subst">${planText}</span>"</span>)</span><br><span class="line"> println(<span class="string">"SHA-256 encoding result: <span class="subst">${digest}</span>"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">----------실행 결과----------</span><br><span class="line">planText: password1!<span class="number">2</span>@<span class="number">3</span>#</span><br><span class="line">SHA-<span class="number">256</span> encoding result: 0979334AF544553966CB7AF741869C971C716A6879C562FDFE781B50F2F5862E</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Hash/">Hash</category>
<category domain="https://jongmin92.github.io/tags/Encryption/">Encryption</category>
<category domain="https://jongmin92.github.io/tags/Message-Digest/">Message Digest</category>
<category domain="https://jongmin92.github.io/tags/MD5/">MD5</category>
<category domain="https://jongmin92.github.io/tags/SHA256/">SHA256</category>
<comments>https://jongmin92.github.io/2019/12/18/Java/hash/#disqus_thread</comments>
</item>
<item>
<title>Gradle에서 Dependency Pollution 문제 해결하기</title>
<link>https://jongmin92.github.io/2019/12/07/Gradle/gradle-implementation/</link>
<guid>https://jongmin92.github.io/2019/12/07/Gradle/gradle-implementation/</guid>
<pubDate>Sat, 07 Dec 2019 04:56:00 GMT</pubDate>
<description><p>지난번에 <a href="https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/">(Gradle dependency) api와 implementation 차이</a>에 대해서 알아보았다.</p></description>
<content:encoded><![CDATA[<p>지난번에 <a href="https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/">(Gradle dependency) api와 implementation 차이</a>에 대해서 알아보았다.</p><span id="more"></span><p>이번에는 api와 implementation을 이용해 Dependency Pollution(의존성 오염) 문제와 해결하는 방법에 대해서 알아보자.</p><h1 id="Dependency-Pollution-이란"><a href="#Dependency-Pollution-이란" class="headerlink" title="Dependency Pollution 이란"></a>Dependency Pollution 이란</h1><p>아래 그림과 같이 프로젝트 X가 있다. 프로젝트 X는 라이브러리 A와 B에 의존하고 있다. 또 다른 프로젝트 C는 프로젝트 X에 의존하고 있다.<br><img src="/images/post/2019-12-07/diagram_1.png" alt="diagram_1"></p><p>이때, C는 X에 대한 의존성 갖게 되면서 A와 B에 대한 의존성 역시 갖게 된다. 이러한 상황을 <strong>C가 A와 B에 대해 <code>의존성 전이(Transitive Dependency)</code>를 갖는다고 한다.</strong></p><p>이러한 의존성을 컴파일 타임에 사용할 수 있다고 생각해보자. X는 A와 B의 클래스들을 사용할 수 있을 것이고, C는 X뿐만 아니라 A와 B의 클래스 역시 사용할 수 있을 것이다.</p><p>이렇게 <strong>X의 의존성(A, B)이 C의 컴파일 타임시에 classpath에 노출되는 것을 <code>Dependency Pollution</code>이라고 한다.</strong></p><h1 id="Dependency-Pollution-문제"><a href="#Dependency-Pollution-문제" class="headerlink" title="Dependency Pollution 문제"></a>Dependency Pollution 문제</h1><p>Transitive Dependency로 인한 Dependency Pollution 문제를 갖고 있는 C에 어떤 영향이 있는지 알아보자.</p><p>C를 개발하고 있는 개발자가 라이브러리 A의 클래스를 사용하기로 결정할 수 있다. 하지만 개발자는 라이브러리 A에 대한 의존성이 X를 통해 추가되었음을 인지하지 못하고 사용할 수 있다.</p><p>X 개발자는 더 이상 라이브러리 A가 필요하지 않다고 결정하고 의존성에서 제거할 수 있다. 이 변경사항에 대해 X 개발자는 프로젝트 X가 제공하는 API에 대해서는 변경하지 않았기 때문에 minor한 업데이트로 간주할 것이다.</p><p>이후에 C 개발자가 X에 대한 버전을 업데이트(A가 제거된 버전)하는 경우에 C에서는 더 이상 라이브러리 A의 클래스를 사용할 수 없기 때문에 컴파일 에러가 발생한다.</p><p><strong>결국, X의 컴파일 타임 의존성을 C의 컴파일 타임까지 전파하는 경우에 C 개발자는 원하지 않는 컴파일 타임 의존성을 갖게 될 수 있다.</strong></p><h1 id="Gradle의-해결-방법"><a href="#Gradle의-해결-방법" class="headerlink" title="Gradle의 해결 방법"></a>Gradle의 해결 방법</h1><p>Gradle을 사용하는 경우 종속성을 제어해 Dependency Pollution 문제를 줄일 수 있다.</p><p>방법은 간단한다. <strong><code>api</code>대신 <code>implementation</code>을 사용하는 것이다.</strong><br><img src="/images/post/2019-12-07/diagram_2.png" alt="diagram_2"></p><p>X에서 A에 대한 종속성을 implementation을 이용해 선언하면 C는 더 이상 A에 대한 Transitive 컴파일 타임 종속성을 갖지 않게 된다. 따라서 C에서 실수로 A의 클래스를 사용할 수 없게 되는 것이다. 만약 C가 A의 클래스를 사용해야하는 경우에는 A에 대한 종속성을 명시적으로 선언해야한다.</p><p>반대로 특정 종속성을 컴파일 타임 종속성에 노출하고자하는 경우에는 implementation가 아닌 api를 사용하면 된다.</p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Gradle/">Gradle</category>
<category domain="https://jongmin92.github.io/tags/Gradle/">Gradle</category>
<comments>https://jongmin92.github.io/2019/12/07/Gradle/gradle-implementation/#disqus_thread</comments>
</item>
<item>
<title>NoSuchMethodException & NoSuchMethodError 해결하기</title>
<link>https://jongmin92.github.io/2019/12/05/Java/nosuchmethod/</link>
<guid>https://jongmin92.github.io/2019/12/05/Java/nosuchmethod/</guid>
<pubDate>Thu, 05 Dec 2019 13:43:00 GMT</pubDate>
<description><p>애플리케이션에서 사용하는 라이브러리의 버전을 업데이트하거나 혹은 새로운 라이브러리를 추가하는 과정에서 종종 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/NoSuchMethodError.html">NoSuchMethodError</a>을 마주하게 된다.<br>이번에는 NoSuchMethodErrors 대한 내용과 해결하는 방법에 대해서 알아보자.</p></description>
<content:encoded><![CDATA[<p>애플리케이션에서 사용하는 라이브러리의 버전을 업데이트하거나 혹은 새로운 라이브러리를 추가하는 과정에서 종종 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/NoSuchMethodError.html">NoSuchMethodError</a>을 마주하게 된다.<br>이번에는 NoSuchMethodErrors 대한 내용과 해결하는 방법에 대해서 알아보자.</p><span id="more"></span><h1 id="NoSuchMethodErrors"><a href="#NoSuchMethodErrors" class="headerlink" title="NoSuchMethodErrors"></a>NoSuchMethodErrors</h1><p><strong>NoSuchMethodErrors는 런타임(runtime) 시점에 존재하지 않는 메서드(method)를 호출하는 경우에 발생한다.</strong><br>일반적으로 존재하지 않는 메서드를 사용하려는 경우 컴파일(compile) 시점에 컴파일러가 에러를 발생시킨다. 그런데 런타임 시점에 에러가 발생했다는 말은 즉, <strong>컴파일때는 해당 메서드가 존재했지만 런타임 시점에 찾을 수 없었다는 것을 의미한다.</strong></p><h1 id="원인"><a href="#원인" class="headerlink" title="원인"></a>원인</h1><p>NoSuchMethodErrors가 발생하는 일반적인 상황에 대해서 좀 더 알아보자.</p><h2 id="라이브러리-Breaking-Change"><a href="#라이브러리-Breaking-Change" class="headerlink" title="라이브러리 Breaking Change"></a>라이브러리 Breaking Change</h2><p>NoSuchMethodErrors가 발생하는 원인 중 하나는 애플리케이션에서 사용하는 라이브러리 중 하나가 한 버전에서 다음 버전으로 변경될 때, Breaking Change를 포함하는 경우이다.<br><strong><code>Breaking Change</code>란 라이브러리가 제공하던 API에 변경 사항이 있음을 의미한다. API가 사라진다던가 return type 혹은 parameters가 변경되는 경우들이 있다.</strong></p><p>그러나, 위에서도 이야기한 것처럼 일반적으로 존재하지 않는 메서드를 사용하려는 경우 컴파일 시점에 컴파일러가 에러를 발생시키는데 어떻게 런타임 시점에 에러가 발생할 수 있는 것일까?</p><p>최근에는 SpringBoot로 애플리케이션을 개발하고 Tomcat 자체를 포함해 fatjar(uberjar)로 패키징(packaging) 후 배포해서 jar 파일을 실행하는 것으로 애플리케이션을 실행하는 것이 자연스럽게 여겨진다.</p><p>Embeded Tomcat을 사용하지 않았던 때를 생각해보자. Spring 애플리케이션을 개발하고 war로 패키징한 후, Tomcat이 설치된 폴더 아래의 webapps에 war를 배포 후 Tomcat을 재기동 하는 것으로 애플리케이션을 실행했었다.</p><p>이것이 의미하는 것이 무엇일까? Servlet Container인 Tomcat과 함게 애플리케이션이 실행되고 있는 런타임 시점에 HTTP 요청이 들어오면 Tomcat은 해당 요청에 대한 HttpServletRequest와 HttpServletResponse를 만들고 우리가 작성한 Spring 애플리케이션 코드를 실행한다.<br>우리는 애플리케이션 코드에서 Tomcat으로부터 전달받은 HttpServletRequest와 HttpServletResponse를 사용하는데, <strong>개발하는 시점에 Spring 애플리케이션에서는 HttpServletRequest와 HttpServletResponse를 어떻게 사용할 수 있는 것일까?</strong></p><p>Gradle을 사용한다면 build.gradle을, Maven을 사용하고 있다면 pom.xml을 확인해보자. 다음과 같이 HttpServletRequest와 HttpServletResponse를 포함하고 있는 <code>javax.servlet-api</code> 라이브러리를 포함하고 있을 것이다.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// ex) build.gradle</span><br><span class="line">compileOnly("javax.servlet:javax.servlet-api:3.0.1")</span><br></pre></td></tr></table></figure><p>다만 <a href="https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/">implementation 혹은 api</a>가 아닌 <a href="https://blog.gradle.org/introducing-compile-only-dependencies">compileOnly</a>를 사용해서 의존성을 선언하고 있다. 이것이 의미하는 것은 <strong>애플리케이션을 작성하고 컴파일 시점까지만<br>3.0.1 버전의 javax.servlet-api를 사용하겠다는 것이다. 그 후 컴파일이 완료되고 Tomcat에 의해서 애플리케이션이 실행되는 런타임 시점에는 Tomcat에 의해 classpath에 포함된 javax.servlet-api를 사용하게 된다.</strong></p><p>Tomcat은 버전에 따라 사용하는 Servlet 버전이 다르다. 따라서 Spring 애플리케이션에서 compileOnly 의존성으로 사용하고 있는 Servlet 라이브러리의 버전과 실제 애플리케이션이 구동되는 Tomcat에서 사용하는 버전에 차이가 있다면 생각과는 다르게 동작할 수 있는 가능성이 있는 것이다.</p><blockquote><p><a href="http://tomcat.apache.org/whichversion.html">Apache Tomcat Versions</a><br><img src="/images/post/2019-12-05/tomcat_version.png" alt="tomcat_version"></p></blockquote><p>만약 Spring 애플리케이션의 Servlet 버전을 3.0 버전에 맞춰 개발하고 있었는데 애플리케이션이 실행되는 Tomcat이 6.0.x(Servlet 버전 2.5) 혹은 8.0.x(Servlet 버전 3.1)이라면 의도치 않은 Breaking Change를 겪게될 수 있는 것이다.</p><h2 id="라이브러리-버전-Overriding"><a href="#라이브러리-버전-Overriding" class="headerlink" title="라이브러리 버전 Overriding"></a>라이브러리 버전 Overriding</h2><p>다른 원인으로는 라이브러리 버전 Overriding 문제가 있다. 다음을 가정해보자.</p><p>애플리케이션에서 라이브러리 A를 사용하고 있다. 라이브러리 A는 다른 라이브러리 B에 대해 의존성(dependency)를 갖고 있다. 애플리케이션에서 라이브러리 B를 직접 호출하지는 않지만 라이브러리 B를 사용하기 때문에 A까지 포함되게 되는데 이때, 라이브러리 B는 해당 애플리케이션에 대해 <code>전이 의존성(transitive dependency)</code>이라고 부른다.</p><p>이 경우, 만약 라이브러리 B를 이용하는 또 다른 라이브러리 C가 있고 이를 애플리케이션에서 사용하고 있다면 <strong>build system에 의해 버전 충돌로 인한 문제가 발생할 수 있다.</strong></p><ul><li>애플리케이션 <- 라이브러리 A <- 라이브러리 B (버전 1.x)</li><li>애플리케이션 <- 라이브러리 C <- 라이브러리 B (버전 2.x)</li></ul><p>Gradle과 Maven 같은 빌드 시스템은 이와 같은 버전 충돌 문제가 발생했을 때, 두 버전 중 하나를 선택하게 된다.</p><p>위의 예시에서 라이브러리 B의 2.x 버전을 선택하게 되었을 때, 라이브러리 C에서는 문제가 발생하지 않지만 라이브러리 A에서는 문제가 발생할 수 있다. (라이브러리 B가 버전 1.x와 2.x 사이에 Breaking Change가 존재할 수 있기 때문이다.)</p><h1 id="해결책"><a href="#해결책" class="headerlink" title="해결책"></a>해결책</h1><p>크게 3단계로 해결할 수 있다.</p><h2 id="문제의-Class-찾기"><a href="#문제의-Class-찾기" class="headerlink" title="문제의 Class 찾기"></a>문제의 Class 찾기</h2><p><strong>먼저 문제의 메서드를 포함하는 클래스를 찾아야한다. NoSuchMethodError 로그로부터 쉽게 찾을 수 있다.</strong></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">Exception in thread <span class="string">"main"</span> java.lang.NoSuchMethodError: </span><br><span class="line"> com.jongmin.example.Service.hello(Ljava/lang/String;)Ljava/lang/String;</span><br></pre></td></tr></table></figure><p>클래스를 찾았다면 검색 혹은 사용하고 있는 IDE를 통해서 해당 클래스가 포함된 JAR(라이브러리)를 찾을 수 있다.</p><h2 id="Class-호출하는-곳-찾기"><a href="#Class-호출하는-곳-찾기" class="headerlink" title="Class 호출하는 곳 찾기"></a>Class 호출하는 곳 찾기</h2><p><strong>다음으로 메서드를 호출하는 위치를 찾아야 한다. 이 정보는 에러 로그의 stack trace를 이용해 찾을 수 있다.</strong></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">Exception in thread <span class="string">"main"</span> java.lang.NoSuchMethodError: </span><br><span class="line"> com.jongmin.example.Service.hello(Ljava/lang/String;)Ljava/lang/String;</span><br><span class="line"> at com.jongmin.example.NoSuchMethod.main(ProvokeNoSuchMethodError.java:<span class="number">7</span>)</span><br></pre></td></tr></table></figure><p>위의 로그에서는 NoSuchMethod 클래스가 런타임에 존재하지 않는 hello 메서드를 호출하려고 하는 것을 확인할 수 있다. 이제 해당 클래스가 속한 라이브러리를 찾아야한다.</p><h2 id="버전-확인"><a href="#버전-확인" class="headerlink" title="버전 확인"></a>버전 확인</h2><p>이제 NoSuchMethodError가 발생하는 위치와 런타임 시점에 존재하지 않는 메서드를 알았으므로 해결할 수 있다.</p><p><strong>현재 프로젝트의 모든 dependency를 출력해보자.</strong></p><p>Gradle을 사용하고 있다면 다음의 명령어를 사용한다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./gradlew dependencies > dependencies.txt</span><br></pre></td></tr></table></figure><p>Maven을 사용한다면 다음의 명령어를 사용한다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mvn dependency:list > dependencies.txt</span><br></pre></td></tr></table></figure><p>dependencies.txt 파일을 확인하면 런타임 시점에 존재하지 않는 메서드와 이를 호출하는 클래스가 포함 된 라이브러리를 찾을 수 있다.</p><p>예를 들면, 다음과 같은 출력 결과를 확인할 수 있다.</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">\--- org.springframework.data:spring-data-commons:1.8.2.RELEASE</span><br><span class="line">| \--- org.springframework:spring-core:3.2.10.RELEASE -> 4.3.20.RELEASE (*)</span><br></pre></td></tr></table></figure><p>위의 결과를 보면 spring-data-commons 라이브러리가 3.2.10 버전의 spring-core에 의존하지만 다른 라이브러리도 버전 4.3.20의 spring-core에 의존하고 있어 3.2.10 버전이 4.3.2 버전으로 Overriding된 것이다.</p><p>그럼 이제 어떤 라이브러리가 spring-core 4.3.2 버전 라이브러리에 의존하고 있는지 찾을 수 있다.</p><p>마지막으로 spring-core 라이브러리의 두 버전 중 어떤 버전이 spring-core를 필요로하는 두 라이브러리의 종속성을 만족시키는지 확인하고 결정해야 한다. 일반적이라면 안정적인 라이브러리라면 업데이트 되더라도 하위 호환성(backward compatibility)를 유지하기 때문에 최신 버전을 사용하면 될 것이다.</p><h1 id="NoSuchMethodException"><a href="#NoSuchMethodException" class="headerlink" title="NoSuchMethodException"></a>NoSuchMethodException</h1><p>NoSuchMethodException은 NoSuchMethodError와 관련이 있지만 다른 context에서 발생한다. <strong><code>NoSuchMethodError</code>는 JAR 파일이 컴파일 시점과 런타임 시점에 다른 버전을 갖는 경우 발생한다. 반면 <code>NoSuchMethodException</code>은 reflection을 이용해 존재하지 않는 메서드를 사용하려고하면 발생한다.</strong></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">String.class.getMethod(<span class="string">"notExist"</span>);</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<comments>https://jongmin92.github.io/2019/12/05/Java/nosuchmethod/#disqus_thread</comments>
</item>
<item>
<title>SpringBoot Application의 monitoring 시스템 구축하기</title>
<link>https://jongmin92.github.io/2019/12/04/Spring/prometheus/</link>
<guid>https://jongmin92.github.io/2019/12/04/Spring/prometheus/</guid>
<pubDate>Tue, 03 Dec 2019 17:20:00 GMT</pubDate>
<description><p><img src="/images/post/2019-12-04/system.png" alt="system"><br>Spring Boot를 사용하고 있는 애플리케이션에서 <a href="https://jongmin92.github.io/2019/12/03/Spring/micrometer/">이전에 살펴본 Micrometer</a>를 이용해서 metric을 생성하고 Prometheus를 이용해 수집, 그리고 Grafana로 시각화하는 시스템을 만들어보자.</p></description>
<content:encoded><![CDATA[<p><img src="/images/post/2019-12-04/system.png" alt="system"><br>Spring Boot를 사용하고 있는 애플리케이션에서 <a href="https://jongmin92.github.io/2019/12/03/Spring/micrometer/">이전에 살펴본 Micrometer</a>를 이용해서 metric을 생성하고 Prometheus를 이용해 수집, 그리고 Grafana로 시각화하는 시스템을 만들어보자.</p><span id="more"></span><blockquote><p><a href="https://prometheus.io/">Prometheus</a>는 metric을 수집하고 모니터링 및 알람에 사용되는 오픈소스 애플리케이션이다. time series database를 사용해 metric을 저장하고 flexible한 query를 사용하 metric을 조회할 수 있다.</p></blockquote><blockquote><p><a href="https://grafana.com/">Grafana</a>는 데이터 시각화, 모니터링 및 분석을 위한 오픈소스 플랫폼이다. 사용자는 Grafana에서 패널(panel)을 사용해 설정된 기간 동안 특정 metric을 나타내는 dashboard를 만들 수 있다.<br>Grafana는 그래프, 테이블 같은 것들을 지원할 뿐만 아니라 시각화를 위한 별도의 플러그인을 추가해서 사용할 수도 있다.</p></blockquote><h1 id="Spring-Boot-Dependency"><a href="#Spring-Boot-Dependency" class="headerlink" title="Spring Boot Dependency"></a>Spring Boot Dependency</h1><p><strong>Spring Boot 2.0 이상</strong>부터는 애플리케이션의 metric 측정을 위해서 <code>Micrometer</code>를 제공한다. Micrometer는 Spring Boot 2의 <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics">Actuator에 포함</a>되어 있기 때문에 <code>spring-boot-starter-actuator</code>를 dependency에 추가해주면 쉽게 사용할 수 있다.</p><p>추가적으로 <code>micrometer-registry-prometheus</code> dependency가 필요하다. 이 dependency는 Micrometer가 만들어내는 metric을 Prometheus 서버에서 사용할 수 있는 metric format으로 변경한다. </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">dependencies {</span><br><span class="line"> implementation <span class="string">'org.springframework.boot:spring-boot-starter-actuator'</span></span><br><span class="line"> implementation <span class="string">'io.micrometer:micrometer-registry-prometheus'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Actuator-Endpoint-설정"><a href="#Actuator-Endpoint-설정" class="headerlink" title="Actuator Endpoint 설정"></a>Actuator Endpoint 설정</h1><p>Actuator는 Spring MVC 혹은 Spring WebFlux를 사용하는 경우, Micrometer를 통해 생성된 애플리케이션의 metric을 <code>Prometheus 서버</code>에서 가져갈(Pull)수 있도록 <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-export-prometheus">추가적인 endpoint를 제공</a>해준다.</p><p>Spring Boot 2.0 이상부터 사용하는 Actuator는 1.x 버전에서 사용하던 것과는 달리 대부분의 endpoint가 disabled로 설정되어 있다. 기본적으로 <code>/health</code>와 <code>/info</code> 2가지 endpoint만 default로 사용 가능하다. 따라서 <code>/Prometheus</code> endpoint를 사용할 수 있도록 다음과 같이 <code>application.yml</code>에서 설정이 필요하다.</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line"> <span class="attr">endpoints:</span></span><br><span class="line"> <span class="attr">web:</span></span><br><span class="line"> <span class="attr">exposure:</span></span><br><span class="line"> <span class="attr">include:</span> <span class="string">health,</span> <span class="string">info,</span> <span class="string">prometheus</span></span><br></pre></td></tr></table></figure><p>애플리케이션을 실행하고 <a href="http://localhost:8080/actuator">http://localhost:8080/actuator</a> 를 통해 Actuator가 제공하는 endpoint들을 확인할 수 있다.<br><img src="/images/post/2019-12-04/endpoint.png" alt="endpoint"></p><p><a href="http://localhost:8080/actuator/prometheus">http://localhost:8080/actuator/prometheus</a> 에서는 Micrometer를 통해 수집된 metric들을 확인할 수 있다.</p><p><img src="/images/post/2019-12-04/prometheus_endpoint.png" alt="prometheus_endpoint"></p><blockquote><p><strong>Spring Boot 2는 기본적으로 다음과 같은 metric들을 제공하고 있다.</strong></p><ul><li>JVM, report utilization of:<ul><li>Various memory and buffer pools</li><li>Statistics related to garbage collection</li><li>Thread utilization</li><li>Number of classes loaded/unloaded</li></ul></li><li>CPU usage</li><li>Spring MVC and WebFlux request latencies</li><li>RestTemplate latencies</li><li>Cache utilization</li><li>Datasource utilization, including HikariCP pool metrics</li><li>RabbitMQ connection factories</li><li>File descriptor usage</li><li>Logback: record the number of events logged to Logback at each level</li><li>Uptime: report a gauge for uptime and a fixed gauge representing the application’s absolute start time</li><li>Tomcat usage</li></ul></blockquote><h1 id="Prometheus-설치-및-설정"><a href="#Prometheus-설치-및-설정" class="headerlink" title="Prometheus 설치 및 설정"></a>Prometheus 설치 및 설정</h1><p>애플리케이션에서의 설정은 끝났으니 애플리케이션에서 생성하는 metric을 수집하기 위한 Prometheus Server를 준비해보자.</p><p>테스트를 할 때는 역시나 docker(<a href="https://hub.docker.com/r/prom/prometheus">prometheus image</a>)를 이용하면 간편하다. 만약 실제 로컬 환경 혹은 별도의 서버 환경에서 설치해서 사용하고 싶다면 <a href="https://prometheus.io/download/#prometheus">https://prometheus.io/download/#prometheus</a> 에서 다운받아 설치하자.</p><p>Prometheus Server는 기동시 <code>/etc/prometheus/prometheus.yml</code> 설정 파일을 사용한다. docker volume mount를 이용해 Prometheus Server에서 사용할 설정 <code>prometheus.yml</code> 파일을 만들어보자.</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">global:</span></span><br><span class="line"> <span class="attr">scrape_interval:</span> <span class="string">10s</span> <span class="comment"># 10초 마다 Metric을 Pulling</span></span><br><span class="line"> <span class="attr">evaluation_interval:</span> <span class="string">10s</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">'spring-boot-app'</span></span><br><span class="line"> <span class="attr">metrics_path:</span> <span class="string">'/actuator/prometheus'</span> <span class="comment"># Application prometheus endpoint</span></span><br><span class="line"> <span class="attr">static_configs:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">'host.docker.internal:8080'</span>] <span class="comment"># Application host:port</span></span><br></pre></td></tr></table></figure><blockquote><p>docker에서 <code>host.docker.internal</code>은 특별한 DNS name으로 사용되며 docker를 실행하는 host를 가리킨다. 개발용으로만 사용해야 하며, Docker Desktop(Mac) 외부의 환경에서는 동작하지 않는다.</p></blockquote><p>파일 생성을 완료했다면 prom/prometheus 이미지를 이용해 docker로 prometheus를 실행한다.</p><figure class="highlight bash"><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="built_in">pwd</span></span><br><span class="line">/Users/user/work/prometheus</span><br><span class="line"></span><br><span class="line">$ docker run -p 9090:9090 -v /Users/user/work/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus -d prom/prometheus --config.file=/etc/prometheus/prometheus.yml</span><br></pre></td></tr></table></figure><p>문제 없이 실행되었다면 <a href="http://localhost:9090/">http://localhost:9090</a> 에 접속해보자. 다음과 같이 Prometheus main 화면을 볼 수 있다.<br><img src="/images/post/2019-12-04/prometheus_main.png" alt="prometheus_main.png"></p><p>docker로 Prometheus를 실행하면서 설정 파일이 잘 적용되었는지도 확인해보자.<br><img src="/images/post/2019-12-04/prometheus_configuration.png" alt="prometheus_configuration.png"></p><p>Status -> Targets 메뉴에서는 Application의 상태를 확인할 수 있다.<br><img src="/images/post/2019-12-04/prometheus_targets.png" alt="prometheus_targets.png"></p><p>Application의 상태(Status)가 DOWN인 경우에는 Application이 현재 기동중인지, prometheus.yml에서 targets의 값이 제대로 되어있는지 확인이 필요하다.<br><img src="/images/post/2019-12-04/prometheus_targets_2.png" alt="prometheus_targets_2.png"></p><p>여기까지 문제가 없다면 아래와 같이 수집된 metric 중 하나를 선택해 값이 잘 나오는지 확인해보자.<br><img src="/images/post/2019-12-04/prometheus_graph.png" alt="prometheus_graph.png"></p><h1 id="Prometheus에서-수집한-metric을-Grafana로-시각화하기"><a href="#Prometheus에서-수집한-metric을-Grafana로-시각화하기" class="headerlink" title="Prometheus에서 수집한 metric을 Grafana로 시각화하기"></a>Prometheus에서 수집한 metric을 Grafana로 시각화하기</h1><p>Prometheus의 웹 페이지에서 쿼리를 실행해 원하는 metric을 그래프로 시각화할 수 있다. 하지만 매번 모니터링을 위해 수동으로 쿼리를 실행하는 것은 비효율적이고 기본적으로 제공하는 대시보드 또한 간단하게 그래프를 볼 수 있는 정도이다.<br><strong>Prometheus가 제공하는 것만으로는 시각화하는데 한계가 있기 때문에 보통 별도의 시각화 도구를 이용해서 metric들을 모니터링한다.</strong></p><p>이번에는 별도의 시각화 도구로 Grafana를 사용해보자. 역시나 docker(<a href="https://hub.docker.com/r/grafana/grafana/">grafana/grafana</a>)를 사용한다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d --name=grafana -p 3000:3000 grafana/grafana</span><br></pre></td></tr></table></figure><p>실행 후 <a href="http://localhost:3000/">http://localhost:3000</a> 에 접속해보자. 다음과 같이 Grafana login 화면을 볼 수 있다.<br>기본 설정된 ID/PW인 <code>admin/admin</code> 으로 로그인할 수 있다.<br><img src="/images/post/2019-12-04/grafana_login.png" alt="grafana_login.png"></p><p>Home Dashboard에서 <code>Add data source</code>를 클릭해 Data Source 추가하자.<br><img src="/images/post/2019-12-04/grafana_add_datasource.png" alt="grafana_add_datasource.png"></p><p>Grafana에서 시각화할 데이터로서 Prometheus에 수집되고 있는 metric을 사용할 것이기 때문에 Prometheus를 선택한다.<br><img src="/images/post/2019-12-04/grafana_add_datasource_2.png" alt="grafana_add_datasource_2.png"></p><p>Name과 URL(Prometheus Server)을 설정하고 Save & Test를 클릭한다.<br><img src="/images/post/2019-12-04/grafana_add_datasource_3.png" alt="grafana_add_datasource_3.png"></p><p>Prometheus가 Data Source로 추가되었다.<br><img src="/images/post/2019-12-04/grafana_add_datasource_4.png" alt="grafana_add_datasource_4.png"></p><p>다음으로는 Data Source를 이용해 Dashboard를 생성해보자.<br><img src="/images/post/2019-12-04/grafana_new_dashboard.png" alt="grafana_new_dashboard.png"></p><p>그래프를 이용해볼 것이기 때문에 <code>Choose Visualization</code>을 클릭한다.<br><img src="/images/post/2019-12-04/grafana_new_dashboard_2.png" alt="grafana_new_dashboard_2.png"></p><p>Metrics로 이전에 Prometheus에서도 확인해보았던 jvm_memoruy_used_bytes를 선택한다.<br><img src="/images/post/2019-12-04/grafana_new_dashboard_3.png" alt="grafana_new_dashboard_3.png"></p><p>작업한 Dashboard를 저장한다.<br><img src="/images/post/2019-12-04/grafana_new_dashboard_4.png" alt="grafana_new_dashboard_4.png"></p><p>추가된 Dashboard를 확인할 수 있다.<br><img src="/images/post/2019-12-04/grafana_new_dashboard_5.png" alt="grafana_new_dashboard_5.png"></p><p>Spring Boot Application에서 생성하는 metric을 Prometheus를 통해 수집하고, Grafana로 시각화하는 것까지 마무리했다.<br>실제로는 애플리케이션에서 기본적으로 제공하는 Metric 뿐만 아니라 Micrometer를 이용해 직접 필요한 Metric을 추가할 수도 있다.<br>또한 Grafana에는 소개하지 않은 더 많은 유용한 기능들이 있다. 필요한 기능은 문서를 통해 찾아가며 사용해보자.</p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Spring-Boot/">Spring Boot</category>
<category domain="https://jongmin92.github.io/tags/Micrometer/">Micrometer</category>
<category domain="https://jongmin92.github.io/tags/Actuator/">Actuator</category>
<category domain="https://jongmin92.github.io/tags/Prometheus/">Prometheus</category>
<category domain="https://jongmin92.github.io/tags/Grafana/">Grafana</category>
<comments>https://jongmin92.github.io/2019/12/04/Spring/prometheus/#disqus_thread</comments>
</item>
<item>
<title>Micrometer</title>
<link>https://jongmin92.github.io/2019/12/03/Spring/micrometer/</link>
<guid>https://jongmin92.github.io/2019/12/03/Spring/micrometer/</guid>
<pubDate>Mon, 02 Dec 2019 15:18:00 GMT</pubDate>
<description><h1 id="Micrometer란"><a href="#Micrometer란" class="headerlink" title="Micrometer란?"></a>Micrometer란?</h1><p><a href="https://micrometer.io/">micrometer.io</a>에서는 <code>Micrometer</code>에 대해서 다음과 같이 소개하고 있다.</p>
<blockquote>
<p><strong>Micrometer</strong> provides a simple <code>facade</code> over the instrumentation <code>clients</code> for the most popular <code>monitoring systems</code>, allowing you to instrument your <code>JVM-based application</code> code without vendor lock-in. Think <code>SLF4J</code>, but for metrics</p>
</blockquote>
<p><strong>Micrometer는 JVM 기반의 애플리케이션에서 다양한 모니터링 도구가 제공하는 클라이언트 라이브러리에 대한 <a href="https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%82%AC%EB%93%9C_%ED%8C%A8%ED%84%B4">facade</a>를 제공한다.</strong> 로깅 관련된 시스템에서는 SLF4J가 있다면 모니터링(metric) 시스템에서는 Micrometer가 있는 것이다.</p>
<p>즉, 모니터링 시스템을 만드는 vendor들은 Micrometer 인터페이스를 따르기 때문에 Micrometer를 사용하면 애플리케이션 내의 코드 상에서는 모니터링 시스템 클라이언트로 어떤 것을 사용할지에 대한 고민에서 벗어나 Micrometer를 이용해 애플리케이션 metric을 수집하기만 하면 된다.<br>(모니터링 시스템을 선택하는 것은 런타임 시점에 정해진다고 생각하면 된다.)</p></description>
<content:encoded><![CDATA[<h1 id="Micrometer란"><a href="#Micrometer란" class="headerlink" title="Micrometer란?"></a>Micrometer란?</h1><p><a href="https://micrometer.io/">micrometer.io</a>에서는 <code>Micrometer</code>에 대해서 다음과 같이 소개하고 있다.</p><blockquote><p><strong>Micrometer</strong> provides a simple <code>facade</code> over the instrumentation <code>clients</code> for the most popular <code>monitoring systems</code>, allowing you to instrument your <code>JVM-based application</code> code without vendor lock-in. Think <code>SLF4J</code>, but for metrics</p></blockquote><p><strong>Micrometer는 JVM 기반의 애플리케이션에서 다양한 모니터링 도구가 제공하는 클라이언트 라이브러리에 대한 <a href="https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%82%AC%EB%93%9C_%ED%8C%A8%ED%84%B4">facade</a>를 제공한다.</strong> 로깅 관련된 시스템에서는 SLF4J가 있다면 모니터링(metric) 시스템에서는 Micrometer가 있는 것이다.</p><p>즉, 모니터링 시스템을 만드는 vendor들은 Micrometer 인터페이스를 따르기 때문에 Micrometer를 사용하면 애플리케이션 내의 코드 상에서는 모니터링 시스템 클라이언트로 어떤 것을 사용할지에 대한 고민에서 벗어나 Micrometer를 이용해 애플리케이션 metric을 수집하기만 하면 된다.<br>(모니터링 시스템을 선택하는 것은 런타임 시점에 정해진다고 생각하면 된다.)</p><span id="more"></span><p>예를 들어, 모니터링 시스템으로 Prometheus를 사용한다고 하면 아래와 같이 표현할 수 있다.<br><img src="/images/post/2019-12-03/micrometer.png" alt="micrometer"></p><p>Micrometer는 다음과 같은 모니터링 시스템을 지원한다.</p><ul><li>AppOptics, Azure Monitor, Netflix Atlas, CloudWatch, Datadog, Dynatrace, Elastic, Ganglia, Graphite, Humio, Influx/Telegraf, JMX, KairosDB, New Relic, Prometheus, SignalFx, Google Stackdriver, StatsD, Wavefront</li></ul><p><strong>Micrometer에 의해서 기록된 애플리케이션의 metric 정보는 시스템의 이상 유뮤룰 판단하기 위한 모니터링(알람) 용도로 사용된다.</strong></p><h1 id="Supported-monitoring-systems"><a href="#Supported-monitoring-systems" class="headerlink" title="Supported monitoring systems"></a>Supported monitoring systems</h1><p>Micrometer는 코어 모듈과 측정 <a href="https://en.wikipedia.org/wiki/Service_provider_interface">SPI</a>(Service Provider Interface)를 포함하고 있다. (각각 Registry라고 부르는 다양한 모니터링 시스템에 대한 구현을 포함하고 있다.)<br>모니터링 시스템에는 중요한 3가지 특징이 있다.</p><h2 id="Dimensionality"><a href="#Dimensionality" class="headerlink" title="Dimensionality"></a>Dimensionality</h2><p>Dimensionality는 수집하는 metric 이름에 tag(key-value)를 붙일 수 있도록 지원하는 것을 말한다. 반면 일부 모니터링 시스템의 경우에는 tag 형태가 아닌, flat한 metric name만 사용이 가능하다. 이를 hierarchical system이라고 한다.<br>Micrometer는 hierarchical system에 metric을 보낼 때는 tag를 metric name에 추가한다.</p><ul><li>Dimensional: AppOptics, Atlas, Azure Monitor, Cloudwatch, Datadog, Datadog StatsD, Dynatrace, Elastic, Humio, Influx, KairosDB, New Relic, <code>Prometheus</code>, SignalFx, Sysdig StatsD, Telegraf StatsD, Wavefront</li><li>Hierarchical: Graphite, Ganglia, JMX, Etsy StatsD</li></ul><h2 id="Rate-aggregation"><a href="#Rate-aggregation" class="headerlink" title="Rate aggregation"></a>Rate aggregation</h2><p>모니터링 시스템 사용자들은 일정 시간 간격 동안의 특정 지표에 대한 평균값을 필요로 하는 경우가 많다. 어떤 모니터링 시스템은 애플리케이션에서 평균값을 직접 구해서 보내주기를 기대한다. 반면에 어떤 모니터링 시스템은 애플리케이션이 누적된 값을 보내주기를 기대하며, 모니터링 시스템이 직접 평균값을 구하는 경우도 있다.</p><ul><li>Client-side: AppOptics, Atlas, Azure Monitor, Datadog, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, all StatsD flavors, SignalFx</li><li>Server-side: <code>Prometheus</code>, Wavefront</li></ul><h2 id="Publishing"><a href="#Publishing" class="headerlink" title="Publishing"></a>Publishing</h2><p>일부 모니터링 시스템은 metric 정보를 애플리케이션으로부터 polling한다. 반면 일부 모니터링 시스템은 애플리케이션이 일정한 간격으로 metric 정보를 push하는 방식으로 사용된다.</p><ul><li>Client pushes: AppOptics, Atlas, Azure Monitor, Datadog, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, SignalFx, Wavefront</li><li>Server polls: <code>Prometheus</code>, all StatsD flavors</li></ul><p>Micrometer 어떤 Registry를 사용하는지에 따라, 위와 같은 요구사항을 충족하도록 metric 정보를 커스터마이징한다.</p><h1 id="Registry"><a href="#Registry" class="headerlink" title="Registry"></a>Registry</h1><p><code>Meter</code>는 애플리케이션의 metric을 수집하기 위한 인터페이스이다. Meter는 <code>MeterRegistry</code>에 의해 생성되어 등록된다. 지원되는 각 모니터링 시스템은 MeterRegistry 구현체를 갖고 있다.</p><p><code>SimpleMeterRegistry</code>는 각 meter의 최신 값을 메모리에 저장한다. 그리고 metric 정보를 다른 시스템으로 내보내지 않는다. 따라서 만약 어떤 모니터링 시스템을 사용할지 결정하지 못했다면 SimpleMegerRegistry를 사용하면 된다.</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">MeterRegistry registry = <span class="keyword">new</span> SimpleMeterRegistry();</span><br></pre></td></tr></table></figure><h2 id="Composite-registries"><a href="#Composite-registries" class="headerlink" title="Composite registries"></a>Composite registries</h2><p>Micrometer는 여러 Registry를 추가할 수 있는 <code>CompositeMeterRegistry</code>를 제공한다. 따라서 CompositeMeterRegistry를 사용하면 둘 이상의 모니터링 시스템에서 동시에 metric을 사용할 수 있다.</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">CompositeMeterRegistry compositeRegistry = <span class="keyword">new</span> CompositeMeterRegistry();</span><br><span class="line">SimpleMeterRegistry oneSimpleMeter = <span class="keyword">new</span> SimpleMeterRegistry();</span><br><span class="line">AtlasMeterRegistry atlasMeterRegistry = <span class="keyword">new</span> AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);</span><br><span class="line"> </span><br><span class="line">compositeRegistry.add(oneSimpleMeter);</span><br><span class="line">compositeRegistry.add(atlasMeterRegistry);</span><br></pre></td></tr></table></figure><h2 id="Global-registry"><a href="#Global-registry" class="headerlink" title="Global registry"></a>Global registry</h2><p>Micrometer는 static 변수로 <code>Global MeterRegistry</code>를 제공한다. <a href="https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/Metrics.java#L35">Metrics.globalRegistry</a>를 통해 static 변수에 접근할 수 있으며 Metrics 클래스에는 글로벌 MeterRegistry를 기반으로 Meter를 생성하는 정적 빌더 메소드가 있다.<br>Global MeterRegistry는 CompositeMeterRegistry 객체이다.</p><h1 id="Meters"><a href="#Meters" class="headerlink" title="Meters"></a>Meters</h1><p>Micrometer는 Meter의 구현체로 다음의 것들을 지원한다. Meter의 type 별로 수집되는 metric 수가 다르다.</p><ul><li>Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer, TimeGauge</li></ul><p><strong>Meter는 <code>이름(name)</code>과 <code>태그(tag)</code>로 고유하게 식별된다.</strong></p><h2 id="Naming"><a href="#Naming" class="headerlink" title="Naming"></a>Naming</h2><p>Micrometer는 소문자 단어를 ‘.’으로 구분하는 naming 규칙을 사용한다. 각각의 모니터링 시스템은 naming 규칙과 관련해 권장 사항을 갖고 있으며 일부 모니터링 시스템은 서로의 naming 규칙이 달라 호환되지 않을 수도 있다.</p><p>따라서 모니터링 시스템의 각 Micrometer 구현체는 소문자 단어와 ‘.’으로 구분된 이름을 각자의 모니터링 시스템 naming 규칙으로 변환하는 기능을 제공한다.</p><p>예를 들면 아래의 <code>timer</code> meter는 각각의 모니터링에서 다음과 같이 변경된다.</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">registry.timer(<span class="string">"http.server.requests"</span>);</span><br></pre></td></tr></table></figure><ul><li>Prometheus: <code>http_server_requests_duration_seconds</code></li><li>Atlas: <code>httpServerRequests</code></li><li>Graphite: <code>http.server.requests</code></li><li>InfluxDB: <code>http_server_requests</code></li></ul><p><strong>그러므로 Micrometer의 소문자 단어를 ‘.’으로 구분하는 naming 규칙을 사용하면 모니터링 시스템 종류에 상관없이 metric name에 대해 이식성을 보장할 수 있다.</strong></p><h2 id="Counter"><a href="#Counter" class="headerlink" title="Counter"></a>Counter</h2><p>Counter는 애플리케이션에서 특정 속성에 대한 카운트를 기록한다. Build method 혹은 MetricRegistry의 helper method를 통해 custom counter를 생성할 수 있다.</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">Counter counter = Counter</span><br><span class="line"> .builder(<span class="string">"instance"</span>)</span><br><span class="line"> .description(<span class="string">"indicates instance count of the object"</span>)</span><br><span class="line"> .tags(<span class="string">"dev"</span>, <span class="string">"performance"</span>)</span><br><span class="line"> .register(registry);</span><br><span class="line"> </span><br><span class="line">counter.increment(<span class="number">2.0</span>);</span><br><span class="line"> </span><br><span class="line">assertTrue(counter.count() == <span class="number">2</span>);</span><br><span class="line"> </span><br><span class="line">counter.increment(-<span class="number">1</span>); <span class="comment">// 카운트는 증가만 가능하다.</span></span><br><span class="line"> </span><br><span class="line">assertTrue(counter.count() == <span class="number">2</span>);</span><br></pre></td></tr></table></figure><h2 id="Timers"><a href="#Timers" class="headerlink" title="Timers"></a>Timers</h2><p>시스템의 latency(지연 시간) 혹은 이벤트 빈도를 측정하기 위해서 Timers를 사용할 수 있다. Timer는 이벤트가 발생한 수와 총 시간을 기록한다.</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></pre></td><td class="code"><pre><span class="line">SimpleMeterRegistry registry = <span class="keyword">new</span> SimpleMeterRegistry();</span><br><span class="line">Timer timer = registry.timer(<span class="string">"app.event"</span>);</span><br><span class="line">timer.record(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(<span class="number">1500</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException ignored) { }</span><br><span class="line">});</span><br><span class="line"> </span><br><span class="line">timer.record(<span class="number">3000</span>, MILLISECONDS);</span><br><span class="line"> </span><br><span class="line">assertTrue(<span class="number">2</span> == timer.count());</span><br><span class="line">assertTrue(<span class="number">4510</span> > timer.totalTime(MILLISECONDS) && <span class="number">4500</span> <= timer.totalTime(MILLISECONDS));</span><br></pre></td></tr></table></figure><h2 id="Gauge"><a href="#Gauge" class="headerlink" title="Gauge"></a>Gauge</h2><p>Gauge는 Meter의 현재 값을 보여준다. 다른 Meter와 다르게 Guage는 데이터의 변경이 관찰 된 경우에만 데이터를 기록한다. 캐시 등의 통계를 모니터링 할 때 유용하다.</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></pre></td><td class="code"><pre><span class="line">SimpleMeterRegistry registry = <span class="keyword">new</span> SimpleMeterRegistry();</span><br><span class="line">List<String> list = <span class="keyword">new</span> ArrayList<>(<span class="number">4</span>);</span><br><span class="line"> </span><br><span class="line">Gauge gauge = Gauge</span><br><span class="line"> .builder(<span class="string">"cache.size"</span>, list, List::size)</span><br><span class="line"> .register(registry);</span><br><span class="line"> </span><br><span class="line">assertTrue(gauge.value() == <span class="number">0.0</span>);</span><br><span class="line"> </span><br><span class="line">list.add(<span class="string">"1"</span>);</span><br><span class="line"> </span><br><span class="line">assertTrue(gauge.value() == <span class="number">1.0</span>);</span><br></pre></td></tr></table></figure><h1 id="Binders"><a href="#Binders" class="headerlink" title="Binders"></a>Binders</h1><p>Micrometer에는 JVM, 캐시, ExecutorService 및 로깅 서비스를 모니터링하기 위한 여러 내장 Binder가 있다. </p><ul><li>JVM 및 시스템 모니터링: ClassLoaderMetrics</li><li>JVM memory pool: JvmMemoryMetrics</li><li>GC metrics: JvmGcMetrics</li><li>Thread 및 CPU 사용률: JvmThreadMetrics, ProcessorMetrics</li></ul>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Spring-Boot/">Spring Boot</category>
<category domain="https://jongmin92.github.io/tags/Micrometer/">Micrometer</category>
<comments>https://jongmin92.github.io/2019/12/03/Spring/micrometer/#disqus_thread</comments>
</item>
<item>
<title>Spring Boot에서의 Bean Validation (2)</title>
<link>https://jongmin92.github.io/2019/11/21/Spring/bean-validation-2/</link>
<guid>https://jongmin92.github.io/2019/11/21/Spring/bean-validation-2/</guid>
<pubDate>Wed, 20 Nov 2019 15:44:00 GMT</pubDate>
<description><blockquote>
<p>해당 포스팅에서 사용된 예제 코드는 <a href="https://github.com/jongmin92/code-examples/tree/master/spring-boot/validation">spring-boot validation example</a>에서 확인 가능합니다.</p>
</blockquote></description>
<content:encoded><![CDATA[<blockquote><p>해당 포스팅에서 사용된 예제 코드는 <a href="https://github.com/jongmin92/code-examples/tree/master/spring-boot/validation">spring-boot validation example</a>에서 확인 가능합니다.</p></blockquote><span id="more"></span><h1 id="Custom-Validator"><a href="#Custom-Validator" class="headerlink" title="Custom Validator"></a>Custom Validator</h1><p>사용 가능한 <a href="https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/javax/validation/constraints/package-summary.html">constraint 어노테이션</a>이 제공하는 제약 조건 외에 필요한 경우, 직접 만들어서 사용할 수 있다.</p><p><a href="https://jongmin92.github.io/2019/11/18/Spring/bean-validation-1/">이전 포스팅</a>에서 <code>InPutRequest</code>와 <code>InputEntity</code> 클래스에서 정규식(Regular Expression)을 사용하여 String이 유효한 PinCode 형식(6자리)인지 확인했었다. 이 부분을 별도의 Validator를 구현해 대체해보려고 한다.</p><p>먼저, custom constraint 어노테이션 <code>PinCode</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target({ FIELD })</span></span><br><span class="line"><span class="meta">@Retention(RUNTIME)</span></span><br><span class="line"><span class="meta">@Constraint(validatedBy = PinCodeValidator.class)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> PinCode {</span><br><span class="line"></span><br><span class="line"> <span class="function">String <span class="title">message</span><span class="params">()</span> <span class="keyword">default</span> "</span>{PinCode.invalid}<span class="string">";</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> Class<?>[] groups() default {};</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> Class<? extends Payload>[] payload() default {};</span></span><br><span class="line"><span class="string">}</span></span><br></pre></td></tr></table></figure><p>custom constraint 어노테이션에는 다음과 같은 것들이 필요하다.</p><ul><li>parameter <code>message</code>: <code>ValidationMessages.properties</code>에서 특정 property key를 가리키는 메시지 (제약 조건 위반시 메시지로 사용된다.)</li><li>parameter <code>groups</code>: 유효성 검사가 어떤 상황에서 실행되는지 정의할 수 있는 매개 변수 그룹.</li><li>parameter <code>payload</code>: 유효성 검사에 전달할 payload를 정의할 수 있는 매개 변수.</li><li><code>@Constraint</code>: <code>ConstraintValidator</code> interface 구현을 나타내는 어노테이션</li></ul><p>PinCode validator는 다음과 같이 구현한다.</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></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">PinCodeValidator</span> <span class="keyword">implements</span> <span class="title">ConstraintValidator</span><<span class="title">PinCode</span>, <span class="title">String</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isValid</span><span class="params">(String value, ConstraintValidatorContext context)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Pattern pattern = Pattern.compile(<span class="string">"^[0-9]{6}$"</span>);</span><br><span class="line"> <span class="keyword">final</span> Matcher matcher = pattern.matcher(value);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> matcher.matches();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>이제 다른 constraint 어노테이션과 마찬가지로 @PinCode 어노테이션을 사용할 수 있다.</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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InputEntityWithCustomValidator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PinCode</span></span><br><span class="line"> <span class="keyword">private</span> String pinCode;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="직접-Validator를-생성해-Validation-하기"><a href="#직접-Validator를-생성해-Validation-하기" class="headerlink" title="직접 Validator를 생성해 Validation 하기"></a>직접 Validator를 생성해 Validation 하기</h1><p>Spring이 지원하는 Bean Validator에 의존하지 않고 직접 Bean Validation을 하고자하는 경우가 있을 수 있다.</p><p>이 경우, 직접 <code>Validator</code>를 생성하고 validation을 할 수 있다. Spring의 지원이 전혀 필요하지 않다는 것이다.</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="meta">@Service</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DirectlyValidateService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">validateInput</span><span class="params">(InputEntity inputEntity)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> ValidatorFactory factory = Validation.buildDefaultValidatorFactory();</span><br><span class="line"> <span class="keyword">final</span> Validator validator = factory.getValidator();</span><br><span class="line"> <span class="keyword">final</span> Set<ConstraintViolation<InputEntity>> violations = validator.validate(inputEntity);</span><br><span class="line"> <span class="keyword">if</span> (!violations.isEmpty()) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConstraintViolationException(violations);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>그러나, Spring Boot는 이미 사전에 설정되어 만들어진 <code>Validator</code> 인스턴스를 제공한다. 그렇기 때문에 위와 같이 별도의 Validator 인스턴스를 직접 생성하지 않고 서비스에서 주입받아 사용하면 된다.</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="meta">@Service</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DirectlyValidateService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Validator validator;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">validateInputWithInjectedValidator</span><span class="params">(InputEntity inputEntity)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Set<ConstraintViolation<InputEntity>> violations = validator.validate(inputEntity);</span><br><span class="line"> <span class="keyword">if</span> (!violations.isEmpty()) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConstraintViolationException(violations);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>위의 Service는 Spring에 의해 Bean으로 생성될 때, Validator를 주입받게 된다.</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><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="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DirectlyValidateServiceTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> DirectlyValidateService service;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputEntityIsInvalid_thenThrowsException</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> InputEntity inputEntity = <span class="keyword">new</span> InputEntity();</span><br><span class="line"> inputEntity.setNumberBetweenOneAndTen(<span class="number">50</span>);</span><br><span class="line"> inputEntity.setNotEmptyString(<span class="string">""</span>);</span><br><span class="line"> inputEntity.setPinCode(<span class="string">"1234"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> service.validateInput(inputEntity);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">givenInjectedValidator_whenInputEntityIsInvalid_thenThrowsException</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> InputEntity inputEntity = <span class="keyword">new</span> InputEntity();</span><br><span class="line"> inputEntity.setNumberBetweenOneAndTen(<span class="number">50</span>);</span><br><span class="line"> inputEntity.setNotEmptyString(<span class="string">""</span>);</span><br><span class="line"> inputEntity.setPinCode(<span class="string">"1234"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> service.validateInputWithInjectedValidator(inputEntity);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Validation-Groups"><a href="#Validation-Groups" class="headerlink" title="Validation Groups"></a>Validation Groups</h1><p>종종 특정 객체(클래스)는 서로 다른 상황에서 공유되어 사용될 수 있다.</p><p>예를 들면, CRUD와 같은 작업에서 “Create”와 “Update”를 수행할 때 같은 객체(클래스)를 사용하는 것이다. 그러나 다음의 경우처럼 서로 다른 상황에서 실행되어야 하는 validation이 있을 수 있다.</p><ul><li>“Create” 상황에서만 Validation</li><li>“Update” 상황에서만 Validation</li><li>두 가지 상황 모두에서 Validation</li></ul><p>위와 같이 Validation 규칙을 구현할 수 있는 Bean Validation 기능을 <code>Validation Groups</code>이라고 부른다.</p><p>이전에 custom constraint 어노테이션을 직접 만들면서 <code>groups</code> 필드가 반드시 있어야 하는것을 보았다. 이를 이용해서 Validation이 실행되어야 하는 특정 validation group을 명시할 수 있다.</p><p>CRUD 예제를 위해 <code>OnCreate</code>와 <code>OnUpdate</code>라는 2개의 marker interface를 정의한다.</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="class"><span class="keyword">interface</span> <span class="title">OnCreate</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">OnUpdate</span> </span>{}</span><br></pre></td></tr></table></figure><p>그 다음 marker interface를 다음과 같은 constraint 어노테이션과 함께 사용할 수 있다.</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"><span class="class"><span class="keyword">class</span> <span class="title">InputEntityWithCustomValidator</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Null(groups = OnCreate.class)</span></span><br><span class="line"> <span class="meta">@NotNull(groups = OnUpdate.class)</span></span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>위의 코드는 “Create” 상황에서는 id가 null일 수 있고, “Update” 상황에서는 not null이어야 함을 의미한다.</p><p>Spring은 <code>@Validated</code> 어노테이션을 이용해 validation group을 사용할 수 있도록 지원한다.</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ValidateServiceWithGroups</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Validated(OnCreate.class)</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">validateForCreate</span><span class="params">(<span class="meta">@Valid</span> InputEntityWithCustomValidator input)</span> </span>{</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Validated(OnUpdate.class)</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">validateForUpdate</span><span class="params">(<span class="meta">@Valid</span> InputEntityWithCustomValidator input)</span> </span>{</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>@Validated</code> 어노테이션은 클래스에도 적용되어야 하며, validation group에 의한 Validation을 하기 위해서는 메서드에도 적용되어야 한다.</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><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="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ValidateServiceWithGroupsTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ValidateServiceWithGroups service;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputIsInvalidForCreate_thenThrowsException</span><span class="params">()</span> </span>{</span><br><span class="line"> InputEntityWithCustomValidator input = <span class="keyword">new</span> InputEntityWithCustomValidator();</span><br><span class="line"> input.setId(<span class="number">17L</span>);</span><br><span class="line"> input.setNumberBetweenOneAndTen(<span class="number">5</span>);</span><br><span class="line"> input.setNotEmptyString(<span class="string">"not empty"</span>);</span><br><span class="line"> input.setPinCode(<span class="string">"123456"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> service.validateForCreate(input);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputIsInvalidForUpdate_thenThrowsException</span><span class="params">()</span> </span>{</span><br><span class="line"> InputEntityWithCustomValidator input = <span class="keyword">new</span> InputEntityWithCustomValidator();</span><br><span class="line"> input.setId(<span class="keyword">null</span>);</span><br><span class="line"> input.setNumberBetweenOneAndTen(<span class="number">5</span>);</span><br><span class="line"> input.setNotEmptyString(<span class="string">"not empty"</span>);</span><br><span class="line"> input.setPinCode(<span class="string">"123456"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> service.validateForUpdate(input);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Spring-Boot/">Spring Boot</category>
<category domain="https://jongmin92.github.io/tags/Bean-Validation/">Bean Validation</category>
<comments>https://jongmin92.github.io/2019/11/21/Spring/bean-validation-2/#disqus_thread</comments>
</item>
<item>
<title>Spring Boot에서의 Bean Validation (1)</title>
<link>https://jongmin92.github.io/2019/11/18/Spring/bean-validation-1/</link>
<guid>https://jongmin92.github.io/2019/11/18/Spring/bean-validation-1/</guid>
<pubDate>Sun, 17 Nov 2019 15:35:00 GMT</pubDate>
<description><p><a href="https://beanvalidation.org/news/2018/02/26/bean-validation-2-0-whats-in-it/">Bean Validation</a>은 Java 생태계에서 유효성(Validation) 검증 로직을 구현하기위한 사실상의 표준이다. Bean Validation은 Spring과 Spring Boot에도 잘 통합되어 있다.</p></description>
<content:encoded><![CDATA[<p><a href="https://beanvalidation.org/news/2018/02/26/bean-validation-2-0-whats-in-it/">Bean Validation</a>은 Java 생태계에서 유효성(Validation) 검증 로직을 구현하기위한 사실상의 표준이다. Bean Validation은 Spring과 Spring Boot에도 잘 통합되어 있다.</p><span id="more"></span><blockquote><p>해당 포스팅에서 사용된 예제 코드는 <a href="https://github.com/jongmin92/code-examples/tree/master/spring-boot/validation">spring-boot validation example</a>에서 확인 가능합니다.</p></blockquote><h1 id="Validation-설정"><a href="#Validation-설정" class="headerlink" title="Validation 설정"></a>Validation 설정</h1><p>Spring Boot에서의 Bean Validation은 <code>spring-boot-starter-validation</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">implementation(<span class="string">'org.springframework.boot:spring-boot-starter-validation'</span>)</span><br></pre></td></tr></table></figure><p>Spring Dependency Management Gradle 플러그인(“io.spring.dependency-management”)을 사용한다면 현재 사용중인 Spring Boot 버전에서 자동으로 의존성(버전)을 가져오기 때문에 별도로 버전을 명시할 필요가 없다.</p><p>만약 <code>spring-boot-starter-web</code>를 포함하고 있다면 Validation Starter도 포함되어 있기 때문에 따로 추가할 필요는 없다.</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">implementation(<span class="string">'org.springframework.boot:spring-boot-starter-web'</span>)</span><br></pre></td></tr></table></figure><blockquote><p>Validation Starter는 Bean Validation Specification 구현체 중 가장 널리 사용되고 있는 <a href="https://hibernate.org/validator/">hibernate validator</a>를 포함하고 있다.</p></blockquote><h1 id="Bean-Validation-기본"><a href="#Bean-Validation-기본" class="headerlink" title="Bean Validation 기본"></a>Bean Validation 기본</h1><p><strong>기본적으로 Bean Validation은 클래스 필드에 특정 어노테이션(annotation)을 달아 제약 조건을 정의하는 방식으로 동작한다.</strong> 그 후 해당 클래스 객체를 <a href="https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/javax/validation/Validator.html">Validator</a>를 이용해 제약 조건의 충족 여부를 확인한다.</p><h1 id="Spring-MVC-Controller의-Validation"><a href="#Spring-MVC-Controller의-Validation" class="headerlink" title="Spring MVC Controller의 Validation"></a>Spring MVC Controller의 Validation</h1><p>Spring RestController를 통해 Client로부터 전달받은 요청(request, input)에 대해 유효성을 검사하고자 한다. Client로부터 들어오는 다음의 3가지 요청 형식에 대해서 유효성 검사를 할 수 있다.</p><ul><li><strong>request body</strong></li><li><strong>path에 포함된 variables</strong> (ex. /foos/{id}의 id) </li><li><strong>query parameters</strong></li></ul><h2 id="Request-Body"><a href="#Request-Body" class="headerlink" title="Request Body"></a>Request Body</h2><p>POST 혹은 PUT 요청에서 request body에 JSON 형식의 데이터를 전달하는 것은 일반적이며, Spring은 JSON 형식의 데이터를 Java 객체에 자동으로 매핑한다. 이 과정에서 매핑되는 Java 객체가 요구 사항을 충족하는지 확인하려 한다.</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="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InputRequest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Min(1)</span></span><br><span class="line"> <span class="meta">@Max(10)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> numberBetweenOneAndTen;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@NotEmpty</span></span><br><span class="line"> <span class="keyword">private</span> String notEmptyString;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Pattern(regexp = "^[0-9]{6}$")</span></span><br><span class="line"> <span class="keyword">private</span> String pinCode;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>numberBetweenOneAndTen는 1에서 10 사이의 값을 가져야 하며, notEmptyString은 빈 문자열(“”)이 아니어야 하고, pinCode는 6자리의 숫자를 가져야 한다.</p><p>request body에서 <code>InputRequest</code> 객체를 가져와 유효성 검사를 하는 RestController는 다음과 같다.</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"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ValidateRequestBodyController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostMapping("/validateBody")</span></span><br><span class="line"> <span class="function">ResponseEntity<String> <span class="title">validateBody</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> InputRequest request)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> ResponseEntity.ok(<span class="string">"valid"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>@RequestBody 어노테이션을 사용하고 있는 Input 매개변수에 <code>@Valid</code> 어노테이션만 추가하면 된다.</strong> 이로인해 Spring은 다른 작업을 수행하기 전에 먼저 객체를 Validator에 전달해서 유효성을 검사하게 된다.</p><blockquote><p>만약 <code>InputRequest</code> 클래스에 유효성을 검사해야하는 다른 객체가 필드로 포함된 경우, 이 필드에도 @Valid 어노테이션을 추가해야한다. 이러한 경우를 <strong>Complex Type</strong>이라고 부른다.</p></blockquote><p>유효성 검사에 실패할 경우 <code>MethodArgumentNotValidException</code> 예외가 발생한다. 기본적으로 Spring은 이 예외에 대해서 HTTP status code 400(Bad Request)으로 변환한다.</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><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@WebMvcTest(controllers = ValidateRequestBodyController.class)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ValidateRequestBodyControllerTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MockMvc mvc;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ObjectMapper objectMapper;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputRequestIsInvalid_thenReturnStatus400</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">final</span> InputRequest request = <span class="keyword">new</span> InputRequest();</span><br><span class="line"> request.setNumberBetweenOneAndTen(<span class="number">50</span>);</span><br><span class="line"> request.setNotEmptyString(<span class="string">""</span>);</span><br><span class="line"> request.setPinCode(<span class="string">"1234"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> String body = objectMapper.writeValueAsString(request);</span><br><span class="line"></span><br><span class="line"> mvc.perform(post(<span class="string">"/validateBody"</span>)</span><br><span class="line"> .contentType(<span class="string">"application/json"</span>)</span><br><span class="line"> .content(body))</span><br><span class="line"> .andExpect(status().isBadRequest());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Path-Variables-amp-Request-Parameters"><a href="#Path-Variables-amp-Request-Parameters" class="headerlink" title="Path Variables & Request Parameters"></a>Path Variables & Request Parameters</h2><p>path에 포함된 variables과 query parameters에 대한 유효성 검사는 조금 다르게 동작한다.</p><p>Path Variable과 Request Parameter의 경우는 int와 같은 primitive type이거나 Integer 혹은 String과 같은 객체이기 때문에 복잡한 유효성 검사를 하지 않는다.</p><p>클래스 필드에 직접 어노테이션을 추가하지 않고 다음과 같이 Controller 메소드 매개 변수에 직접 제약 조건을 추가한다.</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ValidateParametersController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping("/validatePathVariable/{id}")</span></span><br><span class="line"> <span class="function">ResponseEntity<String> <span class="title">validatePathVariable</span><span class="params">(<span class="meta">@PathVariable("id")</span> <span class="meta">@Min(5)</span> <span class="keyword">int</span> id)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> ResponseEntity.ok(<span class="string">"valid"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping("/validateRequestParameter")</span></span><br><span class="line"> <span class="function">ResponseEntity<String> <span class="title">validateRequestParameter</span><span class="params">(<span class="meta">@RequestParam("param")</span> <span class="meta">@Min(5)</span> <span class="keyword">int</span> param)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> ResponseEntity.ok(<span class="string">"valid"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong><code>@Validated</code> 어노테이션을 클래스 레벨의 Controller에 추가해 Spring이 메서드 매개 변수에 대한 제한 조건 annotation을 평가하게 해야한다.</strong></p><p>request body 유효성 검사와 달리 실패할 경우 <code>MethodArgumentNotValidException</code> 예외가 아닌 <code>ConstraintViolationException</code> 예외가 발생한다. 주의할 것으로는 Spring은 <code>ConstraintViolationException</code> 예외에 대해서는 기본적으로 exception을 handling 하지 않기 때문에 HTTP status code 500(Internal Server Error)로 처리한다.</p><p>만약 HTTP status code 400(Bad Request)으로 처리하고자 한다면, custom exception handler를 사용하면 된다.</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="meta">@Validated</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ValidateParametersController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// request mapping method omitted</span></span><br><span class="line"> </span><br><span class="line"> <span class="meta">@ExceptionHandler(ConstraintViolationException.class)</span></span><br><span class="line"> <span class="meta">@ResponseStatus(HttpStatus.BAD_REQUEST)</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="function">String <span class="title">handleConstraintViolationException</span><span class="params">(ConstraintViolationException e)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"not valid due to validation error: "</span> + e.getMessage();</span><br><span class="line"> }</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><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="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@WebMvcTest(controllers = ValidateParametersController.class)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ValidateParametersControllerTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MockMvc mvc;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenPathVariableIsInValid_thenReturnStatus400</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> mvc.perform(get(<span class="string">"/validatePathVariable/1"</span>))</span><br><span class="line"> .andExpect(status().isBadRequest());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenRequestParameterIsInvalid_thenReturnStatus400</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> mvc.perform(get(<span class="string">"/validateRequestParameter"</span>)</span><br><span class="line"> .param(<span class="string">"param"</span>, <span class="string">"1"</span>))</span><br><span class="line"> .andExpect(status().isBadRequest());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Spring-Service의-Validation"><a href="#Spring-Service의-Validation" class="headerlink" title="Spring Service의 Validation"></a>Spring Service의 Validation</h1><p>Controller 레벨에서 입력을 검증하는것 뿐만 아니라 <code>@Validated</code>와 <code>@Valid</code> 어노테이션을 이용해 다른 Spring component에서도 입력에 대해 유효성을 검증할 수 있다.</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"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ValidateService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">validateInputRequest</span><span class="params">(<span class="meta">@Valid</span> InputRequest input)</span> </span>{</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>@Validated</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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ValidateServiceTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ValidateService service;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputRequestIsInvalid_thenThrowException</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> InputRequest request = <span class="keyword">new</span> InputRequest();</span><br><span class="line"> request.setNumberBetweenOneAndTen(<span class="number">50</span>);</span><br><span class="line"> request.setNotEmptyString(<span class="string">""</span>);</span><br><span class="line"> request.setPinCode(<span class="string">"1234"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> service.validateInputRequest(request);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Spring-Repository의-Validation"><a href="#Spring-Repository의-Validation" class="headerlink" title="Spring Repository의 Validation"></a>Spring Repository의 Validation</h1><p>유효성 검사를 위한 가장 마지막 계층은 persistence layer이다. 기본적으로 Spring Data는 Hibernate를 사용하여 Bean Validation을 지원한다.</p><h2 id="JPA-Entities"><a href="#JPA-Entities" class="headerlink" title="JPA Entities"></a>JPA Entities</h2><p><code>InputEntity</code> 클래스의 객체를 DB에 저장하려고 한다. 먼저 필요한 JPA 어노테이션들을 추가한다.</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="meta">@Data</span></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InputEntity</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Id</span></span><br><span class="line"> <span class="meta">@GeneratedValue</span></span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Min(1)</span></span><br><span class="line"> <span class="meta">@Max(10)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> numberBetweenOneAndTen;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@NotEmpty</span></span><br><span class="line"> <span class="keyword">private</span> String notEmptyString;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Pattern(regexp = "^[0-9]{6}$")</span></span><br><span class="line"> <span class="keyword">private</span> String pinCode;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>위의 Entity를 관리하는 CRUE Repository도 생성한다.</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">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ValidateRepository</span> <span class="keyword">extends</span> <span class="title">CrudRepository</span><<span class="title">InputEntity</span>, <span class="title">Long</span>> </span>{}</span><br></pre></td></tr></table></figure><p>기본적으로 제약 조건을 위반한 <code>InputEntity</code> 객체를 저장할 때 <code>ConstraintViolationException</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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ExtendWith(SpringExtension.class)</span></span><br><span class="line"><span class="meta">@DataJpaTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ValidateRepositoryTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ValidateRepository repository;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> EntityManager entityManager;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">whenInputEntityIsInvalid_thenThrowsException</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> InputEntity inputEntity = <span class="keyword">new</span> InputEntity();</span><br><span class="line"> inputEntity.setNumberBetweenOneAndTen(<span class="number">50</span>);</span><br><span class="line"> inputEntity.setNotEmptyString(<span class="string">""</span>);</span><br><span class="line"> inputEntity.setPinCode(<span class="string">"1234"</span>);</span><br><span class="line"></span><br><span class="line"> assertThrows(ConstraintViolationException.class, () -> {</span><br><span class="line"> repository.save(inputEntity);</span><br><span class="line"> entityManager.flush();</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>Bean Validation은 <code>EntityManager</code>가 flush된 후에 트리거 된다.</strong> Hibernate는 특정 상황에서 <code>EntityManager</code>를 자동으로 flush하지만 integration test의 경우에는 직접 수행해야한다.</p><p>만약 Spring Data Repository에서 Bean Validation을 비활성화하려면 Spring Boot property인 <code>spring.jpa.properties.javax.persistence.validation.mode</code> 값을 <code>none</code>으로 설정하면 된다.</p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Spring-Boot/">Spring Boot</category>
<category domain="https://jongmin92.github.io/tags/Bean-Validation/">Bean Validation</category>
<comments>https://jongmin92.github.io/2019/11/18/Spring/bean-validation-1/#disqus_thread</comments>
</item>
<item>
<title>MySQL - Replication</title>
<link>https://jongmin92.github.io/2019/11/13/Database/mysql-replication/</link>
<guid>https://jongmin92.github.io/2019/11/13/Database/mysql-replication/</guid>
<pubDate>Wed, 13 Nov 2019 13:22:00 GMT</pubDate>
<description><h1 id="Database-Replication"><a href="#Database-Replication" class="headerlink" title="Database Replication"></a>Database Replication</h1><</description>
<content:encoded><![CDATA[<h1 id="Database-Replication"><a href="#Database-Replication" class="headerlink" title="Database Replication"></a>Database Replication</h1><p><strong>여러 대의 DB 서버가 있을 때 각각의 DB 서버가 동일한 데이터를 유지하도록 하는 메커니즘 혹은 기법을 말한다.</strong></p><p>Replication(리플리케이션)을 이용해서 <code>백업</code>과 <code>부하분산</code>같은 목적을 달성할 수 있다.<br>master 서버에 데이터가 기록(쓰기)되고, slave 서버들은 master에 기록된 데이터를 전파받으며 보통 읽기에 사용된다.</p><p>정리하면 한대의 master 서버는 쓰기, 여러대의 slave 서버들은 읽기에 사용되는 것이다.</p><h1 id="백업과-부하분산"><a href="#백업과-부하분산" class="headerlink" title="백업과 부하분산"></a>백업과 부하분산</h1><p><strong>master DB의 데이터가 빠른 속도로 slave DB에 복제되기 때문에 master DB에 문제가 생겼을 때, slave DB를 master DB로 대체해 빠르게 장애를 복구할 수 있다.</strong></p><p>그렇지만 <strong>Replication 자체가 완전한 백업을 의미하는 것은 아니다.</strong> master DB에 의도하지 않은 작업(데이터를 잘못해서 대량으로 삭제)을 수행했을 때 해당 작업이 slave DB에도 전파가 되기 때문에 이러한 것들은 복구가 불가능하다.<br>그러므로 실시간 성의 백업은 Replication이 담당하고, 시간차를 두고 백업이 필요한 것은 보통 스케쥴링을 통하여 시간 간격을 두고 백업을 하는것이 바람직하다.</p><p>읽기와 관련된 부하분산을 할 수 있다는 것이 Replication의 장점이다. 한계라고 하면 쓰기와 관련된 부분인데, 한 대의 master DB에 쓰기가 집중되기 때문에 쓰기 작업에 부하가 많다면 문제가 생길 수 있다.</p><p>간략하게 Replication이 동작하는 방식을 확인해보자. 아래 그림은 master DB와 slave DB가 1대1로 연결되어 있는 구조이다.</p><p><img src="/images/post/2019-11-13/replication_1.jpg" alt="replication-1"></p><ol><li>master DB가 Data의 변경사항을 <code>Binary log</code>에 기록한다.</li><li>master DB가 Slave DB에게 변경이 있음을 통지한다.</li><li>slave DB가 <code>I/O thread</code> 이용해 Binary log를 가져온다.</li><li>slave DB의 <code>Relay log</code>에 변경사항을 기록한다.</li><li>slave DB의 <code>SQL thread</code>를 이용해 변경사항을 반영한다.</li></ol><p>아래 그림은 또 다른 구조의 Replication이다. master DB와 slave DB가 존재하고 slave DB에 다시 slave DB가 존재하는 구조이다.<br>가운데의 slave DB가 master DB 처럼 Binary log를 생성해서 다른 slave에게 전달하는 것이다. 이 경우 가장 마지막에 있는 slave DB가 직접 master DB에 접근하는 것이 아니기 때문에 master DB의 부담을 줄일 수 있다.<br>또한 가운데의 slave DB 역시 master DB 처럼 Binary log를 생성하고 있기 때문에 master DB에 문제가 생겼을 때, 빠르고 쉽게 master DB로 대체가 가능하다.</p><p><img src="/images/post/2019-11-13/replication_2.jpg" alt="replication-2"></p><h1 id="Replication-실습"><a href="#Replication-실습" class="headerlink" title="Replication 실습"></a>Replication 실습</h1><p>그럼 이제 간단하게 MySQL Replication을 구축해보자. 여러 대의 서버에 MySQL을 설치하고 준비하는 것은 번거롭기 때문에 간편하게 도커를 이용해서 진행해보자.</p><h2 id="MySQL-master-컨테이너-실행하기"><a href="#MySQL-master-컨테이너-실행하기" class="headerlink" title="MySQL master 컨테이너 실행하기"></a>MySQL master 컨테이너 실행하기</h2><p>먼저 master DB 컨테이너를 생성하고 접속한다.</p><figure class="highlight shell"><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">$</span><span class="bash"> docker run --name mysql-master -e MYSQL_ROOT_PASSWORD=asdf1234 -d mysql</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-master /bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> apt-get update; apt-get install vim -y</span></span><br></pre></td></tr></table></figure><p>설치한 vim을 이용해 MySQL 설정 파일인 <code>/etc/mysql/my.cnf</code> 파일에 다음의 내용을 추가한다.</p><figure class="highlight text"><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">[mysqld]</span><br><span class="line">log-bin=mysql-bin </span><br><span class="line">server-id=1</span><br></pre></td></tr></table></figure><blockquote><ul><li><strong>log-bin</strong>: 업데이트되는 모든 query들을 Binary log 파일에 기록한다는 의미이다.<br>기본적으로 Binary log 파일은 MySQL의 data directory인 /var/lib/mysql/ 에 호스트명-bin.000001, 호스트명-bin.000002 형태로 생성된다.<br>이때, log-bin 설정을 변경하면 Binary log 파일의 경로와 파일명의 접두어를 변경할 수 있다. log-bin=mysql 이라 설정하면 mysql-bin.000001, mysql-bin.000002 형태로 Binary log 파일이 생성된다.</li><li><strong>server-id</strong>: Replication 설정에서 서버를 식별하기 위한 고유 ID값이다. master, slave 각각 다르게 설정해야 한다.</li></ul></blockquote><p>도커 컨테이너(MySQL)를 재시작해서 변경된 설정 파일을 반영한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker restart mysql-master</span></span><br></pre></td></tr></table></figure><p>변경한 설정이 잘 적용되었는지 확인해보자.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-master /bin/bash</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span> </span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SHOW MASTER STATUS\G</span></span><br><span class="line"></span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line"> File: mysql-bin.000001</span><br><span class="line"> Position: 155</span><br><span class="line"> Binlog_Do_DB:</span><br><span class="line"> Binlog_Ignore_DB:</span><br><span class="line">Executed_Gtid_Set:</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="master-DB에-User-생성하기"><a href="#master-DB에-User-생성하기" class="headerlink" title="master DB에 User 생성하기"></a>master DB에 User 생성하기</h2><p>slave DB에서 접근할 수 있도록 master DB에 User 계정을 생성하고 <code>REPLICATION SLAVE</code> 권한을 부여한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql></span><span class="bash"> CREATE USER <span class="string">'repl'</span>@<span class="string">'%'</span> IDENTIFIED BY <span class="string">'replpw'</span>;</span></span><br><span class="line">Query OK, 0 rows affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> ALTER USER <span class="string">'repl'</span>@<span class="string">'%'</span> IDENTIFIED WITH mysql_native_password BY <span class="string">'replpw'</span>;</span></span><br><span class="line">Query OK, 0 rows affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> GRANT REPLICATION SLAVE ON *.* TO <span class="string">'repl'</span>@<span class="string">'%'</span>;</span></span><br><span class="line">Query OK, 0 rows affected (0.01 sec)</span><br></pre></td></tr></table></figure><p>User가 생성되었는지 User 테이블을 확인한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql></span><span class="bash"> USE mysql;</span></span><br><span class="line">Reading table information for completion of table and column names</span><br><span class="line">You can turn off this feature to get a quicker startup with -A</span><br><span class="line"></span><br><span class="line">Database changed</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SELECT user, host FROM user;</span></span><br><span class="line">+------------------+-----------+</span><br><span class="line">| user | host |</span><br><span class="line">+------------------+-----------+</span><br><span class="line">| repl | % |</span><br><span class="line">| root | % |</span><br><span class="line">| mysql.infoschema | localhost |</span><br><span class="line">| mysql.session | localhost |</span><br><span class="line">| mysql.sys | localhost |</span><br><span class="line">| root | localhost |</span><br><span class="line">+------------------+-----------+</span><br><span class="line">6 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>다음으로, Replication 테스트를 위한 DB와 테이블을 생성한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql></span><span class="bash"> CREATE DATABASE testdb;</span></span><br><span class="line">Query OK, 1 row affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> USE testdb;</span></span><br><span class="line">Database changed</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> CREATE TABLE testtable ( text varchar(20) );</span></span><br><span class="line">Query OK, 0 rows affected (0.03 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> DESC testtable;</span></span><br><span class="line">+-------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field | Type | Null | Key | Default | Extra |</span><br><span class="line">+-------+-------------+------+-----+---------+-------+</span><br><span class="line">| text | varchar(20) | YES | | NULL | |</span><br><span class="line">+-------+-------------+------+-----+---------+-------+</span><br><span class="line">1 row in set (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> INSERT INTO testtable VALUES (<span class="string">'test row'</span>);</span></span><br><span class="line">Query OK, 1 row affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SELECT * from testtable;</span></span><br><span class="line">+----------+</span><br><span class="line">| text |</span><br><span class="line">+----------+</span><br><span class="line">| test row |</span><br><span class="line">+----------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="master-DB-dump"><a href="#master-DB-dump" class="headerlink" title="master DB dump"></a>master DB dump</h2><p>slave DB에서 master DB를 연결하기 전에 master DB의 현재 DB 상태(table과 data)를 slave DB에 그대로 반영하기 위해 dump한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-master /bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysqldump -u root -p testdb > dump.sql</span></span><br></pre></td></tr></table></figure><p>dump된 파일을 slave DB 컨테이너에 옮기기 위해 먼저 로컬 PC로 복사한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker cp mysql-master:dump.sql .</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> cat dump.sql</span></span><br></pre></td></tr></table></figure><h2 id="MySQL-slave-컨테이너-실행하기"><a href="#MySQL-slave-컨테이너-실행하기" class="headerlink" title="MySQL slave 컨테이너 실행하기"></a>MySQL slave 컨테이너 실행하기</h2><p>slave DB 컨테이너를 생성하고 접속한다.</p><figure class="highlight shell"><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">$</span><span class="bash"> docker run --name mysql-slave --link mysql-master -e MYSQL_ROOT_PASSWORD=asdf1234 -d mysql</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-slave /bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> apt-get update; apt-get install vim -y</span></span><br></pre></td></tr></table></figure><p>설치한 vim을 이용해 MySQL 설정 파일인 <code>/etc/mysql/my.cnf</code> 파일에 다음의 내용을 추가한다.<br>slave 서버를 여러 대로 구축하고자 할 때에 각 slave 서버의 server-id는 각각 달라야 한다는 것에 주의하자. (2^32-1 까지 가능하다.)</p><figure class="highlight text"><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">[mysqld]</span><br><span class="line">log-bin=mysql-bin </span><br><span class="line">server-id=2</span><br></pre></td></tr></table></figure><p>도커 컨테이너(MySQL)를 재시작해서 변경된 설정 파일을 반영한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker restart mysql-slave</span></span><br></pre></td></tr></table></figure><h2 id="slave-DB에-dump-파일-적용"><a href="#slave-DB에-dump-파일-적용" class="headerlink" title="slave DB에 dump 파일 적용"></a>slave DB에 dump 파일 적용</h2><p>로컬 PC로 복사한 master DB의 dump 파일을 slave DB로 옮긴 후 반영한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker cp dump.sql mysql-slave:.</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-slave /bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> CREATE DATABASE testdb;</span></span><br><span class="line">Query OK, 1 row affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> <span class="built_in">exit</span></span></span><br><span class="line">Bye</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p testdb < dump.sql</span></span><br></pre></td></tr></table></figure><p>다시 mysql에 접속해 <code>testdb</code> DB에 <code>testtable</code> 테이블과 데이터가 생성되어 있다면 정상적으로 dump 파일이 적용된 것이다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql></span><span class="bash"> USE testdb;</span></span><br><span class="line">Reading table information for completion of table and column names</span><br><span class="line">You can turn off this feature to get a quicker startup with -A</span><br><span class="line"></span><br><span class="line">Database changed</span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SHOW TABLES;</span></span><br><span class="line">+------------------+</span><br><span class="line">| Tables_in_testdb |</span><br><span class="line">+------------------+</span><br><span class="line">| testtable |</span><br><span class="line">+------------------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="slave-DB에서-master-DB-연동하기"><a href="#slave-DB에서-master-DB-연동하기" class="headerlink" title="slave DB에서 master DB 연동하기"></a>slave DB에서 master DB 연동하기</h2><p>이제 마지막으로 slave 서버에서 master 서버와 연동하는 작업만 하면 된다.</p><p>그 전에 master DB의 mysql에 한번 더 접속하여 Binary log 파일의 현재 상태를 읽어야 한다. 이 <strong>Binary log 파일을 통해 master와 slave의 DB가 동기화되므로 반드시 동일한 로그의 위치를 서로 참조하고 있어야 한다.</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-master /bin/bash</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span> </span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SHOW MASTER STATUS\G</span> </span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line"> File: mysql-bin.000001</span><br><span class="line"> Position: 1949</span><br><span class="line"> Binlog_Do_DB:</span><br><span class="line"> Binlog_Ignore_DB:</span><br><span class="line">Executed_Gtid_Set:</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>출력된 결과에서 File, Position 필드의 값을 기억하도록 한다.<br><strong><code>File</code> 은 현재 바이너리 로그 파일명이고, <code>Position</code> 은 현재 로그의 위치를 나타낸다.</strong> 앞서 DB와 테이블을 생성한 query가 추가됐으므로 이전에 <strong>SHOW MASTER STATUS\G</strong> 를 실행했을 때보다 Position 값이 증가했음을 볼 수 있다.</p><p>이제 slave 서버의 mysql에 접속하여 master 서버와의 연결에 필요한 변수들을 적절히 설정해주어야 한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-slave /bin/bash</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span> </span><br><span class="line"><span class="meta">mysql></span><span class="bash"> CHANGE MASTER TO MASTER_HOST=<span class="string">'mysql-master'</span>, MASTER_USER=<span class="string">'repl'</span>, MASTER_PASSWORD=<span class="string">'replpw'</span>, MASTER_LOG_FILE=<span class="string">'mysql-bin.000001'</span>, MASTER_LOG_POS=1949;</span></span><br><span class="line">Query OK, 0 rows affected, 2 warnings (0.03 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> START SLAVE;</span></span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br></pre></td></tr></table></figure><blockquote><ul><li>MASTER_HOST : master 서버의 호스트명</li><li>MASTER_USER : master 서버의 mysql에서 REPLICATION SLAVE 권한을 가진 User 계정의 이름</li><li>MASTER_PASSWORD : master 서버의 mysql에서 REPLICATION SLAVE 권한을 가진 User 계정의 비밀번호</li><li>MASTER_LOG_FILE : master 서버의 바이너리 로그 파일명</li><li>MASTER_LOG_POS : master 서버의 현재 로그의 위치</li></ul></blockquote><p>아래의 명령어를 실행해 slave의 상태를 확인해보자.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">mysql></span><span class="bash"> SHOW SLAVE STATUS\G</span></span><br></pre></td></tr></table></figure><h2 id="Replication-테스트"><a href="#Replication-테스트" class="headerlink" title="Replication 테스트"></a>Replication 테스트</h2><p>문제 없이 Replication 설정이 완료되었다면 마지막으로 실제 잘 동작하는지 확인해보자.<br>master DB에서 데이터를 생성하고, slave DB에 복제되어 데이터가 조회되는지 확인한다.</p><p>master DB에서 데이터를 생성한다.</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-master /bin/bash</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span></span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> USE testdb;</span></span><br><span class="line">Reading table information for completion of table and column names</span><br><span class="line">You can turn off this feature to get a quicker startup with -A</span><br><span class="line"></span><br><span class="line">Database changed</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> INSERT INTO testtable VALUES (<span class="string">'test row2'</span>);</span></span><br><span class="line">Query OK, 1 row affected (0.01 sec)</span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SELECT * FROM testtable;</span></span><br><span class="line">+-----------+</span><br><span class="line">| text |</span><br><span class="line">+-----------+</span><br><span class="line">| test row |</span><br><span class="line">| test row2 |</span><br><span class="line">+-----------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>slave DB에서 데이터를 조회한다.</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker <span class="built_in">exec</span> -it mysql-slave /bin/bash</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mysql -u root -p</span></span><br><span class="line"></span><br><span class="line"><span class="meta">mysql></span><span class="bash"> SELECT * from testtable;</span></span><br><span class="line">+-----------+</span><br><span class="line">| text |</span><br><span class="line">+-----------+</span><br><span class="line">| test row |</span><br><span class="line">| test row2 |</span><br><span class="line">+-----------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Database/">Database</category>
<category domain="https://jongmin92.github.io/tags/MySQL/">MySQL</category>
<category domain="https://jongmin92.github.io/tags/Replication/">Replication</category>
<category domain="https://jongmin92.github.io/tags/MMM/">MMM</category>
<comments>https://jongmin92.github.io/2019/11/13/Database/mysql-replication/#disqus_thread</comments>
</item>
<item>
<title>Reactive Streams (3)</title>
<link>https://jongmin92.github.io/2019/11/10/Java/reactive-3/</link>
<guid>https://jongmin92.github.io/2019/11/10/Java/reactive-3/</guid>
<pubDate>Sun, 10 Nov 2019 12:01:00 GMT</pubDate>
<description><p><strong>“<code>Reactive Streams</code>란 non-blocking과 back pressure를 이용한 asynchronous 스트림 처리의 표준이다.”</strong> 라고 지난 글에서 이야기 했다.</p>
<p>이번</description>
<content:encoded><![CDATA[<p><strong>“<code>Reactive Streams</code>란 non-blocking과 back pressure를 이용한 asynchronous 스트림 처리의 표준이다.”</strong> 라고 지난 글에서 이야기 했다.</p><p>이번에는 <code>asynchronous(비동기 처리)</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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><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">SchedulerEx</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"> Publisher<Integer> pub = <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> sub.onSubscribe(<span class="keyword">new</span> Subscription() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">(<span class="keyword">long</span> n)</span> </span>{</span><br><span class="line"> sub.onNext(<span class="number">1</span>);</span><br><span class="line"> sub.onNext(<span class="number">2</span>);</span><br><span class="line"> sub.onNext(<span class="number">3</span>);</span><br><span class="line"> sub.onNext(<span class="number">4</span>);</span><br><span class="line"> sub.onNext(<span class="number">5</span>);</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> Subscriber<Integer> sub = <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onSubscribe"</span>);</span><br><span class="line"> s.request(Long.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer integer)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onNext: {}"</span>, integer);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onError"</span>, t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onComplete"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> pub.subscribe(sub);</span><br><span class="line"> </span><br><span class="line"> log.info(<span class="string">"Exit)"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- 실행결과</span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.475</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onSubscribe</span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.480</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - request: <span class="number">9223372036854775807</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">2</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">3</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">4</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">5</span></span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - onComplete</span><br><span class="line"><span class="number">23</span>:<span class="number">27</span>:<span class="number">45.484</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - exit</span><br></pre></td></tr></table></figure><p>subscriber를 publisher에 등록(subscribe)하고 처리(onSubscribe -> request -> next)가 끝나면 exit 로그를 마지막으로 종료된다. 이때 subscriber와 publisher의 진행은 모두 main 스레드에서 진행된다.<br>즉, subscriber를 등록 후 publisher가 데이터를 push하고 처리할 때까지 main 스레드를 붙잡고 있게 된다.<br>만약 publisher 혹은 subscriber의 처리가 지연된다면 main 스레드는 더욱 오래 사용해야 할 것이다.</p><p>결국 publisher에 subscriber를 등록하면 별도의 스레드에서 진행하고 main 스레드는 계속해서 다른 작업을 진행하기를 원하는 것이다.</p><h1 id="publishOn"><a href="#publishOn" class="headerlink" title="publishOn"></a>publishOn</h1><p>먼저 publisher가 main 스레드가 아닌 별도의 스레드에서 동작하도록 만들어보자.</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"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">publishOn</span><span class="params">(Publisher<Integer> pub)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> {</span><br><span class="line"> pub.subscribe(sub);</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>파라미터로 전달받은 publisher의 subscribe 메서드를 별도의 스레드에서 실행하도록 하는 publisher를 새로 만들어서 반환한다. publishOn 메서드를 기존의 publisher에 적용해 실행하면 다음과 같이 실행결과를 확인할 수 있다.</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">- 실행결과</span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.820</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.820</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onSubscribe</span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.824</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - request: <span class="number">9223372036854775807</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">2</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">3</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">4</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">5</span></span><br><span class="line"><span class="number">23</span>:<span class="number">55</span>:<span class="number">57.827</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onComplete</span><br></pre></td></tr></table></figure><p>main 스레드는 바로 해제되어 이후의 일을 진행할 수 있게 되었고 onSubscribe 이후의 처리는 모두 별도의 스레드에서 진행된다.</p><p>그러나 아직 “빠른 프로듀서”와 “느린 컨슈머”의 문제가 남아있다. publisher가 데이터를 빠르게 생산하지만 subscriber의 onNext에서 데이터를 소비하는 작업에 시간이 오래 걸리는 경우인 것이다.<br>이때는 subscriber 또한 별도의 스레드에서 onNext 처리를 하도록 함으로써 해결할 수 있다.</p><h1 id="subscribeOn"><a href="#subscribeOn" class="headerlink" title="subscribeOn"></a>subscribeOn</h1><p>subscriber도 별도의 스레드에서 동작하도록 만들어보자.</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">subscriberOn</span><span class="params">(Subscriber<Integer> sub)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onSubscribe(s));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onNext(i));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onError(t));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onComplete());</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>파라미터로 전달받은 subscriber의 onSubscribe, onNext, onError, onComplete 메서드를 별도의 스레드에서 실행하도록 하는 subscriber를 새로 만들어서 반환한다. subscriberOn 메서드를 기존의 subscriber에 적용해 실행하면 다음과 같이 실행결과를 확인할 수 있다.</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">- 실행결과</span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.604</span> [main] INFO com.jongmin.reactive.practice.SchedulerEx - exit</span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.604</span> [pool-<span class="number">2</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onSubscribe</span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.607</span> [pool-<span class="number">2</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - request: <span class="number">9223372036854775807</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.609</span> [pool-<span class="number">3</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">1</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.609</span> [pool-<span class="number">4</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">2</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.609</span> [pool-<span class="number">5</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">3</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.609</span> [pool-<span class="number">6</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">4</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.610</span> [pool-<span class="number">7</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onNext: <span class="number">5</span></span><br><span class="line"><span class="number">00</span>:<span class="number">14</span>:<span class="number">53.610</span> [pool-<span class="number">8</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.SchedulerEx - onComplete</span><br></pre></td></tr></table></figure><p>이제는 main 스레드는 subscriber를 publisher에 등록(subscribe)까지만 하고 그 이후의 작업은 publisher와 subscriber 모두 별도의 스레드에서 동작하게 되었다.</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><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><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">SchedulerEx</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"> Publisher<Integer> pub = <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> sub.onSubscribe(<span class="keyword">new</span> Subscription() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">(<span class="keyword">long</span> n)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"request: {}"</span>, n);</span><br><span class="line"> sub.onNext(<span class="number">1</span>);</span><br><span class="line"> sub.onNext(<span class="number">2</span>);</span><br><span class="line"> sub.onNext(<span class="number">3</span>);</span><br><span class="line"> sub.onNext(<span class="number">4</span>);</span><br><span class="line"> sub.onNext(<span class="number">5</span>);</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> Subscriber<Integer> sub = <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onSubscribe"</span>);</span><br><span class="line"> s.request(Long.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer integer)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onNext: {}"</span>, integer);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onError"</span>, t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onComplete"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> publishOn(pub).subscribe(subscriberOn(sub));</span><br><span class="line"></span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">publishOn</span><span class="params">(Publisher<Integer> pub)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> {</span><br><span class="line"> pub.subscribe(sub);</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="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">subscriberOn</span><span class="params">(Subscriber<Integer> sub)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onSubscribe(s));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onNext(i));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onError(t));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> Executors.newSingleThreadExecutor().execute(() -> sub.onComplete());</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>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Reactive/">Reactive</category>
<comments>https://jongmin92.github.io/2019/11/10/Java/reactive-3/#disqus_thread</comments>
</item>
<item>
<title>Reactive Streams (2)</title>
<link>https://jongmin92.github.io/2019/11/07/Java/reactive-2/</link>
<guid>https://jongmin92.github.io/2019/11/07/Java/reactive-2/</guid>
<pubDate>Thu, 07 Nov 2019 13:17:00 GMT</pubDate>
<description><p>지난번 Reactive Streams API를 구현한 예제를 바탕으로 간단한 Operator를 만들어보자.</p>
<p>Operator라 함은 Stream의 <a href="https://docs.oracle.com/javase/8/docs/ap</description>
<content:encoded><![CDATA[<p>지난번 Reactive Streams API를 구현한 예제를 바탕으로 간단한 Operator를 만들어보자.</p><p>Operator라 함은 Stream의 <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#map-java.util.function.Function-">map</a>연산 처럼 Publisher가 제공하는 data를 가공할 수 있도록 하는 것이다.</p><p>먼저 간단한 Publisher와 Subscriber 코드이다.</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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><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">PubSub</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"> Publisher<Integer> pub = iterPub(Stream.iterate(<span class="number">1</span>, a -> a + <span class="number">1</span>)</span><br><span class="line"> .limit(<span class="number">5</span>)</span><br><span class="line"> .collect(Collectors.toList()));</span><br><span class="line"> pub.subscribe(logSub());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">iterPub</span><span class="params">(List<Integer> iter)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber sub)</span> </span>{</span><br><span class="line"> sub.onSubscribe(<span class="keyword">new</span> Subscription() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">(<span class="keyword">long</span> n)</span> </span>{</span><br><span class="line"> iter.forEach(i -> sub.onNext(i));</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">logSub</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onSubscribe"</span>);</span><br><span class="line"> s.request(Long.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onNext: {}"</span>, i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onError"</span>, t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onComplete"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- 실행결과</span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.255</span> [main] INFO com.jongmin.reactive.practice.PubSub - onSubscribe</span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.260</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.262</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">2</span></span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.262</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">3</span></span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.262</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">4</span></span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.262</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">5</span></span><br><span class="line"><span class="number">23</span>:<span class="number">32</span>:<span class="number">28.262</span> [main] INFO com.jongmin.reactive.practice.PubSub - onComplete</span><br></pre></td></tr></table></figure><h1 id="Operator"><a href="#Operator" class="headerlink" title="Operator"></a>Operator</h1><p><strong>Publisher -> [Data1] -> <code>Operator</code> -> [Data2] -> Subscriber</strong></p><p>위와 같이 Data1을 Data2로 변환하는 Operator를 만들어보자.</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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><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">PubSub</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"> Publisher<Integer> pub = iterPub(Stream.iterate(<span class="number">1</span>, a -> a + <span class="number">1</span>)</span><br><span class="line"> .limit(<span class="number">5</span>)</span><br><span class="line"> .collect(Collectors.toList()));</span><br><span class="line"> Publisher<Integer> mapPub = mapPub(pub, s -> s * <span class="number">10</span>);</span><br><span class="line"> <span class="comment">// iterrPub -> [Data1] -> mapPub -> [Data2] -> logSub</span></span><br><span class="line"> mapPub.subscribe(logSub());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">mapPub</span><span class="params">(Publisher<Integer> pub, Function<Integer, Integer> f)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> pub.subscribe(<span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> sub.onSubscribe(s);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> sub.onNext(f.apply(i));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> sub.onError(t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">iterPub</span><span class="params">(List<Integer> iter)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">logSub</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- 실행결과</span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.758</span> [main] INFO com.jongmin.reactive.practice.PubSub - onSubscribe</span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.764</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">10</span></span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.767</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">20</span></span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.767</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">30</span></span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.767</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">40</span></span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.767</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">50</span></span><br><span class="line"><span class="number">23</span>:<span class="number">45</span>:<span class="number">19.767</span> [main] INFO com.jongmin.reactive.practice.PubSub - onComplete</span><br></pre></td></tr></table></figure><p>mapPub 메서드가 추가되었다. Data를 제공하는 Publisher와 가공에 사용할 Function을 받아 Operator(새로운 Publisher)를 반환한다.</p><p>실제 하는 일은 단순하다. 기존 Publisher와 Subscriber를 이어준다.</p><p>Operator가 기존 Publisher를 subscribe하고, 받게되는 Subscription을 기존 Subscriber에게 전달한다. </p><h1 id="DelegateSub"><a href="#DelegateSub" class="headerlink" title="DelegateSub"></a>DelegateSub</h1><p>Operator가 하는 일은 기존 Publisher와 Subscriber를 이어주면서, onNext 부분에서 전달받은 Function을 적용해주는 것 뿐이다.</p><p>onNext를 제외하고는 Operator 마다 코드가 반복될 수 있기 때문에 해당 부분을 DelegateSub으로 분리해보자.</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></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">DelegateSub</span> <span class="keyword">implements</span> <span class="title">Subscriber</span><<span class="title">Integer</span>> </span>{</span><br><span class="line"></span><br><span class="line"> Subscriber sub;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DelegateSub</span><span class="params">(Subscriber sub)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.sub = sub;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span> </span>{</span><br><span class="line"> sub.onSubscribe(s);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> sub.onNext(i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> sub.onError(t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>DelegateSub을 사용해서 기존 코드를 다음과 같이 수정할 수 있다. 필요한 onNext 메서드만 오버라이딩해서 사용한다.</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></pre></td><td class="code"><pre><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">PubSub2</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"> Publisher<Integer> pub = iterPub(Stream.iterate(<span class="number">1</span>, a -> a + <span class="number">1</span>)</span><br><span class="line"> .limit(<span class="number">5</span>)</span><br><span class="line"> .collect(Collectors.toList()));</span><br><span class="line"> Publisher<Integer> mapPub = mapPub(pub, s -> s * <span class="number">10</span>);</span><br><span class="line"> mapPub.subscribe(logSub());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">mapPub</span><span class="params">(Publisher<Integer> pub, Function<Integer, Integer> f)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> pub.subscribe(<span class="keyword">new</span> DelegateSub(sub) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> sub.onNext(f.apply(i));</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">iterPub</span><span class="params">(List<Integer> iter)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">logSub</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="SumPub"><a href="#SumPub" class="headerlink" title="SumPub"></a>SumPub</h1><p>이번에는 Publisher로부터 전달받은 Data를 전부 더하는 sum operation을 만들어보자.</p><p>기존 Publisher와 Subscriber를 onNext로 이어주지 않고, onComplete이 호출되었을 때, sum 값을 onNext로 전달한 뒤 onComplete을 호출해 종료한다.</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><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><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">PubSub2</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"> Publisher<Integer> pub = iterPub(Stream.iterate(<span class="number">1</span>, a -> a + <span class="number">1</span>)</span><br><span class="line"> .limit(<span class="number">5</span>)</span><br><span class="line"> .collect(Collectors.toList()));</span><br><span class="line"> Publisher<Integer> sumPub = sumPub(pub);</span><br><span class="line"> sumPub.subscribe(logSub());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">sumPub</span><span class="params">(Publisher<Integer> pub)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Publisher<Integer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> Integer> sub)</span> </span>{</span><br><span class="line"> pub.subscribe(<span class="keyword">new</span> DelegateSub(sub) {</span><br><span class="line"> <span class="keyword">int</span> sum = <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> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer i)</span> </span>{</span><br><span class="line"> sum += i;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> sub.onNext(sum);</span><br><span class="line"> sub.onComplete();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Publisher<Integer> <span class="title">iterPub</span><span class="params">(List<Integer> iter)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Subscriber<Integer> <span class="title">logSub</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">- 실행결과</span><br><span class="line"><span class="number">00</span>:<span class="number">30</span>:<span class="number">48.643</span> [main] INFO com.jongmin.reactive.practice.PubSub2 - onSubscribe</span><br><span class="line"><span class="number">00</span>:<span class="number">30</span>:<span class="number">48.648</span> [main] INFO com.jongmin.reactive.practice.PubSub2 - onNext: <span class="number">15</span></span><br><span class="line"><span class="number">00</span>:<span class="number">30</span>:<span class="number">48.650</span> [main] INFO com.jongmin.reactive.practice.PubSub2 - onComplete</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Reactive/">Reactive</category>
<comments>https://jongmin92.github.io/2019/11/07/Java/reactive-2/#disqus_thread</comments>
</item>
<item>
<title>Reactive Streams (1)</title>
<link>https://jongmin92.github.io/2019/11/05/Java/reactive-1/</link>
<guid>https://jongmin92.github.io/2019/11/05/Java/reactive-1/</guid>
<pubDate>Mon, 04 Nov 2019 16:03:00 GMT</pubDate>
<description><h1 id="Reactive-Streams-란"><a href="#Reactive-Streams-란" class="headerlink" title="Reactive Streams 란?"></a>Reactive Streams 란?</h1><p><a h</description>
<content:encoded><![CDATA[<h1 id="Reactive-Streams-란"><a href="#Reactive-Streams-란" class="headerlink" title="Reactive Streams 란?"></a>Reactive Streams 란?</h1><p><a href="http://www.reactive-streams.org/">reactive-streams.org</a> 에서는 <code>Reactive Streams</code>를 다음과 같이 정의하고 있다.</p><blockquote><p>Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. </p></blockquote><p><strong><code>Reactive Streams</code>란 non-blocking과 back pressure를 이용한 asynchronous 스트림 처리의 표준이다.</strong></p><p>중요한 키워드가 여러개 등장 했는데, 먼저 back pressure에 대해서 알아보자.</p><h1 id="Back-Pressure"><a href="#Back-Pressure" class="headerlink" title="Back Pressure"></a>Back Pressure</h1><p><code>Back Pressure</code>는 Reactive Streams에서 가장 중요한 요소라고 할 수 있다. Back Pressure가 등장하게 된 배경을 이해하기 위해서 먼저 옵저버 패턴을 이해하고 옵저버 패턴이 갖고 있는 문제점을 인식할 수 있어야한다.</p><h2 id="Observable-amp-Observer"><a href="#Observable-amp-Observer" class="headerlink" title="Observable & Observer"></a>Observable & Observer</h2><blockquote><p><strong>옵저버 패턴(observer pattern)</strong> 은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.</p><p><a href="https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4">옵저버 패턴 - 위키백과</a></p></blockquote><p>예를 들면, 안드로이드에서 Button이 클릭되었을 때 실행할 함수를 onclicklistener에 추가하는데 이와 같이 이벤트 핸들링 처리를 위해 사용되는 패턴이다. 이 패턴에는 Observable과 Observer가 등장한다.</p><ul><li><strong>Osbservable</strong>: 등록된 Observer들을 관리하며, 새로운 데이터(이벤트)가 들어오면 등록된 Observer에게 데이터를 전달한다. 데이터를 생성해서 전달하기 때문에 <strong>Publisher</strong>(발행)라고 부른다.</li><li><strong>Observer</strong>: Observable로 부터 데이터(이벤트)를 받을 수 있다. 데이터를 전달 받기 때문에 <strong>Subscriber</strong>(구독)라고 부른다.</li></ul><p>Java는 이미 JDK 1.0 부터 옵저버 패턴을 쉽게 구현할 수 있는 인터페이스를 제공하고 있다. 아래의 코드는 JDK 1.0에 포함된 Observable과 Observer 인터페이스를 사용해 만든 간단한 예시 코드이다.</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></pre></td><td class="code"><pre><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">Ob</span> </span>{</span><br><span class="line"> <span class="comment">// Source -> Event/Data -> Observer</span></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">IntObservable</span> <span class="keyword">extends</span> <span class="title">Observable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i <= <span class="number">10</span>; i++) {</span><br><span class="line"> setChanged();</span><br><span class="line"> notifyObservers(i); <span class="comment">// push</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span 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"> Observer ob = <span class="keyword">new</span> Observer() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(Observable o, Object arg)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"{}"</span>, arg);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> IntObservable io = <span class="keyword">new</span> IntObservable();</span><br><span class="line"> io.addObserver(ob);</span><br><span class="line"></span><br><span class="line"> ExecutorService es = Executors.newSingleThreadExecutor();</span><br><span class="line"> es.execute(io);</span><br><span class="line"></span><br><span class="line"> log.info(<span class="string">"EXIT"</span>);</span><br><span class="line"> es.shutdown();</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="number">01</span>:<span class="number">47</span>:<span class="number">30.715</span> [main] INFO com.jongmin.reactive.practice.Ob - EXIT</span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.715</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">1</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">2</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">3</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">4</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">5</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">6</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">7</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">8</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">9</span></span><br><span class="line"><span class="number">01</span>:<span class="number">47</span>:<span class="number">30.719</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.jongmin.reactive.practice.Ob - <span class="number">10</span></span><br></pre></td></tr></table></figure><h2 id="문제점"><a href="#문제점" class="headerlink" title="문제점"></a>문제점</h2><p>옵저버 패턴에서는 Publisher(Observable)이 Subscriber(Observer)에게 데이터(이벤트)를 Push(notifyObservers)하는 방식으로 전달한다. 이때, Publisher는 Subscriber의 상태에 상관없이 데이터를 전달하는데만 집중한다.</p><p>만약, Subscriber는 1초에 10개의 데이터를 처리할 수 있는데 Publisher가 1초에 20개의 데이터를 전달(Push)한다면 어떤 문제가 발생할까? 다음과 같은 문제가 발생할 수 있다.</p><ul><li>Subscriber에 별도의 queue(버퍼)를 두고 처리하지 않고 대기중인 데이터를 저장할 수 있다.</li><li>하지만, queue의 사용 가능한 공간도 전부 금방 소모될 것이다.</li><li>queue의 크기를 넘어가게 되면 데이터는 소실될 것이다.</li><li>queue의 크기를 너무 크게 생성하면 OOM(Out Of Memory) 문제가 발생할 수 있다.</li></ul><h2 id="해결-방법"><a href="#해결-방법" class="headerlink" title="해결 방법"></a>해결 방법</h2><p>Observable과 Observer의 문제를 어떻게 해결할 수 있을까? Publisher가 Subscriber에게 데이터를 Push 하던 기존의 방식을 Subscriber가 Publisher에게 자신이 <strong>처리할 수 있는 만큼의 데이터를 Request하는 방식</strong>으로 해결할 수 있다. 필요한(처리할 수 있는) 만큼만 요청해서 Pull하는 것이다. 데이터 요청의 크기가 Subscriber에 의해서 결정되는 것이다. 이를 <code>dynamic pull</code> 방식이라 부르며, Back Pressure의 기본 원리이다.</p><h1 id="Reactive-Streams-API"><a href="#Reactive-Streams-API" class="headerlink" title="Reactive Streams API"></a>Reactive Streams API</h1><p><strong>Reactive Streams는 표준화된 API이다.</strong> 2013년 netflix, pivotal, lightbend의 엔지니어들에 의해서 처음 시작되어, 2015 4월에 JVM에 대한 1.0.0 스펙이 릴리즈 되었다.<br>Java 9부터는 reactive streams이 java.util.concurrent의 패키지 아래 <a href="https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html">Flow</a>라는 형태로 JDK에 포함되었다. 기존에 reactive streams가 가진 API와 스펙, pull방식을 사용하는 원칙을 그대로 수용하였다.</p><p>아래는 Reactive Streams API이다.</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Publisher</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber<? <span class="keyword">super</span> T> s)</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Subscription</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">(<span class="keyword">long</span> n)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Subscriber</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription s)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(T t)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>실제로 보면 굉장히 간단한 API들의 조합으로 이루어져 있다.</p><ul><li><strong>Publisher</strong>: Subscriber를 받아들이는 <code>subscribe</code> 메서드 하나만 갖는다.</li><li><strong>Subscriber</strong>: 데이터를 받아 처리할 수 있는 <code>onNext</code>, 에러를 처리하는 <code>onError</code>, 모든 데이터를 받아 완료되었을 때는 <code>onComplete</code>, 그리고 Publisher로부터 Subscription을 전달 받는 <code>onSubscribe</code> 메서드로 이루어진다.</li><li><strong>Subscription</strong>: n개의 데이터를 요청하는 <code>request</code>와 구독을 취소하는 <code>cancel</code>을 갖는다.</li></ul><p>전체적인 흐름은 다음과 같다.<br><img src="/images/post/2019-11-05/reactive-streams.png" alt="reactive streams"></p><ol><li>Subscriber가 Publisher에게 구독을 요청한다.</li><li>Publisher는 Subscriber의 onSubscribe 메서드를 통해 Subscription을 전달한다.</li><li>Subscriber는 Publisher에게 직접 데이터를 요청하지 않고 Subscription을 통해 요청한다.</li><li>Publisher는 Subscription을 통해 onNext에 데이터를 전달하고 완료되면 onComplete, 에러가 발생하면 onError에 전달한다.</li></ol><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><p>마지막으로 Reactive Streams API를 간단하게 구현해 테스트 해보자.</p><blockquote><p>Reactive Streams API의 Interface는 간단해 보이지만 이를 구현한 구현체는 Reactive Streams Specification을 만족해야만 한다. 구현체가 Specification을 만족하는지는 Reactive Streams TCK(Technology Compatibility Kit)라는 도구를 이용해 검증할 수 있다.</p></blockquote><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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><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">PubSub</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"> Iterable<Integer> iter = Arrays.asList(<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><br><span class="line"></span><br><span class="line"> Publisher p = <span class="keyword">new</span> Publisher() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(Subscriber subscriber)</span> </span>{</span><br><span class="line"> Iterator<Integer> it = iter.iterator();</span><br><span class="line"></span><br><span class="line"> subscriber.onSubscribe(<span class="keyword">new</span> Subscription() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">(<span class="keyword">long</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">while</span>(n-- > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (it.hasNext()) {</span><br><span class="line"> subscriber.onNext(it.next());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> subscriber.onComplete();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"cancel"</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> Subscriber<Integer> s = <span class="keyword">new</span> Subscriber<Integer>() {</span><br><span class="line"> Subscription subscription;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription subscription)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onSubscribe"</span>);</span><br><span class="line"> <span class="keyword">this</span>.subscription = subscription;</span><br><span class="line"> <span class="keyword">this</span>.subscription.request(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(Integer item)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onNext: {}"</span>, item);</span><br><span class="line"> <span class="keyword">this</span>.subscription.request(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onError"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"onComplete"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> p.subscribe(s);</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="number">00</span>:<span class="number">19</span>:<span class="number">08.655</span> [main] INFO com.jongmin.reactive.practice.PubSub - onSubscribe</span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.660</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">1</span></span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.662</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">2</span></span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.663</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">3</span></span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.663</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">4</span></span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.663</span> [main] INFO com.jongmin.reactive.practice.PubSub - onNext: <span class="number">5</span></span><br><span class="line"><span class="number">00</span>:<span class="number">19</span>:<span class="number">08.663</span> [main] INFO com.jongmin.reactive.practice.PubSub - onComplete</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Reactive/">Reactive</category>
<comments>https://jongmin92.github.io/2019/11/05/Java/reactive-1/#disqus_thread</comments>
</item>
<item>
<title>Spring Bean LifeCycle</title>
<link>https://jongmin92.github.io/2019/05/21/Spring/spring-bean-life-cycle/</link>
<guid>https://jongmin92.github.io/2019/05/21/Spring/spring-bean-life-cycle/</guid>
<pubDate>Mon, 20 May 2019 16:54:00 GMT</pubDate>
<description><p>최근 사내에서 Kafka 관련된 설정을 리팩토링하는 작업을 했습니다. SpringBoot를 도입하며 Kafka 설정 관련된 부분들을 SpringBoot의 <a href="https://docs.spring.io/spring-boot/docs/cu</description>
<content:encoded><![CDATA[<p>최근 사내에서 Kafka 관련된 설정을 리팩토링하는 작업을 했습니다. SpringBoot를 도입하며 Kafka 설정 관련된 부분들을 SpringBoot의 <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationProperties.html">@ConfigurationProperties</a>를 이용하도록 변경하고, XML 기반의 빈 생성 부분을 Java Config 기반으로 변경했습니다.<br>현재 프로젝트에서는 여러가지 이유로 기존의 Kafka Producer를 확장(extends)해서 사용하고 있는데, 이번에 리팩토링 관련한 PR에서 <strong>확장해서 사용하고 있는 Kafka Producer에서 <code>close</code> 메서드를 구현하고 있는지 확인해달라는 리뷰를 받았습니다.</strong></p><p>컨테이너에 등록된 Bean이 DisposableBean interface를 구현하고 있거나 @PreDestroy 어노테이션 또는 destroyMethod 속성을 사용하고 있다면 Bean의 Lifecycle 마지막에 자원을 해제하거나 필요한 작업을 수행할 수 있습니다. 그러나 위의 방법 말고도 Spring container에서 Bean을 제거 할 때, <strong><code>close()</code></strong> 와 <strong><code>shutdown()</code></strong> 메서드를 호출합니다.</p><p><a href="https://kafka.apache.org/11/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html">Kafka Producer</a>는 <strong><a href="https://docs.oracle.com/javase/7/docs/api/java/io/Closeable.html">Closeable</a></strong> interface를 구현(implement)하고 있기 때문에 close 메서드를 포함하고 있습니다. Kafka Producer가 Bean으로 등록되어 있고 후에 소멸될 때 close 메서드가 호출되어 해당 자원이 모두 해제될 것입니다.</p><p> 따라서 제가 받았던 리뷰는 기존 Kafka Producer를 확장한 것을 Bean으로 등록해 사용하고 있는데, 해당 확장 클래스가 close 메서드를 제대로 구현해 Bean이 소멸 될 때 호출될 close 메서드에서 자원이 제대로 해제하고 있는지 확인해 달라는 것이었습니다.</p><blockquote><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html">AutoCloseable</a></strong> 은 <a href="https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html">try-with-resource</a> 구문과 함께 사용됩니다.</p></blockquote><p>이번 리뷰를 통해 Bean의 LifeCycle과 close, shutdown 메서드에 대해 다시 살펴보는 계기가 되었습니다.</p><h1 id="Initialize-메서드"><a href="#Initialize-메서드" class="headerlink" title="Initialize 메서드"></a>Initialize 메서드</h1><p>Initialize 메서드는 Bean Object가 생성되고 DI를 마친 후 실행되는 메서드입니다. 일반적으로 Object의 초기화 작업이 필요한 경우 생성자에서 처리하지만 DI를 통해 Bean이 주입된 후에 초기화할 작업이 있다면 초기화 메서드를 이용해서 초기화를 진행할 수 있습니다.</p><h2 id="PostConstruct"><a href="#PostConstruct" class="headerlink" title="@PostConstruct"></a>@PostConstruct</h2><p>초기화 하고 싶은 메서드에 <strong><code>@PostConstruct</code></strong> 어노테이션을 붙여주면 Spring이 해당 메서드를 초기화시에 호출합니다. PostConstruct는 <a href="https://jcp.org/en/jsr/detail?id=250">JSR-250</a> 스펙에 포함되어 있기 때문에 JSR-250을 구현한 다른 프레임워크 혹은 라이브러리에서도 동작합니다. 다른 초기화 메서드에 비해 Spring에 의존적이지 않다는 장점이 있습니다.</p><blockquote><p><a href="https://en.wikipedia.org/wiki/JSR_250">JSR-250</a><br>JSR 250 is a Java Specification Request with the objective to develop annotations (that is, information about a software program that is not part of the program itself) for common semantic concepts in the Java SE and Java EE platforms that apply across a variety of individual technologies.</p></blockquote><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">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostConstruct</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postConstruct</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"postConstruct"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="InitializingBean"><a href="#InitializingBean" class="headerlink" title="InitializingBean"></a>InitializingBean</h2><p><strong><code>InitializingBean</code></strong> 인터페이스를 구현하면 Spring이 afterPropertiesSet 메서드를 초기화시에 호출합니다.</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">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> <span class="keyword">implements</span> <span class="title">InitializingBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> log.info(<span class="string">"afterPropertiesSet"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Bean-initMethod"><a href="#Bean-initMethod" class="headerlink" title="@Bean(initMethod)"></a>@Bean(initMethod)</h2><p>@Bean 어노테이션을 이용해 Bean을 생성할 때, @Bean 어노테이션의 <strong><code>initMethod</code></strong> 속성을 이용해 초기화 메서드를 지정할 수 있습니다.</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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean(initMethod = "init")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> SimpleBean <span class="title">simpleBean</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> SimpleBean();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Slf4j</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> log.info(<span class="string">"init"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Destroy-메서드"><a href="#Destroy-메서드" class="headerlink" title="Destroy 메서드"></a>Destroy 메서드</h1><p>Destroy 메서드는 스프링 컨테이너가 종료 될 때, 호출되어 Bean이 사용한 리소스들을 반환하거나 종료 시점에 처리해야할 작업이 있을 경우 사용합니다.</p><h2 id="PreDestroy"><a href="#PreDestroy" class="headerlink" title="PreDestroy"></a>PreDestroy</h2><p><strong><code>@PreDestroy</code></strong> 도 PostConstruct처럼 <a href="https://jcp.org/en/jsr/detail?id=250">JSR-250</a> 스펙에 포함되어 있기 때문에 JSR-250을 구현한 다른 프레임워크 혹은 라이브러리에서도 동작합니다. 컨테이너가 종료 될 때 실행하고 싶은 메서드에 어노테이션을 붙여주면 Spring이 컨테이너 종료 시 해당 메서드를 호출합니다.</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">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PreDestroy</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">preDestroy</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"preDestroy"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="DisposableBean"><a href="#DisposableBean" class="headerlink" title="DisposableBean"></a>DisposableBean</h2><p><strong><code>DisposableBean</code></strong> 인터페이스를 구현하면 Spring이 destroy 메서드를 호출합니다.</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">lf4j</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> <span class="keyword">implements</span> <span class="title">DisposableBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> log.info(<span class="string">"destroy"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Bean-destroyMethod"><a href="#Bean-destroyMethod" class="headerlink" title="@Bean(destroyMethod)"></a>@Bean(destroyMethod)</h2><p>@Bean 어노테이션을 이용해 Bean을 생성할 때, @Bean 어노테이션의 <strong><code>destroyMethod</code></strong> 속성을 이용해 컨테이너 종료시 실행하고자 하는 메서드를 지정할 수 있습니다.</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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestConfiguration</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean(destroyMethod = "destroy")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> SimpleBean <span class="title">simpleBean</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> SimpleBean();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Slf4j</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleBean</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> log.info(<span class="string">"destroy"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="close-amp-shutdown"><a href="#close-amp-shutdown" class="headerlink" title="close & shutdown"></a>close & shutdown</h1><p>close와 shutdown 메서드는 <a href="https://docs.spring.io/spring/docs/3.2.4.RELEASE_to_4.0.0.M3/Spring%20Framework%203.2.4.RELEASE/org/springframework/beans/factory/support/DisposableBeanAdapter.html">DisposableBeanAdapter</a>에 의해 실행됩니다.<br><img src="/images/post/2019-05-21/disposableBeanAdapter.png"></p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Spring/">Spring</category>
<category domain="https://jongmin92.github.io/tags/Bean/">Bean</category>
<category domain="https://jongmin92.github.io/tags/Closeable/">Closeable</category>
<category domain="https://jongmin92.github.io/tags/AutoClosable/">AutoClosable</category>
<category domain="https://jongmin92.github.io/tags/DisposableBeanAdapter/">DisposableBeanAdapter</category>
<comments>https://jongmin92.github.io/2019/05/21/Spring/spring-bean-life-cycle/#disqus_thread</comments>
</item>
<item>
<title>오픈소스 컷 컨트리뷰트 경험기</title>
<link>https://jongmin92.github.io/2019/05/18/Etc/open-source-experience/</link>
<guid>https://jongmin92.github.io/2019/05/18/Etc/open-source-experience/</guid>
<pubDate>Sat, 18 May 2019 11:13:00 GMT</pubDate>
<description><p>얼마 전 개발자 생에 처음으로 오픈소스에 컨트리뷰트를 하는 경험을 하였습니다. 이번 포스팅에서는 오픈소스 첫 컨트리뷰트 관련해 이야기 해보려 합니다.</p>
<h1 id="어떻게-시작하게-되었는가"><a href="#어떻게-시작하게-되었는가" c</description>
<content:encoded><![CDATA[<p>얼마 전 개발자 생에 처음으로 오픈소스에 컨트리뷰트를 하는 경험을 하였습니다. 이번 포스팅에서는 오픈소스 첫 컨트리뷰트 관련해 이야기 해보려 합니다.</p><h1 id="어떻게-시작하게-되었는가"><a href="#어떻게-시작하게-되었는가" class="headerlink" title="어떻게 시작하게 되었는가?"></a>어떻게 시작하게 되었는가?</h1><p>개발자라면 한 번쯤 오픈소스에 기여하고 컨트리뷰터가 되어보고 싶다는 생각을 가져봅니다. 저 역시 언젠가 한 번쯤… 이라는 생각은 오래 전 부터 갖고 있었지만 막상 실행에 옮기기 까지가 쉽지 않았습니다. 이미 오픈소스에 기여해 본 많은 개발자 분들이 오픈소스 기여에 쉽게 입문 할 수 있도록 여러 가이드들도 많이 만들어 주셨지만 저는 그 마저도 이용을 하지 못하고 있었습니다. 그러던 도중 우연히 <code>Armeria Sprint</code>라는 좋은 기회가 찾아왔습니다.</p><blockquote><p>LINE의 오픈소스와 Armeria에 대해 조금 더 알아보고 싶다면 다음 글들이 도움이 될 것 같습니다.</p></blockquote><ul><li><a href="https://engineering.linecorp.com/ko/blog/line-developer-interview-3/">비동기를 사랑하는 오픈소스 개발자, 이희승</a></li><li><a href="https://engineering.linecorp.com/ko/blog/thank-you-for-contributing-to-armeria/">오픈소스 Armeria의 기여자를 위한 이벤트를 진행하였습니다</a></li><li><a href="https://engineering.linecorp.com/ko/blog/armeria-sprint-1/">GitHub Contributions 그래프를 푸릇푸릇하게 만들어보아요(feat. Armeria Sprint)</a></li></ul><h1 id="Armeria-Sprint"><a href="#Armeria-Sprint" class="headerlink" title="Armeria Sprint"></a>Armeria Sprint</h1><p>사내에서 개발하여 오픈소스로 공개한 프로젝트인 Armeria에 기여할 수 있도록 사내 개발자를 대상으로 Armeria Sprint 행사가 있었습니다.</p><blockquote><p><strong>오픈소스 스프린트란?</strong><br>오픈소스 스프린트란 오픈소스에 관심있는 사람들이 모여서 오픈소스에 기여해 보는 것이라고 정의할 수 있습니다. 행사마다 편차가 있겠지만 보통 진행 기간을 하루 정도로 잡고 오전에는 다같이 모여서 각자 할 일(어떤 이슈를 맡아서 할지)을 정하고 오후에는 집중해서 코딩을 합니다.<br>참고 : <a href="https://engineering.linecorp.com/ko/blog/armeria-sprint-1/">GitHub Contributions 그래프를 푸릇푸릇하게 만들어보아요(feat. Armeria Sprint)</a></p></blockquote><p>오픈소스에 기여해 보고 싶어도 여러 이유로 시작하지 못하고 있었던 저는 해당 행사의 인원 모집이 시작되자마자 고민없이 바로 신청해 참가할 수 있었습니다.</p><p>행사는 이틀에 나누어서 첫째 날에는 환영 세션이 2시간 동안 진행되었고, 둘째 날에는 스프린트가 4시간 동안 진행되었습니다. 행사 동안에는 간단한 자기 소개, 오픈소스에 기여하기 전에 알야아할 것, 스프린트 기간 동안 해결할 이슈 정하기, 그리고 마지막으로 집중해서 코딩하기와 같은 활동들이 있었습니다.</p><h1 id="Contribute"><a href="#Contribute" class="headerlink" title="Contribute"></a>Contribute</h1><p>오픈소스에 처음 기여할 때 어려운 부분 중 하나가 **”어떤 이슈를 맡아 해결하여 기여를 할 것인가”**인데요. 저는 이번 Armeria Sprint를 통해 현재 해결해야 할 이슈들이 어떤 것들이 있는지, 해당 이슈는 어떤 부분에 대한 내용인지에 대해 직접 듣고 모르는 부분은 직접 물어보며 진행 할 수 있었기 때문에 조금은 더 수월하게 진행할 수 있었습니다.</p><p>아마 처음 온라인으로 직접 이슈를 처음 선택하기에는 어려운 부분이 있을 것 같은데요. Armeria에서는 <strong><a href="https://github.com/line/armeria/issues?q=is:open+is:issue+label:good-first-issue">good-first-issue</a></strong> 라는 이름의 Label을 붙여 조금은 해결하기 쉬운 이슈들을 표시해주고 있습니다.<br><img src="/images/post/2019-05-18/armeria_1.png"></p><p>해당 이슈들 중 아는 부분이 있거나 해보고 싶은 이슈가 있다면 본인이 해결해 보겠다는 코멘트를 남긴 후 작업을 진행하면 됩니다.<br>내가 맡은 이슈가 어떤 문제를 해결(개선)하기 위한 것인지, 코드의 어떤 부분을 수정해야 하는지 파악하는 것이 처음에 가장 중요하다고 생각합니다. 이를 토대로 처음 PR을 올리게 되면 maintainer 분들이 꼼꼼한 리뷰와 함께 코멘트를 남겨주시기 때문에 같이 고민해가며 코드를 점차 개선해 나아갈 수 있습니다.</p><p>아래는 Armeria Sprint 동안 제가 맡았던 Issue와 PR입니다.</p><ul><li>Issue: <a href="https://github.com/line/armeria/issues/1584">Provide a way to get request body in service method.</a></li><li>PR: <a href="https://github.com/line/armeria/pull/1745">Enable to convert request body to expected result type regardless of content-type</a></li></ul><p>스프린트 2일차 때, 약 4시간 정도의 시간 동안 코딩을 하고 당일날 첫 PR을 올릴 수 있었습니다. 첫 PR을 올리고 다음날 maintainer 분들의 리뷰 코멘트가 달리기 시작했고, 틈틈히 코멘트 반영과 리뷰를 반복한 결과 약 3주 정도 후 첫 PR이 머지될 수 있었습니다.</p><p>위 과정을 반복하며 오픈소스에 기여하는데 있어 필요한 부분들을 다시 한 번 생각해 보게 되었습니다.</p><ul><li>몇번의 리뷰와 코멘트 반영 없이 한번에 PR이 머지되기는 쉽지 않습니다. 프로젝트의 maintainer가 아닌 이상 내가 작성한 코드가 모든 경우를 다 커버할 수 있을지는 테스트 코드를 작성하더라도 쉽게 확신할 수 없습니다. 그렇기 때문에 이슈 해결을 위한 코드와 테스트 코드를 작성한 후에는 PR을 만들어 리뷰를 요청드리는게 더 빠르게 머지될 수 있는 방법 같습니다.</li><li>저는 Armeria Sprint를 통해 처음 궁금했던 부분들에 대해 오프라인에서 직접 여쭤보고 답을 받을 수 있었지만, 실제 오픈소스에 기여하는 과정에서는 모든 과정이 온라인에서 진행됩니다. 따라서 글로 본인의 의사를 잘 전달할 수 있는 능력이 중요합니다.<ul><li>나의 생각이 어떠한지, 어떤 부분에 대해서 모르는지 아는지를 글로써 잘 전달해야 maintainer 분들도 참고해 도움이 될 수 있는 코멘트를 남겨주실 수 있습니다.</li></ul></li><li>모든 의사소통은 영어를 이용해서 하지만 Google 번역기가 있으니 너무 걱정하지 않아도 됩니다.</li></ul><h1 id="후기"><a href="#후기" class="headerlink" title="후기"></a>후기</h1><p>Armeria Sprint에서 기념품으로 컵을 받았는데요.<br><img src="/images/post/2019-05-18/armeria_2.jpeg"></p><p>뒤에 이런 문구가 적혀 있었습니다. 오픈소스에 그리고 Armeria에 관심이 있다면 여러분들도 한 번 기여해보세요!<br><img src="/images/post/2019-05-18/armeria_3.jpeg"></p><p>처음으로 오픈소스에 기여해보았다는 것, 그리고 그 오픈소스가 Armeria라는 것이 매우 재밌고 뜻 깊은 경험이었습니다. 저도 이번 첫 컨트리뷰트를 시작으로 가능하면 꾸준히 기여를 해보려고 합니다.<br><img src="/images/post/2019-05-18/armeria_4.png"></p>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Armeria/">Armeria</category>
<category domain="https://jongmin92.github.io/tags/Open-source/">Open source</category>
<comments>https://jongmin92.github.io/2019/05/18/Etc/open-source-experience/#disqus_thread</comments>
</item>
<item>
<title>(Gradle dependency) api와 implementation 차이</title>
<link>https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/</link>
<guid>https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/</guid>
<pubDate>Thu, 09 May 2019 13:13:00 GMT</pubDate>
<description><p><strong>build script의 <code>dependencies</code> 블록에 여러 가지 다양한 종속성 구성(api, implementation, compileOnly, runtimeOnly, annotationProcessor)을</description>
<content:encoded><![CDATA[<p><strong>build script의 <code>dependencies</code> 블록에 여러 가지 다양한 종속성 구성(api, implementation, compileOnly, runtimeOnly, annotationProcessor)을 사용하여 라이브러리 종속성을 선언할 수 있습니다.</strong> 다양한 종속성 구성 중 api와 implementation의 차이에 대해서 알아봅니다.</p><h1 id="api-amp-implementation"><a href="#api-amp-implementation" class="headerlink" title="api & implementation"></a>api & implementation</h1><p><a href="https://docs.gradle.org/current/userguide/dependency_management_for_java_projects.html#sec:configurations_java_tutorial">Gradle document</a>에서는 api와 implementation에 대해서 다음과 같이 설명하고 있습니다.</p><ul><li><strong>api</strong><blockquote><p><strong>The dependencies required to compile the production source of the project which are part of the API exposed by the project.</strong> For example the project uses Guava and exposes public interfaces with Guava classes in their method signatures.</p><p><strong>프로젝트에 의해 노출 된 API의 일부인 프로젝트의 프로덕션 소스를 컴파일하는 데 필요한 종속성</strong></p></blockquote></li><li><strong>implementation</strong><blockquote><p><strong>The dependencies required to compile the production source of the project which are not part of the API exposed by the project.</strong> For example the project uses Hibernate for its internal persistence layer implementation.</p><p><strong>프로젝트에 의해 노출 된 API의 일부가 아닌 프로젝트의 프로덕션 소스를 컴파일하는 데 필요한 종속성</strong></p></blockquote></li></ul><p>위 설명만 가지고는 이해하기가 쉽지 않습니다. <a href="https://developer.android.com/studio/build/dependencies?utm_source=android-studio#dependency_configurations">Android Developers document</a>에서는 같은 내용을 다음과 같이 한글로 설명하고 있습니다.</p><ul><li><strong>api</strong><blockquote><p>Gradle은 컴파일 클래스 경로 및 빌드 출력에 종속성을 추가합니다. <strong>모듈에 api 종속성을 포함하면 다른 모듈에 그 종속성을 과도적으로 내보내기를 원하며 따라서 런타임과 컴파일 시 모두 종속성을 사용할 수 있다는 사실을 Gradle에 알려줄 수 있습니다.</strong></p><p>이 구성은 compile(현재 지원 중단됨)과 똑같이 동작합니다. 다만 이것은 주의해서 사용해야 하며 다른 업스트림 소비자에게 일시적으로 내보내는 종속성만 함께 사용해야 합니다. 그 이유는 api 종속성이 외부 API를 변경하면 Gradle이 컴파일 시 해당 종속성에 액세스할 권한이 있는 모듈을 모두 다시 컴파일하기 때문입니다. 그러므로 api 종속성이 많이 있으면 빌드 시간이 상당히 증가합니다. 종속성의 API를 별도의 모듈에 노출시키고 싶은 것이 아니라면 라이브러리 모듈은 implementation 종속성을 대신 사용해야 합니다.</p></blockquote></li><li><strong>implementation</strong><blockquote><p>Gradle은 종속성을 컴파일 클래스 경로에 추가하여 종속성을 빌드 출력에 패키징합니다. 다만 <strong>모듈이 implementation 종속성을 구성하는 경우, 이것은 Gradle에 개발자가 모듈이 컴파일 시 다른 모듈로 유출되는 것을 원치 않는다는 것을 알려줍니다. 즉, 종속성은 런타임 시 다른 모듈에서만 이용할 수 있습니다.</strong></p><p>api 또는 compile(지원 중단됨) 대신 이 종속성 구성을 사용하면 빌드 시스템이 재컴파일해야 하는 모듈의 수가 줄어들기 때문에 빌드 시간이 상당히 개선될 수 있습니다. 예를 들어, implementation 종속성이 API를 변경하면 Gradle은 해당 종속성과 그 종속성에 직접 연결된 모듈만 다시 컴파일합니다. 대부분의 앱과 테스트 모듈은 이 구성을 사용해야 합니다.</p></blockquote></li></ul><p>간단하게 차이점을 정리하면 다음과 같습니다.</p><ul><li><strong>api</strong>: 의존 라이브러리 수정시 해당 모듈을 의존하고 있는 모듈들 또한 재빌드<ul><li>A(api) <- B <- C 일 때, C 에서 A 를 접근할 수 있음</li><li>A 수정시 B 와 C 모두 재빌드</li></ul></li><li><strong>implementaion</strong>: 의존 라이브러리 수정시 본 모듈까지만 재빌드<ul><li>A(implementation) <- B <- C 일 때, C 에서 A 를 접근할 수 없음</li><li>A 수정시 B 까지 재빌드 </li></ul></li></ul><h1 id="참고"><a href="#참고" class="headerlink" title="참고"></a>참고</h1><ul><li><a href="https://stackoverflow.com/a/44419574">https://stackoverflow.com/a/44419574</a></li><li><a href="https://medium.com/mindorks/implementation-vs-api-in-gradle-3-0-494c817a6fa">https://medium.com/mindorks/implementation-vs-api-in-gradle-3-0-494c817a6fa</a></li><li><a href="https://www.youtube.com/watch?v=7ll-rkLCtyk&feature=youtu.be&t=29m35s">https://www.youtube.com/watch?v=7ll-rkLCtyk&feature=youtu.be&t=29m35s</a></li></ul>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Gradle/">Gradle</category>
<category domain="https://jongmin92.github.io/tags/Gradle/">Gradle</category>
<comments>https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/#disqus_thread</comments>
</item>
<item>
<title>CompletableFuture</title>
<link>https://jongmin92.github.io/2019/05/05/Java/java-async-4/</link>
<guid>https://jongmin92.github.io/2019/05/05/Java/java-async-4/</guid>
<pubDate>Sat, 04 May 2019 16:26:00 GMT</pubDate>
<description><p>해당 포스팅은 토비님의 <strong><a href="https://www.youtube.com/watch?v=PzxV-bmLSFY&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=4">토비의 봄 TV 11회 스</description>
<content:encoded><![CDATA[<p>해당 포스팅은 토비님의 <strong><a href="https://www.youtube.com/watch?v=PzxV-bmLSFY&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=4">토비의 봄 TV 11회 스프링 리액티브 프로그래밍 (7) CompletableFuture</a></strong> 라이브 코딩을 보며 따라했던 실습 내용을 바탕으로 정리한 글입니다.</p><p>실습 코드들은 IntelliJ를 이용해 <strong>SpringBoot 2.1.3.RELEASE 버전</strong> 기반으로 프로젝트를 생성 후(web, lombok 포함) 진행했습니다. </p><p> 이번에는 자바8에 나온 <strong><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html">CompletableFuture</a></strong> 라는 새로운 비동기 자바 프로그래밍 기술에 대해서 알아보고, 지난 3회 정도 동안 다루어 왔던 자바 서블릿, 스프링의 비동기 기술 발전의 내용을 자바 8을 기준으로 다시 재작성합니다.</p><h1 id="CompletableFuture"><a href="#CompletableFuture" class="headerlink" title="CompletableFuture"></a>CompletableFuture</h1><p>먼저 간단한 코드를 통해서 CompletableFuture 사용법에 대해서 알아보겠습니다.</p><h2 id="runAsync-amp-thenRun"><a href="#runAsync-amp-thenRun" class="headerlink" title="runAsync & thenRun"></a>runAsync & thenRun</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></pre></td><td class="code"><pre><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">CFuture</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> <span class="comment">// Async 작업이 끝나고 해당 스레드에서 계속해서 작업을 수행한다.</span></span><br><span class="line"> CompletableFuture</span><br><span class="line"> .runAsync(() -> log.info(<span class="string">"runAsync"</span>))</span><br><span class="line"> .thenRun(() -> log.info(<span class="string">"thenRun"</span>))</span><br><span class="line"> .thenRun(() -> log.info(<span class="string">"thenRun"</span>));</span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 별도의 pool을 설정하지 않으면 자바7 부터는 ForkJoinPool이 자동으로 사용된다.</span></span><br><span class="line"> ForkJoinPool.commonPool().shutdown();</span><br><span class="line"> ForkJoinPool.commonPool().awaitTermination(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 결과</span></span><br><span class="line"><span class="number">23</span>:<span class="number">43</span>:<span class="number">15.841</span> [main] INFO com.example.study.CFuture - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">43</span>:<span class="number">15.841</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - runAsync</span><br><span class="line"><span class="number">23</span>:<span class="number">43</span>:<span class="number">15.845</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenRun</span><br><span class="line"><span class="number">23</span>:<span class="number">43</span>:<span class="number">15.845</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenRun</span><br></pre></td></tr></table></figure><h2 id="supplyAsync-thenApply-thenAccept"><a href="#supplyAsync-thenApply-thenAccept" class="headerlink" title="supplyAsync, thenApply, thenAccept"></a>supplyAsync, thenApply, thenAccept</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></pre></td><td class="code"><pre><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">CFuture</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> <span class="comment">// Async 작업이 끝나고 해당 스레드에서 계속해서 작업을 수행한다.</span></span><br><span class="line"> CompletableFuture</span><br><span class="line"> .supplyAsync(() -> {</span><br><span class="line"> log.info(<span class="string">"supplyAsync"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용해 새로운 값을 return 한다.</span></span><br><span class="line"> .thenApply(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> s + <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용하며 return이 없다.</span></span><br><span class="line"> .thenAccept(s -> log.info(<span class="string">"thenAccept {}"</span>, s));</span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 별도의 pool을 설정하지않으면 자바7 부터는 ForkJoinPool이 자동으로 사용된다.</span></span><br><span class="line"> ForkJoinPool.commonPool().shutdown();</span><br><span class="line"> ForkJoinPool.commonPool().awaitTermination(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 결과</span></span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">00.650</span> [main] INFO com.example.study.CFuture - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">00.650</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - supplyAsync</span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">00.654</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">00.656</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenAccept <span class="number">2</span></span><br></pre></td></tr></table></figure><h2 id="thenCompose"><a href="#thenCompose" class="headerlink" title="thenCompose"></a>thenCompose</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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CFuture</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> <span class="comment">// Async 작업이 끝나고 해당 스레드에서 계속해서 작업을 수행한다.</span></span><br><span class="line"> CompletableFuture</span><br><span class="line"> .supplyAsync(() -> {</span><br><span class="line"> log.info(<span class="string">"supplyAsync"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// return이 CompletableFuture인 경우 thenCompose를 사용한다.</span></span><br><span class="line"> .thenCompose(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> CompletableFuture.completedFuture(s + <span class="number">1</span>);</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용해 새로운 값을 return 한다.</span></span><br><span class="line"> .thenApply(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> s + <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용하며 return이 없다.</span></span><br><span class="line"> .thenAccept(s -> log.info(<span class="string">"thenAccept {}"</span>, s));</span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 별도의 pool을 설정하지않으면 자바7 부터는 ForkJoinPool이 자동으로 사용된다.</span></span><br><span class="line"> ForkJoinPool.commonPool().shutdown();</span><br><span class="line"> ForkJoinPool.commonPool().awaitTermination(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 결과</span></span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">35.893</span> [main] INFO com.example.study.CFuture - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">35.893</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - supplyAsync</span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">35.897</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">35.899</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">2</span></span><br><span class="line"><span class="number">23</span>:<span class="number">50</span>:<span class="number">35.899</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenAccept <span class="number">3</span></span><br></pre></td></tr></table></figure><h2 id="exceptionally"><a href="#exceptionally" class="headerlink" title="exceptionally"></a>exceptionally</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><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></pre></td><td class="code"><pre><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">CFuture</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> <span class="comment">// Async 작업이 끝나고 해당 스레드에서 계속해서 작업을 수행한다.</span></span><br><span class="line"> CompletableFuture</span><br><span class="line"> .supplyAsync(() -> {</span><br><span class="line"> log.info(<span class="string">"supplyAsync"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// return이 CompletableFuture인 경우 thenCompose를 사용한다.</span></span><br><span class="line"> .thenCompose(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">if</span> (<span class="number">1</span> == <span class="number">1</span>) <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException();</span><br><span class="line"> <span class="keyword">return</span> CompletableFuture.completedFuture(s + <span class="number">1</span>);</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용해 새로운 값을 return 한다.</span></span><br><span class="line"> .thenApply(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> s + <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> .exceptionally(e -> {</span><br><span class="line"> log.info(<span class="string">"exceptionally"</span>);</span><br><span class="line"> <span class="keyword">return</span> -<span class="number">10</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용하며 return이 없다.</span></span><br><span class="line"> .thenAccept(s -> log.info(<span class="string">"thenAccept {}"</span>, s));</span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 별도의 pool을 설정하지않으면 자바7 부터는 ForkJoinPool이 자동으로 사용된다.</span></span><br><span class="line"> ForkJoinPool.commonPool().shutdown();</span><br><span class="line"> ForkJoinPool.commonPool().awaitTermination(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 결과</span></span><br><span class="line"><span class="number">23</span>:<span class="number">51</span>:<span class="number">31.255</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - supplyAsync</span><br><span class="line"><span class="number">23</span>:<span class="number">51</span>:<span class="number">31.257</span> [main] INFO com.example.study.CFuture - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">51</span>:<span class="number">31.259</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">51</span>:<span class="number">31.261</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - exceptionally</span><br><span class="line"><span class="number">23</span>:<span class="number">51</span>:<span class="number">31.261</span> [ForkJoinPool.commonPool-worker-<span class="number">1</span>] INFO com.example.study.CFuture - thenAccept -<span class="number">10</span></span><br></pre></td></tr></table></figure><h2 id="thenApplyAsync"><a href="#thenApplyAsync" class="headerlink" title="thenApplyAsync"></a>thenApplyAsync</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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><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">CFuture</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> ExecutorService es = Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Async 작업이 끝나고 해당 스레드에서 계속해서 작업을 수행한다.</span></span><br><span class="line"> CompletableFuture</span><br><span class="line"> .supplyAsync(() -> {</span><br><span class="line"> log.info(<span class="string">"supplyAsync"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }, es)</span><br><span class="line"> <span class="comment">// return이 CompletableFuture인 경우 thenCompose를 사용한다.</span></span><br><span class="line"> .thenCompose(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> CompletableFuture.completedFuture(s + <span class="number">1</span>);</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용해 새로운 값을 return 한다.</span></span><br><span class="line"> .thenApply(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> s + <span class="number">2</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 이 작업은 다른 스레드에서 처리를 하려고 할 때, thenApplyAsync를 사용한다.</span></span><br><span class="line"> <span class="comment">// 스레드의 사용을 더 효율적으로 하고 자원을 더 효율적으로 사용한다.</span></span><br><span class="line"> <span class="comment">// 현재 스레드 풀의 정책에 따라서 새로운 스레드를 할당하거나 대기중인 스레드를 사용한다. (스레드 풀 전략에 따라 다르다.)</span></span><br><span class="line"> .thenApplyAsync(s -> {</span><br><span class="line"> log.info(<span class="string">"thenApply {}"</span>, s);</span><br><span class="line"> <span class="keyword">return</span> s + <span class="number">3</span>;</span><br><span class="line"> }, es)</span><br><span class="line"> .exceptionally(e -> {</span><br><span class="line"> log.info(<span class="string">"exceptionally"</span>);</span><br><span class="line"> <span class="keyword">return</span> -<span class="number">10</span>;</span><br><span class="line"> })</span><br><span class="line"> <span class="comment">// 앞의 비동기 작업의 결과를 받아 사용하며 return이 없다.</span></span><br><span class="line"> .thenAcceptAsync(s -> log.info(<span class="string">"thenAccept {}"</span>, s), es);</span><br><span class="line"> log.info(<span class="string">"exit"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 별도의 pool을 설정하지않으면 자바7 부터는 ForkJoinPool이 자동으로 사용된다.</span></span><br><span class="line"> ForkJoinPool.commonPool().shutdown();</span><br><span class="line"> ForkJoinPool.commonPool().awaitTermination(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 결과</span></span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.043</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.example.study.CFuture - supplyAsync</span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.043</span> [main] INFO com.example.study.CFuture - exit</span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.047</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">1</span></span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.048</span> [pool-<span class="number">1</span>-thread-<span class="number">1</span>] INFO com.example.study.CFuture - thenApply <span class="number">2</span></span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.049</span> [pool-<span class="number">1</span>-thread-<span class="number">2</span>] INFO com.example.study.CFuture - thenApply <span class="number">4</span></span><br><span class="line"><span class="number">23</span>:<span class="number">54</span>:<span class="number">00.049</span> [pool-<span class="number">1</span>-thread-<span class="number">3</span>] INFO com.example.study.CFuture - thenAccept <span class="number">7</span></span><br></pre></td></tr></table></figure><h1 id="ListenableFuture에서-CompletableFuture로-변환"><a href="#ListenableFuture에서-CompletableFuture로-변환" class="headerlink" title="ListenableFuture에서 CompletableFuture로 변환"></a>ListenableFuture에서 CompletableFuture로 변환</h1><p>Spring 4.0에 들어간 <code>AsyncRestTemplate</code>이 return하는 것은 <code>CompletableFuture</code>가 아닌 <code>ListenableFuture</code>입니다.<br>Spring 4까지는 자바 6~8을 지원하기 때문에 CompletableFuture로 return을 만들지 못하고 계속 ListenableFuture를 유지했습니다. 따라서 ListenableFuture를 CompletableFuture로 만들어 체이닝하기 위해서는 유틸성 wrapper 메서드를 만들어 사용하면 됩니다.</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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableAsync</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">StudyApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RestController</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">MyController</span> </span>{</span><br><span class="line"> AsyncRestTemplate rt = <span class="keyword">new</span> AsyncRestTemplate(<span class="keyword">new</span> Netty4ClientHttpRequestFactory(<span class="keyword">new</span> NioEventLoopGroup(<span class="number">1</span>)));</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> MyService myService;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String URL1 = <span class="string">"http://localhost:8081/service?req={req}"</span>;</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String URL2 = <span class="string">"http://localhost:8081/service2?req={req}"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping("/rest")</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DeferredResult<String> <span class="title">rest</span><span class="params">(<span class="keyword">int</span> idx)</span> </span>{</span><br><span class="line"> DeferredResult<String> dr = <span class="keyword">new</span> DeferredResult<>();</span><br><span class="line"></span><br><span class="line"> toCF(rt.getForEntity(<span class="string">"http://localhost:8081/service?req={req}"</span>, String.class, <span class="string">"hello"</span> + idx))</span><br><span class="line"> .thenCompose(s -> toCF(rt.getForEntity(<span class="string">"http://localhost:8081/service2?req={req}"</span>, String.class, s.getBody())))</span><br><span class="line"> .thenCompose(s -> toCF(myService.work(s.getBody())))</span><br><span class="line"> .thenAccept(s -> dr.setResult(s))</span><br><span class="line"> .exceptionally(e -> {</span><br><span class="line"> dr.setErrorResult(e.getMessage());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"><span class="comment">// f1.addCallback(s -> {</span></span><br><span class="line"><span class="comment">// ListenableFuture<ResponseEntity<String>> f2 = rt.getForEntity("http://localhost:8081/service2?req={req}", String.class, s.getBody());</span></span><br><span class="line"><span class="comment">// f2.addCallback(s2 -> {</span></span><br><span class="line"><span class="comment">// ListenableFuture<String> f3 = myService.work(s2.getBody());</span></span><br><span class="line"><span class="comment">// f3.addCallback(s3 -> {</span></span><br><span class="line"><span class="comment">// dr.setResult(s3);</span></span><br><span class="line"><span class="comment">// }, e -> {</span></span><br><span class="line"><span class="comment">// dr.setErrorResult(e.getMessage());</span></span><br><span class="line"><span class="comment">// });</span></span><br><span class="line"><span class="comment">// }, e -> {</span></span><br><span class="line"><span class="comment">// dr.setErrorResult(e.getMessage());</span></span><br><span class="line"><span class="comment">// });</span></span><br><span class="line"><span class="comment">// }, e -> {</span></span><br><span class="line"><span class="comment">// dr.setErrorResult(e.getMessage());</span></span><br><span class="line"><span class="comment">// });</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dr;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <T> <span class="function">CompletableFuture<T> <span class="title">toCF</span><span class="params">(ListenableFuture<T> lf)</span> </span>{</span><br><span class="line"> CompletableFuture<T> cf = <span class="keyword">new</span> CompletableFuture<>();</span><br><span class="line"> lf.addCallback(s -> cf.complete(s), e -> cf.completeExceptionally(e));</span><br><span class="line"> <span class="keyword">return</span> cf;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Service</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">MyService</span> </span>{</span><br><span class="line"> <span class="meta">@Async</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ListenableFuture<String> <span class="title">work</span><span class="params">(String req)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> AsyncResult<>(req + <span class="string">"/asyncwork"</span>);</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="function"><span class="keyword">public</span> ThreadPoolTaskExecutor <span class="title">myThreadPool</span><span class="params">()</span> </span>{</span><br><span class="line"> ThreadPoolTaskExecutor te = <span class="keyword">new</span> ThreadPoolTaskExecutor();</span><br><span class="line"> te.setCorePoolSize(<span class="number">1</span>);</span><br><span class="line"> te.setMaxPoolSize(<span class="number">1</span>);</span><br><span class="line"> te.initialize();</span><br><span class="line"> <span class="keyword">return</span> te;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(StudyApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="https://jongmin92.github.io/categories/Programming/">Programming</category>
<category domain="https://jongmin92.github.io/categories/Programming/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Java/">Java</category>
<category domain="https://jongmin92.github.io/tags/Async/">Async</category>
<category domain="https://jongmin92.github.io/tags/CompletableFuture/">CompletableFuture</category>
<comments>https://jongmin92.github.io/2019/05/05/Java/java-async-4/#disqus_thread</comments>
</item>
</channel>
</rss>