<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>어? 되네? 어? 안되네?</title>
    <link>https://born2bedeveloper.tistory.com/</link>
    <description>개발자가 되고싶은 사람의 기록용 블로그</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 17:12:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jimkwon</managingEditor>
    <image>
      <title>어? 되네? 어? 안되네?</title>
      <url>https://tistory1.daumcdn.net/tistory/5243022/attach/9d3457018c7644a3a18d4c66166104f5</url>
      <link>https://born2bedeveloper.tistory.com</link>
    </image>
    <item>
      <title>OOM 원인 분석기, 근데 이제 데드락과 스레드</title>
      <link>https://born2bedeveloper.tistory.com/78</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시리즈로 나오지 않기를 간절히 바라며 포스팅을 시작합니다.. &lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  문제 발생: Heap도 안 찼는데 OOM?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2oKZk/btsNRPxcv1O/E7M0iSjdDXkvEfc20Y3xRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2oKZk/btsNRPxcv1O/E7M0iSjdDXkvEfc20Y3xRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2oKZk/btsNRPxcv1O/E7M0iSjdDXkvEfc20Y3xRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2oKZk%2FbtsNRPxcv1O%2FE7M0iSjdDXkvEfc20Y3xRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;287&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이틀에&amp;nbsp;한&amp;nbsp;번&amp;nbsp;꼴로&amp;nbsp;날라오는&amp;nbsp;OOM&amp;nbsp;발생&amp;nbsp;메일..&amp;nbsp;&lt;br /&gt;늘&amp;nbsp;그렇듯&amp;nbsp;원인&amp;nbsp;분석을&amp;nbsp;위해&amp;nbsp;힙&amp;nbsp;덤프&amp;nbsp;파일을&amp;nbsp;요청하려&amp;nbsp;했으나&amp;nbsp;예상&amp;nbsp;외의&amp;nbsp;답변이&amp;nbsp;돌아왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfbLeQ/btsNSc6r5gV/GkRoA7AHS3rU6j3i8F6I9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfbLeQ/btsNSc6r5gV/GkRoA7AHS3rU6j3i8F6I9k/img.png&quot; data-alt=&quot;예?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfbLeQ/btsNSc6r5gV/GkRoA7AHS3rU6j3i8F6I9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfbLeQ%2FbtsNSc6r5gV%2FGkRoA7AHS3rU6j3i8F6I9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;65&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제&amp;nbsp;OOM이&amp;nbsp;발생하는&amp;nbsp;시기의&amp;nbsp;Heap&amp;nbsp;메모리&amp;nbsp;영역을&amp;nbsp;확인해보니&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1423&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6WFY6/btsNRc7cMRC/bEjqDwY0eXg3FnLXs0PtK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6WFY6/btsNRc7cMRC/bEjqDwY0eXg3FnLXs0PtK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6WFY6/btsNRc7cMRC/bEjqDwY0eXg3FnLXs0PtK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6WFY6%2FbtsNRc7cMRC%2FbEjqDwY0eXg3FnLXs0PtK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1423&quot; height=&quot;548&quot; data-origin-width=&quot;1423&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대&amp;nbsp;용량으로&amp;nbsp;지정해놓은&amp;nbsp;1Gib에&amp;nbsp;한참&amp;nbsp;밑도는&amp;nbsp;상황에서&amp;nbsp;터지는&amp;nbsp;것을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1747007742892&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: GC Overhead limit exceeded
java.lang.OutOfMemoryError: Requested array size exceeds VM&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;애플리케이션 내부에서 OOM이 발생하여 종료된다면, 위처럼 로그 내부에서 힌트를 얻을 수 있겠지만,&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;현재는 ECS가 컨테이너를 모니터링하며 전체 메모리 사용량(힙 + 네이티브 + 스택 메모리)이 지정된 한도를 넘을 경우 컨테이너를 종료하는 상황이다.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 따라서 어플리케이션이 로그를 남기고 죽기 전에 ECS에서 먼저 컨테이너를 죽여버려서 덤프파일도, 로그도, 아무것도 얻지 못하는 상황..&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;빈 손으로 털레털레 데이터독으로 들어가 APM 항목을 훑어보던 중 특이한 점을 발견할 수 있었는데&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u data-prosemirror-mark-name=&quot;underline&quot; data-prosemirror-content-type=&quot;mark&quot;&gt;바로 스레드의 수가 비정상적으로 높다는 것&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;937&quot; data-start=&quot;904&quot; data-ke-size=&quot;size26&quot;&gt;  단서 발견: DataDog에서 보인 &amp;lsquo;스레드 수&amp;rsquo;&lt;b&gt;&lt;u data-prosemirror-mark-name=&quot;underline&quot; data-prosemirror-content-type=&quot;mark&quot;&gt;&lt;/u&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccOZfR/btsNUccRbBQ/WHCeFHy9pnYbIG3dBsPic0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccOZfR/btsNUccRbBQ/WHCeFHy9pnYbIG3dBsPic0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccOZfR/btsNUccRbBQ/WHCeFHy9pnYbIG3dBsPic0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccOZfR%2FbtsNUccRbBQ%2FWHCeFHy9pnYbIG3dBsPic0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1322&quot; height=&quot;464&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-pm-slice=&quot;0 0 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;mediaSingle&quot; data-prosemirror-content-type=&quot;node&quot;&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;760&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;확실히 스레드의 개수가 높아진 시점과 OOM이 발생한 시점이 비슷해보인다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경의 인증 서버의 경우 동일대 시간으로 봤을 때 대략 70개 전후의 스레드 개수에 비해&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXEw5i/btsNSOxd58x/CbkrSa8eBYOwPIQX0xhPrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXEw5i/btsNSOxd58x/CbkrSa8eBYOwPIQX0xhPrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXEw5i/btsNSOxd58x/CbkrSa8eBYOwPIQX0xhPrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXEw5i%2FbtsNSOxd58x%2FCbkrSa8eBYOwPIQX0xhPrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1031&quot; height=&quot;658&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;문제가 발생하는 회원 서버의 경우에는 최대 2.28k의 개수까지 치솟고 있는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vArAp/btsNSM7gHKn/nMWS1iMBuit9tirJNpuqjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vArAp/btsNSM7gHKn/nMWS1iMBuit9tirJNpuqjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vArAp/btsNSM7gHKn/nMWS1iMBuit9tirJNpuqjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvArAp%2FbtsNSM7gHKn%2FnMWS1iMBuit9tirJNpuqjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1171&quot; height=&quot;641&quot; data-origin-width=&quot;1171&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;단순히 개수가 많다고 해서 OOM과 연관이 있는 것은 아니나, 스레드는 일정 시간이 지나면(자신이 해야 할 일을 다 했다면) 회수 되어야 한다.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;인증 서버의 경우 잠시 증가된 스레드는 일정 시간 후에 회수되고 있으나, 회원 서버 의 경우 회수되는 것에 비해 증가 폭이 큰 것을 볼 수 있었다.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;만약 스레드가 회수되지 못해 메모리에 남게 된다면 OOM의 주 원인이 될 수 있다.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h1 data-end=&quot;1206&quot; data-start=&quot;1182&quot;&gt;문제 재현을 위한 부하 테스트 환경 구성&lt;/h1&gt;
&lt;h2 data-end=&quot;1228&quot; data-start=&quot;1208&quot; data-ke-size=&quot;size26&quot;&gt;⚙️ 테스트 도구 &amp;amp; 환경 세팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1312&quot; data-start=&quot;1230&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1257&quot; data-start=&quot;1230&quot;&gt;부하 테스트: &lt;b&gt;Apache JMeter&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1282&quot; data-start=&quot;1258&quot;&gt;JVM 모니터링: &lt;b&gt;visualVM&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1312&quot; data-start=&quot;1283&quot;&gt;JVM 옵션: -Xmn100m -Xmx300m&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 인텔리제이 내부의 플러그인을 설치하여 간편하게 연동하여 사용할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;(설치 및 연동은 &lt;/span&gt;&lt;a href=&quot;https://monny.tistory.com/267&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳&lt;/a&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;a href=&quot;https://monny.tistory.com/267&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;을 &lt;/a&gt;참고하자)&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&amp;nbsp;대상&amp;nbsp;API&amp;nbsp;분석&amp;nbsp;및&amp;nbsp;선정&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 메모리가 급증하는 구간과 평탄한 구간 마다 가장 많이 호출됐던 API 리스트를 뽑아봤을 때&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;거의 순위가 차이가 없음을 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1613&quot; data-start=&quot;1551&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 갑작스러운 메모리 폭증보다는, &lt;b&gt;빈번한 호출로 인해 서서히 쌓이는 메모리 누수&lt;/b&gt;일 가능성이 높다고 판단.&lt;/p&gt;
&lt;p data-end=&quot;1636&quot; data-start=&quot;1615&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 테스트 대상 API 3종:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1725&quot; data-start=&quot;1638&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1656&quot; data-start=&quot;1638&quot;&gt;POST /v1/login&lt;/li&gt;
&lt;li data-end=&quot;1701&quot; data-start=&quot;1657&quot;&gt;POST /v1/auth/member/log/{memberLogType}&lt;/li&gt;
&lt;li data-end=&quot;1725&quot; data-start=&quot;1702&quot;&gt;GET /v1/auth/member&lt;/li&gt;
&lt;li data-end=&quot;1725&quot; data-start=&quot;1702&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로&amp;nbsp;JMeter에&amp;nbsp;테스트&amp;nbsp;환경까지&amp;nbsp;구성해주면&amp;nbsp;테스트&amp;nbsp;진행&amp;nbsp;준비는&amp;nbsp;끝났다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ipo4M/btsNTzsP5i0/K5Drck5WGZcr2ivkxzF34k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ipo4M/btsNTzsP5i0/K5Drck5WGZcr2ivkxzF34k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ipo4M/btsNTzsP5i0/K5Drck5WGZcr2ivkxzF34k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIpo4M%2FbtsNTzsP5i0%2FK5Drck5WGZcr2ivkxzF34k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;373&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1826&quot; data-start=&quot;1757&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1826&quot; data-start=&quot;1757&quot; data-ke-size=&quot;size16&quot;&gt;가장 자주 호출되는 login API부터 테스트했다.&lt;br /&gt;100명의 유저(스레드)가 1초 안에 실행되고, 총 250회 반복.&lt;/p&gt;
&lt;p data-end=&quot;1832&quot; data-start=&quot;1828&quot; data-ke-size=&quot;size16&quot;&gt;결과는&amp;hellip;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1928&quot; data-start=&quot;1834&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1847&quot; data-start=&quot;1834&quot;&gt;Heap은 안정적&lt;/li&gt;
&lt;li data-end=&quot;1891&quot; data-start=&quot;1848&quot;&gt;GC는 Eden &amp;rarr; Survivor에서 정상적으로 Minor GC 수행&lt;/li&gt;
&lt;li data-end=&quot;1908&quot; data-start=&quot;1892&quot;&gt;Old 영역 진입 없음&lt;/li&gt;
&lt;li data-end=&quot;1928&quot; data-start=&quot;1909&quot;&gt;테스트 후 스레드 수 복구 OK&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1953&quot; data-start=&quot;1930&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 1번 API는 &lt;b&gt;범인 아님&lt;/b&gt; 확정!&lt;/p&gt;
&lt;p data-end=&quot;1953&quot; data-start=&quot;1930&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1992&quot; data-start=&quot;1960&quot;&gt;2차 테스트: Member Log API에서 데드락이?&lt;/h1&gt;
&lt;p data-end=&quot;2059&quot; data-start=&quot;1994&quot; data-ke-size=&quot;size16&quot;&gt;이 API는 로그인/로그아웃 등 이벤트 로그를 남기는 기능이다.&lt;br /&gt;테스트 중간에 갑자기 발생한 &lt;b&gt;데드락&lt;/b&gt; 로그&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ElwXq/btsNRguZEnI/L9CxcBpV6sS4YgnIrLZohk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ElwXq/btsNRguZEnI/L9CxcBpV6sS4YgnIrLZohk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ElwXq/btsNRguZEnI/L9CxcBpV6sS4YgnIrLZohk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FElwXq%2FbtsNRguZEnI%2FL9CxcBpV6sS4YgnIrLZohk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1264&quot; height=&quot;142&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  데드락 원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API 내부 로직은 다음과 같다:&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Transactional
public MemberResponse.Result createMemberLog(...) {
    member.updateLastLoginDateTime();
    eventPublisherComponent.publishMemberEvent(...);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 로그 저장은 이렇게 처리하고 있었음&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void logEvent(MemberLogDto memberLogDto) {
    memberLogRepository.save(...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #1f1f21; color: #bfc1c4; text-align: start;&quot; data-renderer-start-pos=&quot;4254&quot; data-ke-size=&quot;size16&quot;&gt;데드락이 발생할 수 있는 이유는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #1f1f21; color: #bfc1c4; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@TransactionalEventListener 의 BEFORE_COMMIT 으로 커밋 직전에 해당 logEvent메소드가 실행된다.&lt;/li&gt;
&lt;li&gt;MemberLog는 Member와 연관관계인 상태 (동일한 member를 참조할 수 있다.)&lt;/li&gt;
&lt;li&gt;같은 트랜잭션 내부에서 아래와 같이 멤버를 수정하려고 할 때 데이터베이스에서 &amp;lsquo;잠금(Locking)&amp;rsquo;이 발생하면, MemberLog를 저장하려 할 때 동일한 테이블에 대한 잠금이 필요해질 수 있다&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1747008765593&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(memberLogType.equals(MemberLogType.LOGIN)){
    member.updateLastLoginDateTime();  
}  // ---&amp;gt; 여기서 1차적으로 Member 테이블에 접근하여 락이 걸리고



final MemberLog memberLog = memberLogDto.toEntity();
memberLogRepository.save(memberLog);
  // ---&amp;gt; 로그 저장하는 시점에서 MemberLog와 연관관계인 Member 테이블에 접근하여 락이 걸림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 실제로 운영환경에서도 데드락 이슈가 간간히 발생하고 있었다..!&amp;nbsp;  )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1109&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pcg3V/btsNTQ8SP0J/qOHkDOXkINUmvvvh2YoJS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pcg3V/btsNTQ8SP0J/qOHkDOXkINUmvvvh2YoJS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pcg3V/btsNTQ8SP0J/qOHkDOXkINUmvvvh2YoJS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpcg3V%2FbtsNTQ8SP0J%2FqOHkDOXkINUmvvvh2YoJS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1109&quot; height=&quot;295&quot; data-origin-width=&quot;1109&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;  데드락 해결을 위한 코드 수정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 트랜잭션 종료 후 비동기로 로그를 처리하도록 수정했다:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Async
@Transactional
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void logEvent(MemberLogDto memberLogDto) {
    memberLogRepository.save(...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AFTER_COMMIT으로 바꾸고, 비동기로 처리하여 락 충돌 방지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;그런데... 또 Heap이 급등?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 다시 해보니 Heap이 급등하고, GC의 Old 영역도 확연히 증가하는 양상을 보였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 데드락은 잡았지만 &lt;b&gt;또 다른 문제가 숨어 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIqApu/btsNSf9WELi/l5JN5QNb4KB4eSkqqqgaKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIqApu/btsNSf9WELi/l5JN5QNb4KB4eSkqqqgaKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIqApu/btsNSf9WELi/l5JN5QNb4KB4eSkqqqgaKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIqApu%2FbtsNSf9WELi%2Fl5JN5QNb4KB4eSkqqqgaKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;980&quot; height=&quot;516&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;  스레드 누수의 진짜 원인 - Async 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncConfig를 살펴보니...&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Bean
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setTaskDecorator(new CustomDecorator());
        return taskExecutor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈에 띈 문제점:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;corePoolSize, maxPoolSize, queueCapacity 설정 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스레드 종료 처리 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RequestContextHolder를 통한 ThreadLocal 사용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 종료 후 RequestAttributes 미정리&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스레드를 계속 생성하면서 회수가 안 되는 구조&lt;br /&gt;&amp;rarr; RequestContextHolder가 스레드에 남아 있어서 누수 발생&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;✅ 최종 조치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하&amp;nbsp;테스트를&amp;nbsp;통해&amp;nbsp;파악한&amp;nbsp;API에&amp;nbsp;대한&amp;nbsp;조치&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 데드락 해결&lt;/h2&gt;
&lt;pre id=&quot;code_1747009652003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class MemberLogListener {

    private final MemberLogRepository memberLogRepository;

    @Async
    @Transactional
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void logEvent(MemberLogDto memberLogDto) {
        final MemberLog memberLog = memberLogDto.toEntity();
        memberLogRepository.save(memberLog);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;&amp;rarr; AFTER_COMMIT을&amp;nbsp;사용하여&amp;nbsp;트랜잭션이&amp;nbsp;끝난&amp;nbsp;후에&amp;nbsp;이벤트를&amp;nbsp;처리하도록&amp;nbsp;설정&amp;nbsp;&lt;br /&gt;@Async&amp;nbsp;와&amp;nbsp;함께&amp;nbsp;비동기&amp;nbsp;이벤트&amp;nbsp;처리를&amp;nbsp;사용하여&amp;nbsp;트랜잭션이&amp;nbsp;끝난&amp;nbsp;후&amp;nbsp;별도의&amp;nbsp;스레드에서&amp;nbsp;이벤트를&amp;nbsp;처리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. @Async 설정 수정&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
public Executor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(2);
    taskExecutor.setMaxPoolSize(50);
    taskExecutor.setQueueCapacity(500);
    taskExecutor.setThreadNamePrefix(&quot;Async-&quot;);
    taskExecutor.setTaskDecorator(new CustomDecorator());
    return taskExecutor;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr; 스레드&amp;nbsp;풀&amp;nbsp;관련&amp;nbsp;설정&amp;nbsp;추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class CustomDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(@NotNull Runnable runnable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return () -&amp;gt; {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();  // 꼭 정리!!
            }
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; try-finally&amp;nbsp;작업을&amp;nbsp;통해&amp;nbsp;비동기&amp;nbsp;작업이&amp;nbsp;완료된&amp;nbsp;후,&amp;nbsp;RequestAttributes를&amp;nbsp;정리하는&amp;nbsp;로직을&amp;nbsp;추가했음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;  테스트 결과&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Heap: 안정적, GC도 Minor에서 반복 처리 잘됨&lt;/li&gt;
&lt;li&gt;Old 영역 증가 없음&lt;/li&gt;
&lt;li&gt;테스트 종료 후 스레드 수 정상 복구&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XJfVj/btsNTGkXOtv/Efc07LXnlQOIkXvs6yyMVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XJfVj/btsNTGkXOtv/Efc07LXnlQOIkXvs6yyMVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XJfVj/btsNTGkXOtv/Efc07LXnlQOIkXvs6yyMVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXJfVj%2FbtsNTGkXOtv%2FEfc07LXnlQOIkXvs6yyMVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;895&quot; height=&quot;511&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Fm1S/btsNSNyk2ic/KLQ58M2D0k2Bm2CUAH5HbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Fm1S/btsNSNyk2ic/KLQ58M2D0k2Bm2CUAH5HbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Fm1S/btsNSNyk2ic/KLQ58M2D0k2Bm2CUAH5HbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Fm1S%2FbtsNSNyk2ic%2FKLQ58M2D0k2Bm2CUAH5HbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;523&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;  근데 찝찝한 점 하나&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 OOM이 났던 시점에는 @Async가 적용되지 않았다는 것&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 중 데드락 이슈를 고치면서 @Async가 붙었기 때문에, 이 조치가 &lt;b&gt;진짜 OOM을 해결한 것인지는 아직 확신할 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 외에도 @Async를 사용하는 코드가 서비스 곳곳에 있었기 때문에&lt;br /&gt;전체적으로 누적된 메모리가 있었을 가능성은 매우 크다고 판단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 결국 실제 PR 환경에 배포하고 나서 계속 &lt;b&gt;메모리 추이 관찰 예정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  번외&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링&amp;nbsp;도구를&amp;nbsp;보면,&amp;nbsp;기동&amp;nbsp;후&amp;nbsp;아무것도&amp;nbsp;하지&amp;nbsp;않아도&amp;nbsp;주기적으로&amp;nbsp;힙&amp;nbsp;메모리가&amp;nbsp;쌓이고&amp;nbsp;줄어드는&amp;nbsp;모양이&amp;nbsp;반복된다?&amp;nbsp;(like&amp;nbsp;바트&amp;nbsp;머리)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/90cxj/btsNS7wxVyY/RcYJAkfQonLW8oIjqn5Nvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/90cxj/btsNS7wxVyY/RcYJAkfQonLW8oIjqn5Nvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/90cxj/btsNS7wxVyY/RcYJAkfQonLW8oIjqn5Nvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F90cxj%2FbtsNS7wxVyY%2FRcYJAkfQonLW8oIjqn5Nvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;263&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이건&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;JVM 내부의 백그라운드 GC 최적화&lt;/b&gt;로 인해 자연스럽게 발생하는 패턴이다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>@Async</category>
      <category>Java</category>
      <category>JVM 메모리</category>
      <category>OOM</category>
      <category>OutOfMemoryError</category>
      <category>RequestContextHolder</category>
      <category>Spring Boot</category>
      <category>ThreadPoolTaskExecutor</category>
      <category>데드락</category>
      <category>메모리 누수</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/78</guid>
      <comments>https://born2bedeveloper.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 12 May 2025 09:31:17 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch StepScope 사용 시 Entity Not Mapped 오류 해결법 (다중 데이터소스 환경)</title>
      <link>https://born2bedeveloper.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Spring Batch에서 Entity Not Mapped 오류 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch에서 여러 데이터소스를 설정하고,&lt;br /&gt;그 중 하나(subEntityManagerFactory)를 이용해서 JpaPagingItemReader로 조회하려고 했다.&lt;br /&gt;하지만 Batch 실행 중 다음 에러가 발생했다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;org.hibernate.hql.internal.ast.QuerySyntaxException: EntityA is not mapped
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 Entity 매핑 문제를 의심했지만, 엔티티도 제대로 선언되어 있었고, Config도 문제없었다.&lt;br /&gt;결국 원인은 EntityManagerFactory 주입 방식 문제였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-end=&quot;128&quot; data-start=&quot;95&quot; data-ke-size=&quot;size23&quot;&gt;  Bean 주입 시점과 프록시로 발생한 문제 분석&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batch는 기본적으로 ApplicationContext 기동 시점에 모든 빈을 싱글톤으로 생성한다.&lt;br /&gt;그런데 @StepScope를 사용하면 &lt;b&gt;Step 실행 시점&lt;/b&gt;에 빈이 생성된다.&lt;br /&gt;이때 필드에 주입되어 있던 EntityManagerFactory는 실제 객체가 아니라 &lt;b&gt;프록시(Proxy)&lt;/b&gt; 객체다.&lt;br /&gt;문제는 이 프록시가 내부적으로 기본 EntityManagerFactory를 바라볼 수도 있다는 점.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@StepScope
public JpaPagingItemReader&amp;lt;EntityA&amp;gt; entityReader() {
    ...
    .entityManagerFactory(entityManagerFactory) // ❌ 프록시 주입 &amp;rarr; 실제 대상이 기본 DB일 수 있음
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 JpaPagingItemReader는 기본 DB를 바라보게 되고, EntityA를 찾지 못해 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Entity A는 기본 DB가 아닌 멀티데이터소스 설정으로 인해 다른 DB에 존재하고 있으니!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;진짜 핵심은 StepScope의 생성 타이밍 때문에, 필드 주입은 신뢰할 수 없다는 것.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;177&quot; data-start=&quot;130&quot; data-ke-size=&quot;size23&quot;&gt;  StepScope란? Spring Batch에서의 Bean 생명주기 설명&lt;/h3&gt;
&lt;h2 data-end=&quot;215&quot; data-start=&quot;195&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 실행 시점&lt;/b&gt;에 Bean을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Step마다 새로운 객체가 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 ItemReader, ItemProcessor, ItemWriter 같은 &lt;b&gt;Step 내부 컴포넌트&lt;/b&gt;에 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StepExecutionContext&lt;/b&gt; 범위 안에서 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;396&quot; data-start=&quot;387&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ 사용하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 Step 실행마다 다른 데이터를 주입하고 싶을 때 (예: JobParameter, 현재 날짜 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Step 실행 전에는 Bean을 생성하지 않고, Step 실행 시점에 생성하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;519&quot; data-start=&quot;506&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ Bean 생성 타이밍&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 기동 시점 &amp;rarr; Proxy 객체만 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Step 실행 시점 &amp;rarr; 실제 Bean 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 코드&lt;/h3&gt;
&lt;pre class=&quot;d&quot;&gt;&lt;code&gt;@Slf4j
@Configuration
@RequiredArgsConstructor
public class SampleBatchJobConfig {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Qualifier(&quot;subEntityManagerFactory&quot;)
    private final EntityManagerFactory entityManagerFactory;

    @Bean
    @StepScope
    public JpaPagingItemReader&amp;lt;EntityA&amp;gt; entityReader() {
        Map&amp;lt;String, Object&amp;gt; params = new HashMap&amp;lt;&amp;gt;();
        params.put(&quot;targetDate&quot;, LocalDate.now());
        params.put(&quot;status&quot;, StatusType.ACTIVE);

        return new JpaPagingItemReaderBuilder&amp;lt;EntityA&amp;gt;()
                .name(&quot;entityReader&quot;)
                .entityManagerFactory(entityManagerFactory) // 문제 발생 지점
                .pageSize(100)
                .queryString(&quot;&quot;&quot;
                    SELECT e
                    FROM EntityA e
                    WHERE e.expireDate &amp;lt; :targetDate
                    AND e.status = :status
                &quot;&quot;&quot;)
                .parameterValues(params)
                .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선된 코드&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Slf4j
@Configuration
@RequiredArgsConstructor
public class SampleBatchJobConfig {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    @StepScope
    public JpaPagingItemReader&amp;lt;EntityA&amp;gt; entityReader(
            @Qualifier(&quot;subEntityManagerFactory&quot;) EntityManagerFactory subEntityManagerFactory) {

        Map&amp;lt;String, Object&amp;gt; params = new HashMap&amp;lt;&amp;gt;();
        params.put(&quot;targetDate&quot;, LocalDate.now());
        params.put(&quot;status&quot;, StatusType.ACTIVE);

        return new JpaPagingItemReaderBuilder&amp;lt;EntityA&amp;gt;()
                .name(&quot;entityReader&quot;)
                // ✅ 메소드 파라미터 주입
                .entityManagerFactory(subEntityManagerFactory) 
                .pageSize(100)
                .queryString(&quot;&quot;&quot;
                    SELECT e
                    FROM EntityA e
                    WHERE e.expireDate &amp;lt; :targetDate
                    AND e.status = :status
                &quot;&quot;&quot;)
                .parameterValues(params)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수정된 코드에서는 @StepScope 빈에서 &lt;b&gt;필드 주입&lt;/b&gt; 대신 &lt;b&gt;메서드 파라미터 주입&lt;/b&gt;을 사용하여, EntityManagerFactory를 정확하게 주입할 수 있게 처리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 프록시 객체가 아닌 실제 subEntityManagerFactory가 주입되도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-end=&quot;371&quot; data-start=&quot;336&quot; data-ke-size=&quot;size23&quot;&gt;  StepScope에서 메서드 주입을 통한 문제 해결&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@StepScope 빈에서는 &lt;b&gt;필드 주입 대신 메소드 파라미터 주입&lt;/b&gt;을 사용한다.&lt;/li&gt;
&lt;li&gt;@Qualifier를 메소드 파라미터에 붙여서 원하는 EntityManagerFactory를 정확히 지정한다.&lt;/li&gt;
&lt;li&gt;프록시가 개입하지 않고, 실제 객체가 정확히 주입된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-end=&quot;412&quot; data-start=&quot;373&quot; data-ke-size=&quot;size23&quot;&gt;  StepScope와 JobScope의 차이점 및 사용 시점&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 99.8837%; height: 236px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;StepScope&lt;/td&gt;
&lt;td&gt;JobScope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;생성 시점&lt;/td&gt;
&lt;td&gt;Step 실행 시점&lt;/td&gt;
&lt;td&gt;Job 시작 시점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스코프 범위&lt;/td&gt;
&lt;td&gt;Step마다 새로운 객체 생성&lt;/td&gt;
&lt;td&gt;Job마다 하나의 객체 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주 사용처&lt;/td&gt;
&lt;td&gt;Reader, Processor, Writer 같은 Step 단위 컴포넌트&lt;/td&gt;
&lt;td&gt;Job 전체 설정, 공통 값 공유할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;매 Step마다 날짜, 상태값 다르게 쓰고 싶을 때&lt;/td&gt;
&lt;td&gt;전체 Job에서 파라미터를 공유할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한마디로 정리하면&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@StepScope : Step이 실행될 때마다 다른 값을 쓸 수 있게 해주는 스코프&lt;/li&gt;
&lt;li&gt;@JobScope : Job 단위로 한 번만 초기화되고 여러 Step이 공유하는 스코프&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@StepScope가 붙은 Bean에서는 무조건 &lt;b&gt;메소드 파라미터 주입&lt;/b&gt;을 쓰자.&lt;/li&gt;
&lt;li&gt;특히 &lt;b&gt;다중 데이터소스&lt;/b&gt; 환경에서는, 어떤 EntityManagerFactory가 주입되는지 명확히 해야 한다.&lt;/li&gt;
&lt;li&gt;이걸 놓치면 Hibernate가 기본 DB를 바라보다가 원하는 Entity를 찾지 못해서 터진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;느낀 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 단순한 엔티티 매핑 문제라고 생각했다. (이 것 때문에 진짜 오만가지 설정을 다시 해보고 난리였음..ㅠ)&lt;br /&gt;근데 진짜 원인은 Bean의 생성 시점 차이였고, 그걸 이해하니까 모든 게 풀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batch + 다중 데이터소스 + StepScope 조합은 &lt;b&gt;StepScope의 동작 방식을 이해하고 잘 써야&lt;/b&gt;&amp;nbsp;한다.&lt;br /&gt;이제부터는 @StepScope 쓰면 &lt;b&gt;무조건 파라미터 주입을 사용하는 습관을 들이자.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>springbatch #springbatch에러 #jpapagingitemreader #stepscope #springboot #entitymanagerfactory #querysyntaxexception #entitynotmapped #다중데이터소스 #멀티데이터소스 #springbatchconfig #hibernate에러 #jpareader #springbatchreader #springbootjpa #bean주입문제 #프록시객체 #bean생성시점 #</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/77</guid>
      <comments>https://born2bedeveloper.tistory.com/77#entry77comment</comments>
      <pubDate>Tue, 6 May 2025 19:23:20 +0900</pubDate>
    </item>
    <item>
      <title>자바의 로봇청소기, 가비지 컬렉션</title>
      <link>https://born2bedeveloper.tistory.com/76</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QXePr/btsIsbezx3L/yIym8Mlyu0xOrgaK5qaubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QXePr/btsIsbezx3L/yIym8Mlyu0xOrgaK5qaubk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QXePr/btsIsbezx3L/yIym8Mlyu0xOrgaK5qaubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQXePr%2FbtsIsbezx3L%2FyIym8Mlyu0xOrgaK5qaubk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;270&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;자바에는 가비지 컬렉션(Garbage Collection) 이라는 기능이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영문 뜻으로 짐작할 수 있듯이 사용하는 객체의 메모리를 주기적으로 검사해서 청소해주는 일을 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;C와 C++같은 Unmanaged language는 free()와 같은 함수를 사용해서 직접 메모리를 해제하는 반면&lt;br /&gt;Garbage Collector는 이러한 번거로운 일을 대신 해주고 있는 것이죠! (like 로봇청소기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1457&quot; data-origin-height=&quot;1457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/48Bee/btsItHDi19z/kXw5qn558rVGhrBun1dHs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/48Bee/btsItHDi19z/kXw5qn558rVGhrBun1dHs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/48Bee/btsItHDi19z/kXw5qn558rVGhrBun1dHs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F48Bee%2FbtsItHDi19z%2FkXw5qn558rVGhrBun1dHs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;344&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1457&quot; data-origin-height=&quot;1457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-pm-slice=&quot;2 1 []&quot; data-panel-color=&quot;#E3FCEF&quot; data-panel-icon-text=&quot;♻️&quot; data-panel-icon-id=&quot;267b&quot; data-panel-icon=&quot;:recycle:&quot; data-panel-type=&quot;custom&quot;&gt;
&lt;div data-panel-content=&quot;true&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ 가비지 컬렉션 ( vs 가비지 컬렉터) &lt;/b&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 헷갈릴 수 있는 두 용어를 정리하고 가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;가비지 컬렉션 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 관리 프로세스로, 더 이상 사용되지 않는 메모리(객체)를 자동으로 찾아서 회수하는 작업 &lt;br /&gt;&amp;rarr; 청소&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;가비지 컬렉터&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가비지 컬렉션을 수행하는 구성요소. 불필요한 메모리를 실제로 찾아내고 해제하는 주체&lt;br /&gt;&amp;rarr; 청소기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ 가비지 컬렉션의 제거 기준&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;그렇담 가비지 컬렉션은 어떠한 기준으로 선정된 객체들을 지워버릴까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 도달성(Reachability) 이라는 개념을 통해 판단합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reachable : 객체가 참조되고 있는 상태&lt;/li&gt;
&lt;li&gt;Unreachable&amp;nbsp;: 객체가 참조되고 있지 않은 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 유효한 참조가 없다면(Unreachable) 수거 대상으로 판단하고 지워버리게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw4hyk/btsIr8WteDQ/e1aVSVJM9el6959HICKG9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw4hyk/btsIr8WteDQ/e1aVSVJM9el6959HICKG9K/img.png&quot; data-alt=&quot;청소좌..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw4hyk/btsIr8WteDQ/e1aVSVJM9el6959HICKG9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw4hyk%2FbtsIr8WteDQ%2Fe1aVSVJM9el6959HICKG9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;152&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;청소좌..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ JVM 메모리에서의 객체들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p65GY/btsIszGhfoP/mTu7QkuwDRIKPNUvXqGsSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p65GY/btsIszGhfoP/mTu7QkuwDRIKPNUvXqGsSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p65GY/btsIszGhfoP/mTu7QkuwDRIKPNUvXqGsSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp65GY%2FbtsIszGhfoP%2FmTu7QkuwDRIKPNUvXqGsSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;965&quot; height=&quot;411&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;Heap 영역 : 실질적으로 객체들이 생성되는 곳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외의 Stack과 Method 영역에서는 Heap영역에서 생성된 객체의 주소를 참조하는 형식으로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 다음과 같은 상황에서 Heap에 있는 객체를 참조하는 변수들이 삭제됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;변수의 Scope를 벗어남&lt;/b&gt; &lt;br /&gt;&amp;rarr; 메소드 안에서 선언된 지역변수가 있을 때, 해당 메소드가 종료됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조가 다른 객체로 변경됨&lt;/b&gt; &lt;br /&gt;&amp;rarr; 다른 Heap 영역의 객체를 참조하게됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조 변수가 null로 설정&lt;/b&gt; &lt;br /&gt;&amp;rarr; 이전에 참조하면 객체에 대한 연결이 끊어짐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클래스 인스턴스의 소멸 &lt;/b&gt;&lt;br /&gt;&amp;rarr; 객체가 속한 클래스의 인스턴스가 소멸되면 내부의 참조변수도 같이 소멸&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 2번 객체와 같이 Unreachable상태가 되며, 가비지 컬렉터의 수거 대상이 됩니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4yr1/btsIsHjN1lN/CrRWPlyUc4okFev5NHy8W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4yr1/btsIsHjN1lN/CrRWPlyUc4okFev5NHy8W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4yr1/btsIsHjN1lN/CrRWPlyUc4okFev5NHy8W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4yr1%2FbtsIsHjN1lN%2FCrRWPlyUc4okFev5NHy8W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;182&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ 가비지 컬렉션의 청소 과정&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;panel&amp;quot;,{&amp;quot;panelType&amp;quot;:&amp;quot;custom&amp;quot;,&amp;quot;panelIcon&amp;quot;:&amp;quot;:recycle:&amp;quot;,&amp;quot;panelIconId&amp;quot;:&amp;quot;267b&amp;quot;,&amp;quot;panelIconText&amp;quot;:&amp;quot;♻️&amp;quot;,&amp;quot;panelColor&amp;quot;:&amp;quot;#E3FCEF&amp;quot;}]&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 가비지 컬렉션은 어떠한 방식으로 청소를 하는지 좀 더 자세히 단계별로 살펴보겠습니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Mark And Sweep&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가비지 컬렉션이 동작하는 아주 기초적인 청소과정으로, 다양한 GC에서 객체를 솎아내기 위해 사용되는 내부 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;1 1 [&amp;quot;decisionList&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;5348f972-8435-4c45-98ef-0b12ef6b14b6&amp;quot;}]&quot; data-decision-state=&quot;DECIDED&quot; data-decision-local-id=&quot;6a4577cf-83c2-4749-8830-8ef1c2b42094&quot;&gt;1. Marking (마킹)&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7vSLP/btsItRZ2F2S/mkXilIPjzFnvykDgqnFxU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7vSLP/btsItRZ2F2S/mkXilIPjzFnvykDgqnFxU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7vSLP/btsItRZ2F2S/mkXilIPjzFnvykDgqnFxU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7vSLP%2FbtsItRZ2F2S%2FmkXilIPjzFnvykDgqnFxU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;255&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
GC는 모든 객체를 순회하며 참조된 객체와 참조되지 않은 객체를 구분하여 마킹합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;1 1 [&amp;quot;decisionList&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;66a8db76-0d18-4dc9-8c2a-e2df1698fbe0&amp;quot;}]&quot; data-decision-state=&quot;DECIDED&quot; data-decision-local-id=&quot;71c4001e-5744-4efa-a706-4729ddcf74da&quot;&gt;2. Sweep (삭제)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;21.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLowNe/btsIt5qf47u/JLDxKxL8QQNBspaycqumdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLowNe/btsIt5qf47u/JLDxKxL8QQNBspaycqumdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLowNe/btsIt5qf47u/JLDxKxL8QQNBspaycqumdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLowNe%2FbtsIt5qf47u%2FJLDxKxL8QQNBspaycqumdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;246&quot; data-filename=&quot;21.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;참조되지 않은 객체들(Unreachable)을 Heap에서 제거합니다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;1 1 [&amp;quot;decisionList&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;856b1e0c-cc90-435a-bb68-7b84b0885fda&amp;quot;}]&quot; data-decision-state=&quot;DECIDED&quot; data-decision-local-id=&quot;140d7a27-5576-4fc8-98f5-2e9eb68a2cb0&quot;&gt;2-a . Compact (압축)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/trIDq/btsIs5dERv2/kYu5nTOiyiIyEuK4rq6Enk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/trIDq/btsIs5dERv2/kYu5nTOiyiIyEuK4rq6Enk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/trIDq/btsIs5dERv2/kYu5nTOiyiIyEuK4rq6Enk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtrIDq%2FbtsIs5dERv2%2FkYu5nTOiyiIyEuK4rq6Enk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;235&quot; data-filename=&quot;20.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;(GC 종류에 따라 해당 단계가 포함되기도, 안되기도함 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f603&quot; data-emoji-short-name=&quot;:smiley:&quot;&gt; &lt;/span&gt; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sweep 단계를 통해 신나는 대청소 후 분산된 객체들을 Heap 시작 주소로 모아 압축합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 데 모아두면 새로운 메모리 할당이 훨씬 쉽고 빨라지겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;1 1 [&amp;quot;decisionList&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;7c769b79-c6f6-4c32-8f65-e0092c0672db&amp;quot;}]&quot; data-decision-state=&quot;DECIDED&quot; data-decision-local-id=&quot;38ac1c3f-9283-4329-b69c-4e59559cd625&quot;&gt;GC의 Root Space&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;19.gif&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUo5Iy/btsIs6jkwte/jjSeXYLokkxKbU1DBIkkqk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUo5Iy/btsIs6jkwte/jjSeXYLokkxKbU1DBIkkqk/img.gif&quot; data-alt=&quot;https://deepu.tech/memory-management-in-programming/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUo5Iy/btsIs6jkwte/jjSeXYLokkxKbU1DBIkkqk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bUo5Iy/btsIs6jkwte/jjSeXYLokkxKbU1DBIkkqk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;441&quot; data-filename=&quot;19.gif&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://deepu.tech/memory-management-in-programming/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;Mark And Sweep(청소)를 진행하기 위해서, GC는 루트 공간을 시작으로 참조를 통해 도달할 수 있는 모든 객체들을 &amp;ldquo;살아있는&amp;ldquo; 객체로 간주하고, 이외의 죽은(ㄷㄷ)녀석들을 청소하는 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;18.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkRybw/btsItQUn7wC/DvMlMhOceEJ56n1u0M78H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkRybw/btsItQUn7wC/DvMlMhOceEJ56n1u0M78H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkRybw/btsItQUn7wC/DvMlMhOceEJ56n1u0M78H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkRybw%2FbtsItQUn7wC%2FDvMlMhOceEJ56n1u0M78H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;308&quot; data-filename=&quot;18.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;루트 공간(Root Space)은 다음과 같습니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack의 로컬 변수&lt;/li&gt;
&lt;li&gt;Method Area의 Static 변수&lt;/li&gt;
&lt;li&gt;Native Method Stack의 JNI 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2D9gj/btsItXy7omT/wRb8ylk9V4Aas66XCnf881/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2D9gj/btsItXy7omT/wRb8ylk9V4Aas66XCnf881/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2D9gj/btsItXy7omT/wRb8ylk9V4Aas66XCnf881/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2D9gj%2FbtsItXy7omT%2FwRb8ylk9V4Aas66XCnf881%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;219&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ 가비지 컬렉션의 실제 동작 과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;아래는 Runtime Data Area에 대한 그림입니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f600&quot; data-emoji-short-name=&quot;:grinning:&quot;&gt; &lt;/span&gt; &lt;br /&gt;그게 무엇인고 하면 JVM이 프로그램을 수행하기 위해 OS로부터 할당받는 메모리라고 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 Heap과 Method Area는 모든 스레드가 공유하는 영역이죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P1AIr/btsIthEW623/PIvdRJtgkA6hLcyjvFQa80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P1AIr/btsIthEW623/PIvdRJtgkA6hLcyjvFQa80/img.png&quot; data-alt=&quot;https://jithub.tistory.com/40 자세한 내용은 이곳에서  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P1AIr/btsIthEW623/PIvdRJtgkA6hLcyjvFQa80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP1AIr%2FbtsIthEW623%2FPIvdRJtgkA6hLcyjvFQa80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;511&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://jithub.tistory.com/40 자세한 내용은 이곳에서  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;가비지 컬렉션은 &amp;lsquo;객체의 메모리&amp;rsquo;를 주기적으로 청소합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;가비지 컬렉션의 대상이 되는 공간&lt;/b&gt;은 실제 객체가 존재하는 공간인 &lt;b&gt;힙(heap)영역입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇담 실제 힙 메모리의 구조는 어떻게 되는지 자세하게 파헤쳐 봅시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ GC의 weak generational hypothesis&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dO1WhC/btsIuuJ09MM/C1KuDdP7mZ0sflxxWKvOt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dO1WhC/btsIuuJ09MM/C1KuDdP7mZ0sflxxWKvOt0/img.png&quot; data-alt=&quot;https://docs.oracle.com/en/java/javase/16/gctuning/garbage-collector-implementation.html#GUID-23844E39-7499-400C-A579-032B68E53073&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dO1WhC/btsIuuJ09MM/C1KuDdP7mZ0sflxxWKvOt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdO1WhC%2FbtsIuuJ09MM%2FC1KuDdP7mZ0sflxxWKvOt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;459&quot; height=&quot;301&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.oracle.com/en/java/javase/16/gctuning/garbage-collector-implementation.html#GUID-23844E39-7499-400C-A579-032B68E53073&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;가비지 컬렉션의 수행(mark and sweep!)에서 매번 추적을 진행한다면, 상당한 부하가 있을겁니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그래프는 대부분의 응용 프로그램들의 object 수명을 나타낸 그래프인데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;x축 : 측정된 object의 lifetime &lt;br /&gt;y축 : lifetime이 있는 개체의 총 Byte&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결과를 다음 사실을 알 수 있습니다!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;대부분의 객체는 금방 소멸한다. (접근 불가능한 상태)&lt;/li&gt;
&lt;li&gt;오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 weak generational hypothesis 이라고 합니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;14.png&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dj8G1o/btsIt7nZpsq/xmsNpa0aKiF54wMwvKvCk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dj8G1o/btsIt7nZpsq/xmsNpa0aKiF54wMwvKvCk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dj8G1o/btsIt7nZpsq/xmsNpa0aKiF54wMwvKvCk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdj8G1o%2FbtsIt7nZpsq%2FxmsNpa0aKiF54wMwvKvCk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;308&quot; height=&quot;226&quot; data-filename=&quot;14.png&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;힙 메모리 구조는 어디가고 이 가설을 난데없이 먼저 말하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 가설에 대한 전제를 토대로 Heap 영역이 처음 설계되었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻&lt;span&gt; &lt;/span&gt;Heap의 Young과 Old 영역&lt;br /&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;효율적인 메모리 관리를 위해, 객체의 생존 기간에 때라 두 가지 물리적 영역으로 나눴습니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;1 1 []&quot; data-ke-style=&quot;style2&quot;&gt;초기(Java7)에는 Perm 영역도 존재했지만, &lt;br /&gt;Java8버전 이후에는 Native Method Stack에 편입되어 사라집니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9IDFl/btsIsOiQ7kp/5mZzJeywjljMZz9RoL3Mk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9IDFl/btsIsOiQ7kp/5mZzJeywjljMZz9RoL3Mk1/img.png&quot; data-alt=&quot;https://aws.amazon.com/blogs/big-data/understanding-the-jvmmemorypressure-metric-changes-in-amazon-opensearch-service/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9IDFl/btsIsOiQ7kp/5mZzJeywjljMZz9RoL3Mk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9IDFl%2FbtsIsOiQ7kp%2F5mZzJeywjljMZz9RoL3Mk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;312&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://aws.amazon.com/blogs/big-data/understanding-the-jvmmemorypressure-metric-changes-in-amazon-opensearch-service/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;Young&amp;nbsp;영역(Young&amp;nbsp;Generation)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새롭게 생성된 객체가 할당(Allocation)되는 영역&lt;/li&gt;
&lt;li&gt;대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.&lt;/li&gt;
&lt;li&gt;Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Old 영역(Old Generation)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역&lt;/li&gt;
&lt;li&gt;Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.&lt;/li&gt;
&lt;li&gt;Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC 또는 Full GC라고 부른다.&lt;/li&gt;
&lt;li&gt;큰 객체들은 바로 Old 영역에 할당됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ Young 영역의 분리 &amp;rarr; Eden, Survival0, Survival1&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size16&quot;&gt;더더더더욱 효율적인 GC를 위해 Young 영역도 다음과 같이 세가지로 나눕니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Eden&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new를 통해 새로 생성된 객체가 위치.&lt;/li&gt;
&lt;li&gt;정기적인 쓰레기 수집 후 살아남은 객체들은 Survivor 영역으로 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Survivor 0 / Survivor 1&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소 1번의 GC 이상 살아남은 객체가 존재하는 영역&lt;/li&gt;
&lt;li&gt;Survivor 영역에는 Survivor 0 또는 Survivor 1 둘 중 하나에는 꼭 비어 있어야 하는 특별한 규칙이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻&lt;span&gt; Minor GC 과정 (in young Generation)&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:1},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;1. 먼저, 새로운 객체가 eden 영역에 할당됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Doo8k/btsItiYdaFQ/Et3lU37ip6KjQGVztqTOf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Doo8k/btsItiYdaFQ/Et3lU37ip6KjQGVztqTOf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Doo8k/btsItiYdaFQ/Et3lU37ip6KjQGVztqTOf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDoo8k%2FbtsItiYdaFQ%2FEt3lU37ip6KjQGVztqTOf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;165&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:2},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;2. eden공간이 가득 차면 GC가 시작됩니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/On0dX/btsIrIcCde3/fCWzDIqgVdE7j0tYAywNcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/On0dX/btsIrIcCde3/fCWzDIqgVdE7j0tYAywNcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/On0dX/btsIrIcCde3/fCWzDIqgVdE7j0tYAywNcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOn0dX%2FbtsIrIcCde3%2FfCWzDIqgVdE7j0tYAywNcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;386&quot; height=&quot;164&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:3},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;3. 마킹작업을 통해 참조된 객체들은 첫 번째 생존공간 (S0)으로 이동합니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebHwjm/btsItlmXOrx/E8EzdsGD0Y4c6HgH06GBBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebHwjm/btsItlmXOrx/E8EzdsGD0Y4c6HgH06GBBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebHwjm/btsItlmXOrx/E8EzdsGD0Y4c6HgH06GBBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebHwjm%2FbtsItlmXOrx%2FE8EzdsGD0Y4c6HgH06GBBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;230&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;이때 살아남은 녀석들의 age값이 1씩 증가합니다.&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;1 1 []&quot; data-ke-style=&quot;style2&quot;&gt;age?&lt;br /&gt;객체가 Survivor 영역에서 살아남은 횟수를 의미. Object Header에 기록된다.&lt;br /&gt;age값이 특정 임계값에 다다르면 Old 영역으로 이동한다.&lt;br /&gt;가장 일반적인 HotSpot JVM의 의 경우는 31이다. (객체 헤더의 age 영역이 6bit임)&lt;/blockquote&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:4},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;4. 다음 마이너 GC단계에서도 Eden이 가득차면 3번과 같이 생존공간으로 이동됩니다!&lt;br /&gt;이 때, 기존 S0공간에 있던 객체중 생존 + Eden에서 생존한 객체들은 모두 S1 공간으로 이동하게 됩니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHLAoC/btsIs2H4SFa/gatBnT97yHtWofSEt30kO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHLAoC/btsIs2H4SFa/gatBnT97yHtWofSEt30kO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHLAoC/btsIs2H4SFa/gatBnT97yHtWofSEt30kO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHLAoC%2FbtsIs2H4SFa%2FgatBnT97yHtWofSEt30kO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;459&quot; height=&quot;275&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:5},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;5. 이후 Eden 영역이 가득 차면 4번과 동일한 프로세스가 반복됩니다.&lt;br /&gt;다만, 이번엔 S1이 아닌 S0 공간으로 이동하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXhUs4/btsIsxV3kFG/XULVJ7OHjHSjJCSYUsS3b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXhUs4/btsIsxV3kFG/XULVJ7OHjHSjJCSYUsS3b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXhUs4/btsIsxV3kFG/XULVJ7OHjHSjJCSYUsS3b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXhUs4%2FbtsIsxV3kFG%2FXULVJ7OHjHSjJCSYUsS3b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;266&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;이렇듯 GC가 발생할 때마다 (Eden영역의 공간이 가득차면) 다음 일이 반복되는 프로세스를 보여줍니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Eden에서 살아남은 객체가 &amp;lsquo;전환된 Survivor공간으로 이동되고, age가 1 증가&amp;rsquo;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기존 Survivor공간에서도 살아남은 객체가 있으면 &amp;lsquo;전환된 Survivor공간으로 이동되고, age가 1 증가'&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 경우에 해당하면 age가 증가하고, 특정 임계치에 다다르면 드디어 Old 영역으로 이동하게 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이를 &lt;b&gt;Promotion&lt;/b&gt;이라고 부릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MDTuh/btsIt54Qgnx/QWfJOVofx4TtkVzPX6KeRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MDTuh/btsIt54Qgnx/QWfJOVofx4TtkVzPX6KeRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MDTuh/btsIt54Qgnx/QWfJOVofx4TtkVzPX6KeRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMDTuh%2FbtsIt54Qgnx%2FQWfJOVofx4TtkVzPX6KeRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;187&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ Major GC 과정 (in Old Generation) &lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;panel&amp;quot;,{&amp;quot;panelType&amp;quot;:&amp;quot;custom&amp;quot;,&amp;quot;panelIcon&amp;quot;:&amp;quot;:recycle:&amp;quot;,&amp;quot;panelIconId&amp;quot;:&amp;quot;267b&amp;quot;,&amp;quot;panelIconText&amp;quot;:&amp;quot;♻️&amp;quot;,&amp;quot;panelColor&amp;quot;:&amp;quot;#E3FCEF&amp;quot;}]&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:1},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;1. 계속해서 마이너GC가 진행되면서, 끈질기게 살아남은 객체들은 점점 나이를 먹게됩니다.&lt;br /&gt;만약 현재 예시의 JVM의 age임계값이 8이라고 가정하게 된다면, &lt;br /&gt;Promotion이 발생하여 Old영역으로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceTzZb/btsIs4semGN/BbsS1i9BiWwCgp4O2yCXI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceTzZb/btsIs4semGN/BbsS1i9BiWwCgp4O2yCXI1/img.png&quot; data-alt=&quot;(Tenured는 Old영역과 같은 의미입니당)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceTzZb/btsIs4semGN/BbsS1i9BiWwCgp4O2yCXI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceTzZb%2FbtsIs4semGN%2FBbsS1i9BiWwCgp4O2yCXI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;317&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(Tenured는 Old영역과 같은 의미입니당)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:2},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;2. 1번의 상황처럼 Promotion이 계속해서 발생할수록, &lt;br /&gt;Old 영역은 계속해서 나이든 객체들이 자리를 잡게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQkq6x/btsItTQ2dxX/gMAmJSpxfZf7AQ3P1PB1w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQkq6x/btsItTQ2dxX/gMAmJSpxfZf7AQ3P1PB1w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQkq6x/btsItTQ2dxX/gMAmJSpxfZf7AQ3P1PB1w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQkq6x%2FbtsItTQ2dxX%2FgMAmJSpxfZf7AQ3P1PB1w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;282&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;orderedList&amp;quot;,{&amp;quot;order&amp;quot;:3},&amp;quot;listItem&amp;quot;,null]&quot; data-ke-size=&quot;size16&quot;&gt;3. 그렇게 Old영역이 다 차게된다면, 해당 공간을 정리하고 압축하는 GC 과정이 진행됩니다.&lt;br /&gt;이것이 Major GC (Old 영역에서 진행되는 GC)입니다! &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f60e&quot; data-emoji-short-name=&quot;:sunglasses:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1spM/btsIsIQz2M0/MlrxVVYPNxvQSONQzvNBM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1spM/btsIsIQz2M0/MlrxVVYPNxvQSONQzvNBM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1spM/btsIsIQz2M0/MlrxVVYPNxvQSONQzvNBM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1spM%2FbtsIsIQz2M0%2FMlrxVVYPNxvQSONQzvNBM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;290&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Minor GC와 Major GC의 동작과정을 자세하게 알아봤습니다 :)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjFtOF/btsIt9TDNR9/LYmFosbKYka2ujVtfgtd4k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjFtOF/btsIt9TDNR9/LYmFosbKYka2ujVtfgtd4k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjFtOF/btsIt9TDNR9/LYmFosbKYka2ujVtfgtd4k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cjFtOF/btsIt9TDNR9/LYmFosbKYka2ujVtfgtd4k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;272&quot; data-filename=&quot;3.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;♻ &lt;b&gt;Stop-The-World&lt;/b&gt; &lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;panel&amp;quot;,{&amp;quot;panelType&amp;quot;:&amp;quot;custom&amp;quot;,&amp;quot;panelIcon&amp;quot;:&amp;quot;:recycle:&amp;quot;,&amp;quot;panelIconId&amp;quot;:&amp;quot;267b&amp;quot;,&amp;quot;panelIconText&amp;quot;:&amp;quot;♻️&amp;quot;,&amp;quot;panelColor&amp;quot;:&amp;quot;#E3FCEF&amp;quot;}]&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;앞서 Heap 구조에 대해 설명할때 언급했듯이 Young 영역은 일반적으로 Old 영역보다 크기가 작습니다.&lt;br /&gt;GC가 보통 0.5초에서 1초 사이에 끝나기 때문에 Minor GC는 애플리케이션에 크게 영향을 주지 않습니다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Old 영역의 Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 여기서 Stop-The-World 가발생!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stop-the-World 과정&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;자바 가상 머신(JVM)에서 가비지 컬렉션(Garbage Collection, GC)을 수행할 때 발생하는 현상입니다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stop-the-World가 발생하면, JVM 내에서 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드가 가비지 컬렉션 작업이 완료될 때까지 멈춥니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 가비지 컬렉터가 메모리 내의 객체들을 안전하게 검사하고, 사용되지 않는 객체를 제거하며, 필요시 메모리를 재배치(Compaction)할 수 있도록 하기 위해 필요하지요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, Major GC같은 경우 처리속도가 길기 때문에 stop-the-world 과정에서 멈춤 현상의 지속시간이 길어지고.. 애플리케이션 이용에 차질이 생기게 됩니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f62b&quot; data-emoji-short-name=&quot;:tired_face:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAthrP/btsIseCuHiz/nG3RFJOSPA3qUQK7OL5Nw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAthrP/btsIseCuHiz/nG3RFJOSPA3qUQK7OL5Nw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAthrP/btsIseCuHiz/nG3RFJOSPA3qUQK7OL5Nw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAthrP%2FbtsIseCuHiz%2FnG3RFJOSPA3qUQK7OL5Nw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;240&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;따라서 자바 개발진들은 Stop-the-World의 영향을 최소화하기 위해 끊임 없이 가비지 컬렉션 알고리즘을 발전 시켜왔습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 알고리즘에 대해서는.. 분량 문제로 따로 다뤄보도록 하겠습니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f913&quot; data-emoji-short-name=&quot;:nerd:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhQBs/btsIsZEwCqV/j0g7jRJR2gMjyT317OEVs1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhQBs/btsIsZEwCqV/j0g7jRJR2gMjyT317OEVs1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhQBs/btsIsZEwCqV/j0g7jRJR2gMjyT317OEVs1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/rhQBs/btsIsZEwCqV/j0g7jRJR2gMjyT317OEVs1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;205&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&quot;&gt;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720530382097&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Java Garbage Collection Basics&quot; data-og-description=&quot;Java Overview Java is a programming language and computing platform first released by Sun Microsystems in 1995. It is the underlying technology that powers Java programs including utilities, games, and business applications. Java runs on more than 850 mill&quot; data-og-host=&quot;www.oracle.com&quot; data-og-source-url=&quot;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&quot; data-og-url=&quot;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/prKcy/hyWvOieEIf/fqPubm66Gu5OKj6cTKiqNK/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/mIauA/hyWzsEyKqw/GWkxU2fmz67xEjvutUgL9K/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/0q0eb/hyWvOoXFxz/TRgyjfwJN5Hmu176taBKJk/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720&quot;&gt;&lt;a href=&quot;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/prKcy/hyWvOieEIf/fqPubm66Gu5OKj6cTKiqNK/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/mIauA/hyWzsEyKqw/GWkxU2fmz67xEjvutUgL9K/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/0q0eb/hyWvOoXFxz/TRgyjfwJN5Hmu176taBKJk/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Garbage Collection Basics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java Overview Java is a programming language and computing platform first released by Sun Microsystems in 1995. It is the underlying technology that powers Java programs including utilities, games, and business applications. Java runs on more than 850 mill&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot;&gt;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot;&gt;%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720530384633&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;☕ 가비지 컬렉션 동작 원리 &amp;amp; GC 종류   총정리&quot; data-og-description=&quot;Garbage Collection(GC) 이란? 가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객&quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot; data-og-url=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhqsze/hyWzrZXZh8/ciS9XbFwCpSzz3fXSfsZ6k/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/t7YAc/hyWvWHinuK/JjG7pLafK1tglE5RtgcN81/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/beDRYH/hyWzACBsJH/kaCQ7aWoY4ZTnko61mKiok/img.png?width=1200&amp;amp;height=728&amp;amp;face=0_0_1200_728&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhqsze/hyWzrZXZh8/ciS9XbFwCpSzz3fXSfsZ6k/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/t7YAc/hyWvWHinuK/JjG7pLafK1tglE5RtgcN81/img.png?width=800&amp;amp;height=485&amp;amp;face=0_0_800_485,https://scrap.kakaocdn.net/dn/beDRYH/hyWzACBsJH/kaCQ7aWoY4ZTnko61mKiok/img.png?width=1200&amp;amp;height=728&amp;amp;face=0_0_1200_728');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;☕ 가비지 컬렉션 동작 원리 &amp;amp; GC 종류   총정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Garbage Collection(GC) 이란? 가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@yyong3519/Garbage-Collection-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81&quot;&gt;https://velog.io/@yyong3519/Garbage-Collection-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720530388031&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Garbage Collection 모니터링&quot; data-og-description=&quot;출처&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@yyong3519/Garbage-Collection-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81&quot; data-og-url=&quot;https://velog.io/@yyong3519/Garbage-Collection-모니터링&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dxHJt1/hyWzAWVH6n/mH9r7ncVV9n4rB0SVonUZK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/I9SzM/hyWvWHinyx/sHwAcHWkIu9KTpLMwKQLR0/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/eTwJ3/hyWzAP8w2U/E3HanOiNUdtLTfeKt8KfZ0/img.png?width=852&amp;amp;height=696&amp;amp;face=0_0_852_696&quot;&gt;&lt;a href=&quot;https://velog.io/@yyong3519/Garbage-Collection-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@yyong3519/Garbage-Collection-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dxHJt1/hyWzAWVH6n/mH9r7ncVV9n4rB0SVonUZK/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/I9SzM/hyWvWHinyx/sHwAcHWkIu9KTpLMwKQLR0/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/eTwJ3/hyWzAP8w2U/E3HanOiNUdtLTfeKt8KfZ0/img.png?width=852&amp;amp;height=696&amp;amp;face=0_0_852_696');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Garbage Collection 모니터링&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;special thx to chatGPT! &lt;/u&gt;&lt;/b&gt;&lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f916&quot; data-emoji-short-name=&quot;:robot:&quot;&gt; &lt;/span&gt;&lt;u&gt; &lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>garbage collection</category>
      <category>GC</category>
      <category>Java</category>
      <category>JVM</category>
      <category>Major GC</category>
      <category>Minor GC</category>
      <category>stop the world</category>
      <category>가비지 컬렉션</category>
      <category>가비지컬렉션</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/76</guid>
      <comments>https://born2bedeveloper.tistory.com/76#entry76comment</comments>
      <pubDate>Tue, 9 Jul 2024 22:07:38 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security @EnabledWebSecurity 의 동작원리</title>
      <link>https://born2bedeveloper.tistory.com/75</link>
      <description>&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring Security?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size16&quot;&gt;Spring의 공식문서에 따르면, &lt;br /&gt;Spring Security는 강력하고 사용자 정의가 가능한 인증 및 액세스 제어 프레임워크입니다. &lt;br /&gt;(Spring 기반 애플리케이션 보안을 위한 사실상의 표준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 어플리케이션에 인증 및 권한 부여를 제공하는 데 중점을 둔 프레임 워크라고 할 수 있죠.&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;349&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div data-pm-slice=&quot;0 0 []&quot; data-width-type=&quot;pixel&quot; data-width=&quot;349&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csfYqT/btsH0PQzxfR/dssLBm0GbEAzrN84sK2NPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csfYqT/btsH0PQzxfR/dssLBm0GbEAzrN84sK2NPK/img.png&quot; data-alt=&quot;like 문지기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csfYqT/btsH0PQzxfR/dssLBm0GbEAzrN84sK2NPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsfYqT%2FbtsH0PQzxfR%2FdssLBm0GbEAzrN84sK2NPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;329&quot; height=&quot;134&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;like 문지기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 및 권한 부여에 대한 포괄적이고 확장 가능한 지원&lt;/li&gt;
&lt;li&gt;세션 고정, 클릭재킹, 크로스 사이트 요청 위조 등과 같은 공격으로부터 보호&lt;/li&gt;
&lt;li&gt;서블릿 API 통합&lt;/li&gt;
&lt;li&gt;Spring Web MVC와의 선택적 통합 등&amp;hellip; 다양한 특징을 가지고 있습니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@ 어노테이션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 제목의 EnabledWebSecurity앞에 붙어있는 @는 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 위해서는 메타데이터 라는 개념을 짚고 넘어갈 필요가 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;메타데이터란 데이터의 대한 속성정보로, 하위 레벨 데이터를 설명하는 또다른 데이터라고 보면 됩니다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도서관을 예시로 들어보자면 표제,저자,주제명,분류기호 등이 포함되어 있는 목록이 메타데이터의 속한다고 볼 수 있습니다 &lt;/b&gt;&lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어노테이션이란 이러한 메타데이터라고 볼 수 있는데요, 본래 주석이라는 의미를 가지고 있습니다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램 실행 관점에서보면 프로그램이 처리할 메인 데이터가 아니라 실행 과정에서 데이터를 어떻게 처리할것인지 알려주는 &lt;u&gt;서브 데이터라고 볼 수 있습니다!&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어노테이션은 크게 세 가지 용도로 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드 문법 에러 체크&amp;nbsp;&lt;/li&gt;
&lt;li&gt;코드 자동 생성 정보 제공&amp;nbsp;&lt;/li&gt;
&lt;li&gt;런타임시 특정 기능을 실행하는 정보 제공&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@EnabledWebSecurity&lt;/b&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@EnableWebSecurity&lt;/span&gt; 애노테이션은 Spring Security를 사용하는 웹 애플리케이션에서 보안 구성을 활성화하는 데 사용됩니다!&lt;br /&gt;이 애노테이션은 &lt;span&gt;WebSecurityConfigurerAdapter&lt;/span&gt;를 확장한 클래스를 정의하거나, &lt;span&gt;SecurityConfigurerAdapter&lt;/span&gt;를 사용하여 보안 구성을 정의하는 데 필요한 메서드를 구현하는 클래스에 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 RPM 코드에서 다음과 같이 사용되고 있지요&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;565&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEnFG7/btsH0NFhMBk/mw1XPs59T4kQ5QeneaBxtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEnFG7/btsH0NFhMBk/mw1XPs59T4kQ5QeneaBxtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEnFG7/btsH0NFhMBk/mw1XPs59T4kQ5QeneaBxtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEnFG7%2FbtsH0NFhMBk%2Fmw1XPs59T4kQ5QeneaBxtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;114&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 애노테이션을 사용하면 Spring Security가 제공하는 기본 보안 구성을 활성화하고, 웹 애플리케이션의 인증 및 권한 부여를 조절할 수 있습니다. 주로 사용자 인증, 권한 부여, HTTPS 설정, 로그인 페이지 커스터마이징 등과 관련된 보안 설정을 구성할 때 활용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@EnabledWebSecurity&amp;nbsp;&amp;nbsp;파헤치기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;그렇담 해당 어노테이션이 어떻게 정의되어있는지 조금 더 파헤쳐보겠습니다.&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;330&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6bT4G/btsH1gNJC90/1T1Zmej0QxqdMglup0LITk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6bT4G/btsH1gNJC90/1T1Zmej0QxqdMglup0LITk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6bT4G/btsH1gNJC90/1T1Zmej0QxqdMglup0LITk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6bT4G%2FbtsH1gNJC90%2F1T1Zmej0QxqdMglup0LITk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;253&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;760&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 어노테이션에 설정된 값들을 하나씩 해석해보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@Retention(RetentionPolicy.RUNTIME)&lt;/span&gt;이 어노테이션의 유지 정책을 &lt;span&gt;RUNTIME&lt;/span&gt;으로 지정합니다. 이는 런타임 시에도 어노테이션 정보가 유지되어야 함을 나타냅니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-table-width=&quot;760&quot; data-table-local-id=&quot;e0ef26b5-ef40-4d73-b01f-bcdd5cb3ed1d&quot; data-autosize=&quot;false&quot; data-layout=&quot;default&quot; data-number-column=&quot;false&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;@Target(ElementType.TYPE)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;이 어노테이션을 타입(ElementType.TYPE)에 적용할 수 있음을 나타냅니다. 즉, 클래스나 인터페이스에 이 어노테이션을 적용할 수 있습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;@Documented&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Javadoc에 이 어노테이션 정보를 포함하도록 지정합니다. 즉, 문서화할 때 이 어노테이션 정보가 포함됩니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class })&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;해당 어노테이션이 사용될 때 가져와야 하는 구성 클래스 및 선택자들을 지정합니다. &lt;br /&gt;&lt;span&gt;WebSecurityConfiguration&lt;/span&gt;,&lt;br /&gt;&lt;span&gt;SpringWebMvcImportSelector&lt;/span&gt;, &lt;br /&gt;&lt;span&gt;OAuth2ImportSelector&lt;/span&gt;, &lt;br /&gt;&lt;span&gt;HttpSecurityConfiguration&lt;/span&gt; 등이 가져와지게 됩니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;@EnableGlobalAuthentication&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;전역 인증을 활성화하는데 사용되는 어노테이션입니다. 이 어노테이션을 사용하면 전역적으로 인증 구성을 할 수 있습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;@Configuration&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;해당 어노테이션이 클래스가 구성 클래스임을 나타냅니다. 이 어노테이션이 붙은 클래스는 스프링 컨테이너에서 빈으로 등록될 수 있습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이곳에서 Import 항목을 보면 &lt;span&gt;WebSecurityConfiguration&lt;/span&gt; 클래스를 포함하고 있는데, 해당 클래스의 내부를 살펴보면&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;760&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgKWtD/btsH2EmBHPl/ST18438OW7U69uMdpRfaY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgKWtD/btsH2EmBHPl/ST18438OW7U69uMdpRfaY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgKWtD/btsH2EmBHPl/ST18438OW7U69uMdpRfaY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgKWtD%2FbtsH2EmBHPl%2FST18438OW7U69uMdpRfaY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;617&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;springSecurityFilterChain&lt;/span&gt;이라는 메소드가 빈을 등록하고 있는 것을 확인할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 빈이 바로, &lt;b&gt;스프링 필터 체인을 정의하는 빈입니다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필터체인?&lt;/b&gt;&lt;br /&gt;&lt;b&gt;-&amp;gt; 여러 개의 필터를 묶어놓은 것&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 필터 체인이 &lt;span&gt;WebSecurityConfiguration&lt;/span&gt;에 의해 등록되는 것이며 이는 다시 &lt;span&gt;EnableWebSecurity&lt;/span&gt;에 의해 등록되어지는 구조를 갖게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;springSecurityFilterChain&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 에서 제공하는 인증,인가를 위한 필터들의 모음입니다.&lt;br /&gt;Spring Security 에서 가장 핵심이 되는 기능을 제공하며, 거의 대부분의 서비스는 &lt;span&gt;Security Filter Chain&lt;/span&gt; 에서 실행된다고 이해하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@EnableGlobalAuthentication&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;애노테이션으로 정의된 EnabledWebSecurity 내부 구현을 살펴본 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSecurityConfiguration.class,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpringWebMvcImportSelector.class,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth2ImportSelector.class,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpSecurityConfiguration.class들을 import해서 실행시켜주는 것을 알 수 있습니다!&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;760&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UL6lR/btsH0X8SZlN/vOqzhFZnc8FjwSP6shmDSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UL6lR/btsH0X8SZlN/vOqzhFZnc8FjwSP6shmDSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UL6lR/btsH0X8SZlN/vOqzhFZnc8FjwSP6shmDSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUL6lR%2FbtsH0X8SZlN%2FvOqzhFZnc8FjwSP6shmDSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;338&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 &lt;span&gt;@EnableGlobalAuthentication &lt;/span&gt;구현부를 살펴보면 다음과 같습니다.&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;405&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMscCs/btsH29TV5tw/PNEKLfwvEBai3BYUO1Ayk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMscCs/btsH29TV5tw/PNEKLfwvEBai3BYUO1Ayk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMscCs/btsH29TV5tw/PNEKLfwvEBai3BYUO1Ayk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMscCs%2FbtsH29TV5tw%2FPNEKLfwvEBai3BYUO1Ayk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;177&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 어노테이션은 AuthenticationConfiguration 클래스를 Import 하는데, 해당 클래스를 살펴보면&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;760&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uAPGk/btsH1atm1eQ/SlmiKkfnAkzIV8ypxh2Tik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uAPGk/btsH1atm1eQ/SlmiKkfnAkzIV8ypxh2Tik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uAPGk/btsH1atm1eQ/SlmiKkfnAkzIV8ypxh2Tik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuAPGk%2FbtsH1atm1eQ%2FSlmiKkfnAkzIV8ypxh2Tik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;989&quot; height=&quot;295&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AuthenticationManagerBuilder&lt;/span&gt;를 통해 전역적으로 사용될 사용자 정보를 구성할 수 있습니다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@EnabledWebSecurity&amp;nbsp;&amp;nbsp;동작원리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 @EnableWebSecurity 어노테이션을 사용하게 되면 다음과 같은 설정정보들이 마법과 같이 추가됩니다.&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;239&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cITqZ6/btsH1gNJPlM/j3crQoT4Z24vIA8tI3LBsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cITqZ6/btsH1gNJPlM/j3crQoT4Z24vIA8tI3LBsk/img.png&quot; data-alt=&quot;짜잔-&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cITqZ6/btsH1gNJPlM/j3crQoT4Z24vIA8tI3LBsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcITqZ6%2FbtsH1gNJPlM%2Fj3crQoT4Z24vIA8tI3LBsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;232&quot; height=&quot;301&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;짜잔-&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 147.093%; height: 460px;&quot; border=&quot;1&quot; data-table-width=&quot;760&quot; data-table-local-id=&quot;a8ab06df-9088-41e1-8ff9-704d503017ef&quot; data-autosize=&quot;false&quot; data-layout=&quot;default&quot; data-number-column=&quot;false&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; width: 24.0698%;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebSecurityConfiguration&lt;/h3&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 76.7432%;&quot;&gt;&lt;span&gt; 스프링 필터 체인을 정의 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; width: 24.0698%;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SpringWebMvcImportSelector&lt;/h3&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 76.7432%;&quot;&gt;&lt;span&gt;Spring Security 설정에 Spring Web MVC와 관련된 구성을 추가&lt;br /&gt;(웹 애플리케이션에서의 보안 설정과 웹 MVC 설정을 통합)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; width: 24.0698%;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OAuth2ImportSelector&lt;/h3&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 76.7432%;&quot;&gt;&lt;span&gt;Spring Security 구성에 OAuth 2.0 인증 및 권한 부여를 위한 필수 구성을 &lt;br /&gt;자동으로 추가&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; width: 24.0698%;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HttpSecurityConfiguration&lt;/h3&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 76.7432%;&quot;&gt;&lt;span&gt;HttpSecurityConfiguration 클래스가 &lt;b&gt;WebSecurityConfigurerAdapter&lt;/b&gt;를 확장하여&lt;br /&gt;구현되며, &lt;b&gt;HttpSecurity&lt;/b&gt;를 구성하여 보안 규칙을 적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; width: 24.0698%;&quot; data-cell-background=&quot;#f4f5f7&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthenticationConfiguration&lt;/h3&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 76.7432%;&quot;&gt;&lt;span&gt;&lt;span&gt;@EnableGlobalAuthentication&lt;/span&gt; 어노테이션이 포함하는 클래스로, &lt;br /&gt;전역적으로 사용될 사용자 정보를 구성할 수 있게 함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Security 기능들을 어노테이션 하나로 활성화시킬 수 있는 아주 유용한 녀석이라고 볼 수 있겠습니다 &lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span data-emoji-text=&quot; &quot; data-emoji-id=&quot;1f642&quot; data-emoji-short-name=&quot;:slight_smile:&quot;&gt;✨ 참고 (Reference)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gngh0101&amp;amp;logNo=222056047335&amp;amp;parentCategoryNo=&amp;amp;categoryNo=32&amp;amp;viewDate=&amp;amp;isShowPopularPosts=false&amp;amp;from=postView&quot;&gt;https://blog.naver.com/PostView.naver?blogId=gngh0101&amp;amp;logNo=222056047335&amp;amp;parentCategoryNo=&amp;amp;categoryNo=32&amp;amp;viewDate=&amp;amp;isShowPopularPosts=false&amp;amp;from=postView&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718611144829&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Security EnableWebSecurity 내부 속으로 -1 (AuthenticationConfiguration)&quot; data-og-description=&quot;Spring Security EnableWebSecurity 내부 속으로 -1 (AuthenticationConfiguration) EnableWebSe...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/PostView.naver?blogId=gngh0101&amp;amp;logNo=222056047335&amp;amp;parentCategoryNo=&amp;amp;categoryNo=32&amp;amp;viewDate=&amp;amp;isShowPopularPosts=false&amp;amp;from=postView&quot; data-og-url=&quot;https://blog.naver.com/gngh0101/222056047335&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3nPFz/hyWlpbRLhG/pXAdWHBPdOQPHWbgXH2Hak/img.jpg?width=60&amp;amp;height=60&amp;amp;face=0_0_60_60&quot;&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gngh0101&amp;amp;logNo=222056047335&amp;amp;parentCategoryNo=&amp;amp;categoryNo=32&amp;amp;viewDate=&amp;amp;isShowPopularPosts=false&amp;amp;from=postView&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/PostView.naver?blogId=gngh0101&amp;amp;logNo=222056047335&amp;amp;parentCategoryNo=&amp;amp;categoryNo=32&amp;amp;viewDate=&amp;amp;isShowPopularPosts=false&amp;amp;from=postView&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3nPFz/hyWlpbRLhG/pXAdWHBPdOQPHWbgXH2Hak/img.jpg?width=60&amp;amp;height=60&amp;amp;face=0_0_60_60');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security EnableWebSecurity 내부 속으로 -1 (AuthenticationConfiguration)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security EnableWebSecurity 내부 속으로 -1 (AuthenticationConfiguration) EnableWebSe...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://k39335.tistory.com/40&quot;&gt;https://k39335.tistory.com/40&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718611145432&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JAVA] Annotation(어노테이션) 이란?&quot; data-og-description=&quot;[JAVA] Annotation(어노테이션) 이란? Annotation어노테이션이란 본래 주석이란 뜻으로, 인터페이스를 기반으로 한 문법이다. 주석과는 그 역할이 다르지만 주석처럼 코드에 달아 클래스에 특별한 의미&quot; data-og-host=&quot;k39335.tistory.com&quot; data-og-source-url=&quot;https://k39335.tistory.com/40&quot; data-og-url=&quot;https://k39335.tistory.com/40&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/itehR/hyWoB9xSNQ/Qdqau9eFAMEqbwBNzCk62k/img.jpg?width=120&amp;amp;height=80&amp;amp;face=0_0_120_80,https://scrap.kakaocdn.net/dn/bpILS8/hyWoKMb12P/qkRoGae4qcc3UyNuflUDd0/img.jpg?width=120&amp;amp;height=80&amp;amp;face=0_0_120_80&quot;&gt;&lt;a href=&quot;https://k39335.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://k39335.tistory.com/40&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/itehR/hyWoB9xSNQ/Qdqau9eFAMEqbwBNzCk62k/img.jpg?width=120&amp;amp;height=80&amp;amp;face=0_0_120_80,https://scrap.kakaocdn.net/dn/bpILS8/hyWoKMb12P/qkRoGae4qcc3UyNuflUDd0/img.jpg?width=120&amp;amp;height=80&amp;amp;face=0_0_120_80');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JAVA] Annotation(어노테이션) 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[JAVA] Annotation(어노테이션) 이란? Annotation어노테이션이란 본래 주석이란 뜻으로, 인터페이스를 기반으로 한 문법이다. 주석과는 그 역할이 다르지만 주석처럼 코드에 달아 클래스에 특별한 의미&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;k39335.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/projects/spring-security/&quot;&gt;https://spring.io/projects/spring-security/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718611148724&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Security&quot; data-og-description=&quot;Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security is a framework that focuses on providing both authentication and authoriz&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/projects/spring-security/&quot; data-og-url=&quot;https://spring.io/projects/spring-security&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c9cnVJ/hyWlbknSMd/08VDjCPfN8fkWMvuMJRl81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/0ey5e/hyWoInja8m/9VqAv06Nl5ZaBaGfK92ARk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/projects/spring-security/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/projects/spring-security/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c9cnVJ/hyWlbknSMd/08VDjCPfN8fkWMvuMJRl81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/0ey5e/hyWoInja8m/9VqAv06Nl5ZaBaGfK92ARk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security is a framework that focuses on providing both authentication and authoriz&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>@enabledwebsecurity</category>
      <category>Java</category>
      <category>Spring Boot</category>
      <category>Spring Security</category>
      <category>springboot</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/75</guid>
      <comments>https://born2bedeveloper.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 17 Jun 2024 17:08:34 +0900</pubDate>
    </item>
    <item>
      <title>RabbiqMQ란 무엇일까?</title>
      <link>https://born2bedeveloper.tistory.com/74</link>
      <description>&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;RabbitMQ&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼랭(Erlang)언어로 AMQP를 구현한 오픈소스 메세지 브로커.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*얼랭 : 함수형 프로그래밍 언어 (C와 비슷한 성격의 언어라고 볼 수 있겠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;메시지 브로커란?&lt;br /&gt;&lt;b&gt;Publisher&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;(&lt;/span&gt;송신자&lt;span style=&quot;color: #555555;&quot;&gt;)로부터 전달받은 메시지를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Subscriber&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;(&lt;/span&gt;수신자&lt;span style=&quot;color: #555555;&quot;&gt;)로 전달해주는 중간 역할이며 &lt;br /&gt;응용 소프트웨어 간에 메시지를 교환할 수 있게 한다.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 소프트웨어끼리 메세지를 서로 교환할 수 있도록 중간에서 알선해주는 녀석이다.(미들웨어 라고 칭함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&amp;nbsp;AMQP&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client application과 middleware broker와의 메세지를 주고 받기 위한 프로토콜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 브로커가 어플리케이션(소프트웨어) 사이에서 메시지를 중개해주는 녀석이라고 했다. 양측의 어플리케이션에서 받은 메세지를 퍼나르기위한 규칙이자 방법이 AMQP인것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로토콜에 따른 MQ제품중 하나가 RabbitMQ다. (다른 친구로는 그렇게 유명한 Kafka가 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;b&gt;RabbitMQ의 동작 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상, RabbitMQ의 동작은 AMQP를 따르기 때문에 AMQP에 대한 내용을 정리한다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP의 라우팅 모델은 아래와 같은 3개의 중요한 component 들로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Exchange&lt;/li&gt;
&lt;li&gt;Queue&lt;/li&gt;
&lt;li&gt;Binding&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP는 이 3가지의 구성요소들이 서로 간에 어떻게 통신하는지를 정의한 프로토콜이라고 볼 수 있다. 전체적인 흐름은 아래의 그림과 함께 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tsem6/btrZj0Havic/kXZLd5DN4uDQsTeXvToI4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tsem6/btrZj0Havic/kXZLd5DN4uDQsTeXvToI4K/img.png&quot; data-alt=&quot;출처 :&amp;amp;nbsp;https://docs.informatica.com/integration-cloud/cloud-application-integration/current-version/rabbitmq-connector-guide/introduction-to-rabbitmq-connector/rabbitmq-overview.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tsem6/btrZj0Havic/kXZLd5DN4uDQsTeXvToI4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTsem6%2FbtrZj0Havic%2FkXZLd5DN4uDQsTeXvToI4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;423&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;nbsp;https://docs.informatica.com/integration-cloud/cloud-application-integration/current-version/rabbitmq-connector-guide/introduction-to-rabbitmq-connector/rabbitmq-overview.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer가 메세지를 생성하고 발송하여 Consumer에게 수신되는 그 사이의 과정이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급한 세 가지 구성요소로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;b&gt;&amp;nbsp;RabbitMQ 구성요소&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;1) Exchange&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher가 보낸 메시지는 반드시 Exchange를 통해 Queue에 접근하게 된다. (큐에 직통으로 접근 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exchange는 수신된 메시지를 적절한 기준에 맞는 Queue나 또다른 exchange로 라우팅한다. 즉 라우터의 기능을 수행하는데, 간단하게 말해서 길잡이 역할을 해준다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 언급한&lt;u&gt;&lt;b&gt; &amp;lsquo;적절한 기준&amp;rsquo;을 exchange type이라고 한다&lt;/b&gt;&lt;/u&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exchange type은 메시지를 어떤 방법으로 라우팅 시킬지를 결정하며, AMQP에서는 해당 방법을 표준화하여 &lt;b&gt;Standard Exchange Type을 제시한다. (표준화 된 방법을 기준으로 exchage가 메시지를 라우팅 하도록!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2) &lt;span data-token-index=&quot;0&quot;&gt;Binding&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exchange와 큐와의 관계를 정의한 일종의 라우팅 테이블이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exchange와 Queue를 연결짓는 행위라고 볼 수 있는데, Exchange는 Publisher로부터 수신한 메시지를 Binding에 따라 적절한 Queue나 또다른 Exchange로 라우팅하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Binding과 Exchange Type의 차이점? &lt;br /&gt;두 요소를 정리하다보면.. 매우 유사하게 느껴진다. exchange type은 메시지를 어떤 방법으로 라우팅 시킬지를 결정하는 것이다. 즉 전달받은 메세지를 분리하는 &amp;lsquo;방법&amp;rsquo;이 되고, binding을 이러한 방법을 이용해 실제로 어떤 메시지를 어떤 큐에 보낼지를 결정한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일정보가 들어오면 특정 큐로 보내는 broker가 있다고 가정해보자.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 도메인 정보(@뒤에 오는 주소)를 보고 큐를 결정하겠다. &amp;rarr; exchange type&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;naver는 1번큐, google은 2번큐, daum은 3번큐로 보내겠다 &amp;rarr; binding&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3)&lt;span&gt; Queue&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer들이 발송한 메세지들이 Consumer가 소비하기 전까지 보관되는 장소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리나 디스크에 메시지를 저장하고, 그것을 consumer에게 전달하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐는 Binding을 통해 Exchange에 bind(연결)된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&amp;nbsp;Standard Exchange Type&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exchange Type은 메시지를 어떤 원칙이나 방법으로 라우팅할지를 결정하는 일종의 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;1&quot;&gt;AMQP는 4개의 Standard &lt;/span&gt;Exchange Type을 규정하여 &lt;span data-token-index=&quot;3&quot;&gt;표준화 된 방법으로 exchage가 메시지를 라우팅 하도록 설계했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Routing Key&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher에서 송신한 메시지 헤더에 포함되는 것으로 일종의 가상 주소라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exchange가 어떤 큐로 메시지를 라우팅할지 결정하는 도구 중 하나다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Direct Exchange&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PTrpZ/btrZkxLuKvd/8xSkk3ISQJ8jHTV0NrVkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PTrpZ/btrZkxLuKvd/8xSkk3ISQJ8jHTV0NrVkKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PTrpZ/btrZkxLuKvd/8xSkk3ISQJ8jHTV0NrVkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPTrpZ%2FbtrZkxLuKvd%2F8xSkk3ISQJ8jHTV0NrVkKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지의 라우팅 키를 큐에 1:N으로 매칭시키는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 큐의 이름과 라우팅 키를 동일하게 작성하여 바인딩한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Topic Exchange&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zdfVN/btrZlYu435Z/X0vK6FXkKsoQA4ZkkZpZrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zdfVN/btrZlYu435Z/X0vK6FXkKsoQA4ZkkZpZrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zdfVN/btrZlYu435Z/X0vK6FXkKsoQA4ZkkZpZrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzdfVN%2FbtrZlYu435Z%2FX0vK6FXkKsoQA4ZkkZpZrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드카드를 이용해서 메시지를 큐에 매칭시키는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;*&quot;는 하나의 단어, &quot;#&quot;은 0개 이상의 단어를 의미한다. &amp;ldquo;.&amp;rdquo; (dot)로 구별되는 단어의 리스트를 가져야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ex. &amp;ldquo;abc.apple.one&amp;rdquo;, &amp;ldquo;aaa.banana.two&amp;rdquo;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Fanout Exchange&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HGOO4/btrZoqrjTZn/es72MxM5JkUvjmTHK4DY40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HGOO4/btrZoqrjTZn/es72MxM5JkUvjmTHK4DY40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HGOO4/btrZoqrjTZn/es72MxM5JkUvjmTHK4DY40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHGOO4%2FbtrZoqrjTZn%2Fes72MxM5JkUvjmTHK4DY40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 메시지를 모든 큐로 라우팅하는 유형이다. 일종의 브로드캐스트 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Headers Exchange&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCVd6l/btrZorcHmfz/GJHKRz1WwTDK3yMouRNp20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCVd6l/btrZorcHmfz/GJHKRz1WwTDK3yMouRNp20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCVd6l/btrZorcHmfz/GJHKRz1WwTDK3yMouRNp20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCVd6l%2FbtrZorcHmfz%2FGJHKRz1WwTDK3yMouRNp20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 3개의 경우는 &amp;lsquo;라우팅 키&amp;rsquo;를 사용하여 결정했다면, 해당 Exchange Type은 key-value로 정의된 헤더에 의해 라우팅을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐를 바인딩할 때 x-match라는 특정 argument를 기준으로 어떻게 바인딩할지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x-match가 all &amp;rarr; 바인딩 조건을 모두 충족시켜야 한다(AND), x-match가 any &amp;rarr; 하나만 충족시키면 된다(OR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Exchange Type을 표로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 90px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;타입&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;설명&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Direct&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Routing key가 정확히 일치하는 Queue에 메세지 전송&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Unicast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Topic&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Routing key 패턴이 일치하는 Queue에 메세지 전송&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Multicast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Headers&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;[key:value]로 이루어진 header 값을 기준으로 일치하는 Queue에 메세지 전송&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Multicast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Fanout&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;해당 Exchange에 등록된 모든 Queue에 메세지 전송&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Broadcast&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** 보너스!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Prefetch Count&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Queue에 여러 Consumer가 존재할 경우, Queue는 기본적으로 Round-Robin 방식으로 메세지를 분배한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(라운드-로빈 : 순서대로 한번씩 돌아가는 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, Consumer가 2개인 상황에서 홀수번째 메세지는 처리 시간이 짧고, 짝수번째 메세지는 처리 시간이 매우 긴 경우, 계속해서 하나의 Consumer만 일을 하게 되는 상황이 발생할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 예방하기 위해, prefetch count를 1로 설정해 두면, 하나의 메세지가 처리되기 전(Ack를 보내기 전)에는 새로운 메세지를 받지 않게 되므로, 작업을 분산시켜 좀 더 효율적으로 운영할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Reference]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.rabbitmq.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.rabbitmq.com/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.dudaji.com/general/2020/05/25/rabbitmq.html&quot;&gt;https://blog.dudaji.com/general/2020/05/25/rabbitmq.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://egloos.zum.com/killins/v/3025514&quot;&gt;http://egloos.zum.com/killins/v/3025514&lt;/a&gt;&lt;/p&gt;</description>
      <category>Infra/MessageBroker</category>
      <category>amqp</category>
      <category>Message Broker</category>
      <category>MSA</category>
      <category>Queue</category>
      <category>rabbitmq</category>
      <category>Topic</category>
      <category>래빗엠큐</category>
      <category>메시지</category>
      <category>메시지브로커</category>
      <category>브로커</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/74</guid>
      <comments>https://born2bedeveloper.tistory.com/74#entry74comment</comments>
      <pubDate>Wed, 15 Feb 2023 10:55:30 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 도커와 컨테이너</title>
      <link>https://born2bedeveloper.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 조금이라도 발을 담가봤다면 대부분 '도커'라는 개념을 들어본 적이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 주위에서 '도커 참 좋더라~' 라고 하는데,&amp;nbsp; '그래서 도커가 뭔데..?' 라는 생각이 머리를 맴돌았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKX5ac/btrJuSRIhCY/8j6eDdhd28XEP8lD2WtrCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKX5ac/btrJuSRIhCY/8j6eDdhd28XEP8lD2WtrCk/img.jpg&quot; data-alt=&quot;출처 : 대학일기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKX5ac/btrJuSRIhCY/8j6eDdhd28XEP8lD2WtrCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKX5ac%2FbtrJuSRIhCY%2F8j6eDdhd28XEP8lD2WtrCk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;225&quot; height=&quot;225&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 대학일기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 도커에 대한 내 생각의 흐름은 이렇게 흘러갔다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;대충 구글에 도커 검색 -&amp;gt; 아~ -&amp;gt; (1주일 뒤) 도커가 뭐더라? -&amp;gt; 대충 구글에 도커 검색 -&amp;gt; 아~&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 무한의 굴레를 끊고자.. &lt;span&gt;제대로 마음잡고 도커에 대해 공부하고&lt;span&gt; 직접 실습해보며 어느 정도 감을 잡기 시작했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 좌충우돌의 과정을 한 달이 지난 이제서야 포스팅으로 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(포스팅으로 정리하는건 배우는 것과 별개로 정말 어렵고 번거롭다..흑흑.. 모든 블로거들에게 무한한 존경을..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도커에 대한 개념을 쉽게 훑어보고, '정확한 용어'로 기억해 스스로 정의할 수 있도록&lt;/b&gt; 하는 것이 이 포스팅의 목표다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;컨테이너의 탄생과 진화과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 도커를 제대로 이해하기 위해서는 컨테이너에 대한 이해가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;도커 = 컨테이너?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 많은 사람들이 위와 같이 도커와 컨테이너를 동일시하는 경우가 있었다. (필자도 마찬가지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 둘은 엄연히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너의 탄생 배경을 훑어 보며 둘의 차이점에 대해 확실히 짚고 넘어가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; chroot의 탄생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너의 역사는 1979년 chroot를 발표 한 것으로부터 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chroot는 프로세스의 루트 디렉토리를 변경하여 프로세스 마다 접근할 수 있는 디렉토리를 제한한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRPUn/btrJuC24pRb/v0J11Ns2UkMLRbnfjVnFeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRPUn/btrJuC24pRb/v0J11Ns2UkMLRbnfjVnFeK/img.png&quot; data-alt=&quot;https://securityqueens.co.uk/im-in-chroot-jail-get-me-out-of-here/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRPUn/btrJuC24pRb/v0J11Ns2UkMLRbnfjVnFeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRPUn%2FbtrJuC24pRb%2Fv0J11Ns2UkMLRbnfjVnFeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;405&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://securityqueens.co.uk/im-in-chroot-jail-get-me-out-of-here/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우나 리눅스에서 터미널을 켠 후, 'ls /' 명령어를 통해 가장 최상위 디렉토리에 대한 목록이 나오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 사진의 경우 노란색 폴더로 명시된 bin, etc, home, usr)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 만약 chroot 명령어를 통해 chroot디렉토리를 루트로 설정하면, 'ls /'로 조회했을 때, 위와 같이 빨간 사각형 안의 초록색 디렉토리 bin, etc, home, usr가 조회될것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 chroot를 통해 루트 디렉토리를 하위로 설정하게 되면, 해당 프로세스에서는 bob, alice와 같은 노란색 폴더에 접근이 제한된다. 이러한 방식으로 사용자 별로 격리된 공간을 할당 받아 주어진 일을 수행할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 chroot 감옥(JAIL)이라고 부른다. 사용자들을 지정된 감옥에 수감시켜 그 감옥 내에서만 일을 수행하도록 하는..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(뭔가 귀여우면서 섬짓한 느낌..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. FreeBSD Jail의 탄생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chroot는 위와 같이 단순히 파일 접근에 대한 제어만 가능했다. 이후 프로세스, 네트워크까지 분리할 수 있는 기술이 탄생했는데 그것이 FreeBSD가 출시한 Jail이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 파일 엑세스를 제어하는 것을 넘어 Jail이라는 OS 가상화 환경에서 파일 시스템, 프로세스, 네트워크를 분리할 수 있는 획기적 기술을 제공했다. 이것이 바로 컨테이너의 시작이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Namespace, cgroup 그리고 LXC의 탄생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[Namespace]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 흘러 이번엔 RedHat에서 시스템 자원을 논리적으로 분할하는 &lt;span style=&quot;color: #448361;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;Namespace&lt;/span&gt;를 발표하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Namespace는 lightweight(경량) 가상화 솔루션이다. (그게 뭔데..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 한 시스템 내에서 '독립된 공간'을 '격리된 환경'에서 운영하는 가상화 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 프로세스 별로 시스템의 리소스를 분리해서 실행하도록 도와주는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 말이 너무 어렵게 느껴진다면.. 이를 '아파트'로 추상화해서 생각해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 아파트에는 101호, 102호..등 호수마다 독립된 주거 환경을 제공해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아파트 = 시스템&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;101호, 102호... = 프로세스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주거 환경 (거실, 안방, 주방...) = 리소스 (PID, IPC, Network, UID, Mount, UTS)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 치환하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Namespace는 개별 프로세스(101호) 마다 시스템(아파트)의 리소스(주거 환경)를 제공한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념이 좀 더 직관적으로 와닿을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프로세스 끼리는 IP로 통신을 하는데, 이는 서로의 공간을 방문하기 위해 101호, 102호와 같이 호실을 알아야 방문할 수 있는 것과 똑같은 개념이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[cgroup]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 Google에서 프로세스 자원 이용량을 제어하는 cgroup을 발표했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 커널의 기능 중 하나로 네트워크, 시스템 메모리, cpu 시간과 같은 자원을 사용자 정의 프로세스에 할당 할 수 있는 기능을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 한 프로세스 안에 사용자 원하는 만큼 자원을 할당해줄 수 있는 기능을 제공하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[&lt;span style=&quot;background-color: #ffffff; color: #2b2b2b;&quot;&gt;LinuX Containers&lt;/span&gt;]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #2b2b2b;&quot;&gt;IBM에서 위의 두 기술&lt;/span&gt; Namespace와 cgroup을 사용하여 LXC를 탄생시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 최초의 Linux 컨테이너 엔진으로 컨테이너 기술의 시초라고 볼 수 있다. (근본!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Namespace를 통해 격리된 공간으로 구성된 환경을 만들고, cgroup을 통해 각 환경에 원하는 만큼 자원을 할당할 수 있도록 구현되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 '컨테이너'라고 부르는 시초격인 이 엔진은 단일 Linux 커널에서 동작하며 처음으로 Linux 상에서 컨테이너 개념을 가장 완벽하게 구현했다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;여기서 '커널'이라는 단어가 언급되는데, 해당 개념이 컨테이너와 가상머신의 차이점을 비교할 때 자주 언급된다.&lt;br /&gt;커널은 CPU, 메모리 등 운영체제의 핵심으로, 거의 모든 하드웨어를 제어하는 프로그램이다.&amp;nbsp;&lt;br /&gt;(쉽게 말해 컴퓨터의 뇌를 담당해 손, 발을 움직이듯 하드웨어를 제어한다고 보면 편하다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;도커의 탄생&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 (LXC)가 개발된 이후 2013년, 도커가 드디어 오픈 소스 소프트웨어로 공개된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커는 Debian, SUSE 등 모든 Linux 배포판을 지원하며 컨테이너 기술 확산 박차를 가하게 되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 시대가 열리기 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커에 대해 한 번이라도 찾아본 사람들은, 주로 가상머신과 비교하는 글들을 많이 봤을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 방금 다뤘던 LXC의 개념을 복기해보면, 이 엔진은 '단일 Linux 커널'에서 동작한다고 언급하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 복습해보자!&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;커널은 CPU, 메모리 등 운영체제의 핵심으로, 거의 모든 하드웨어를 제어하는 프로그램이다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;(컴퓨터의 뇌를 담당해 사람의 손, 발을 움직이듯 하드웨어를 제어한다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;단일 커널에서 동작한다는 개념은 컨테이너의와 가상머신을 구분하는 핵심 내용이다. 사진과 함께 얘기해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M2E1j/btrJu1IFvIN/TeckYascD1FgANErgVm2yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M2E1j/btrJu1IFvIN/TeckYascD1FgANErgVm2yK/img.png&quot; data-alt=&quot;출처 :&amp;amp;nbsp; https://cloud.google.com/containers/?hl=ko&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M2E1j/btrJu1IFvIN/TeckYascD1FgANErgVm2yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM2E1j%2FbtrJu1IFvIN%2FTeckYascD1FgANErgVm2yK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;274&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;nbsp; https://cloud.google.com/containers/?hl=ko&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 내용을 다루며 이와 비슷한 사진을 수도 없이 봤는데, 그럼에도 한 번에 와닿지가 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(OS를 직접 올리지 않아 자원이 절약...뭐시기..그렇구만.. 라는 모호한 결론으로 끝났다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템을 운영하는 운영 체제는 종류가 다양하다. 가상머신의 경우 그림 가운데에 놓인 Hypervisor가 '하드웨어'를 물리적으로 구분해서 가상화를 진행하므로 App1, App2, App3 환경에는 각기 다른 커널을 통해 하드웨어 제어가 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 원래의 Host OS 위에 3개의 Guest OS가 통째로 올라가고, 각각의 Guest OS는 각기 다른 커널을 가지고 하드웨어 제어가 이루어지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본체를 이루는 두뇌와, 그 위의 쫄병 셋의 두뇌까지 올라가 있다고 생각해보라. 상당히 차지하는 공간도 무거울 것이고, 4개의 뇌까지 사용할 만큼의 일이 거의 없으니 불필요한 자원이 낭비되고 부팅이 상당히 오래 걸리게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 컨테이너의 경우, 본체의 뇌를 같이 공유한다. 따라서 App1~3이 각기 작동하기 위해 필요한 최~소한의 OS만 받아와 구동되기 때문에 훨씬 가볍고 당연히 속도도 훨씬 빨라지게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;도커 = 컨테이너?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 게시글의 맨 처음에서 다뤘던 이 질문에 대해 확실하게 대답할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아니더라도 걱정하지 말라.. 필자도 수십번을 다시 찾아보면서 이해했으니..^_ㅜ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 '격리된 공간에서 프로세스가 동작하게 해주는 가상화 기술'이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커는 해당 기술을 사용자에게 편리하게 제공해주는 '플랫폼'이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이제 어디가서 도커 = 컨테이너라고 말하지 말기로 나 스스로에게 약속ㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도커와 LXC의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 세부적으로 둘의 차이를 다뤄보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LXC(LinuX Container)는 아까 컨테이너의 시초로 다룬 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커도 물론 처음에는 LXC 기술을 기반으로 구축되었으나, 그 이후 종속 관계를 벗어나 독자적인 기술로 발전했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LXC는 가상화 기술로 자원의 경량화를 이뤄내는 유용한 방법이지만 사용자에 친숙하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 차이점에 대해 그림과 함께 다뤄보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAY7pj/btrJwZQ75ON/QCmbukw0Dy3d3ks3GEV9f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAY7pj/btrJwZQ75ON/QCmbukw0Dy3d3ks3GEV9f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAY7pj/btrJwZQ75ON/QCmbukw0Dy3d3ks3GEV9f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAY7pj%2FbtrJwZQ75ON%2FQCmbukw0Dy3d3ks3GEV9f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;468&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LXC는 하나의 컨테이너에 여러 응용프로그램을 띄우지만, 도커는 1컨테이너 = 1응용프로그램을 권장하고 그와 관련된 여러 툴을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애플리케이션의 기능을 분해해 MSA방식에 유용하고 필요한 기능들 중 필요한걸 골라서 재사용이 용이함&lt;/li&gt;
&lt;li&gt;SOA 작동 방식처럼 각 컨테이너 별 기능을 모듈화 하여 여러 애플리케이션 사이에서 프로세스를 공유할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패션 쇼핑몰을 예로 들어서 생각해보자. 기능을 대략적으로 설계해보면 구매, 장바구니, 마이페이지 등이 존재할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 구매, 장바구니, 마이페이지가 한 어플리케이션이 있지않고 각각 독자적으로 만들어 분리된 환경(컨테이너)에서 돌아가도록 하는 것이 MSA 방식이다. (MSA 방식에 대한 자세한 글은 이곳을 참고해보자.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 뷰티 쇼핑몰을 만들었을 때, 기존에 돌아가던 장바구니 기능을 그대로 가져와(필요하면 조금 수정해서) 재사용할 수 있으니 상당히 효율적인 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한&amp;nbsp; Docker에서는 컨테이너 생성 시 '도커 이미지'를 이용해 구축한다. 그림을 통해 도커 컨테이너 구조를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FA2tg/btrJvRzm6IZ/L169UA68SxM1JP0hWGd1x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FA2tg/btrJvRzm6IZ/L169UA68SxM1JP0hWGd1x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FA2tg/btrJvRzm6IZ/L169UA68SxM1JP0hWGd1x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFA2tg%2FbtrJvRzm6IZ%2FL169UA68SxM1JP0hWGd1x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;493&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 아래는 아까 언급했던 커널이다. Host OS에서 공유받아 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 위는 컨테이너를 구성하기 위한 Base Image이다. 이 이미지의 특성은 겹겹이 쌓이는 구조를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 layer형태로 쌓여있다고 하는데, 해당 방식의 이점이 뭐냐면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 새 컨테이너를 만들 때, 이전 컨테이너를 만들 당시 생성했던 layer가 중복될 경우 또 설치하지 않고 재사용하기 때문에 속도가 더 빠르고 효율성이 개선된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(요리에 비교를 하면, 김치찌개(컨테이너1)와 김치볶음밥(컨테이너2)를 만들 때, 이미 김치찌개에 사용된 재료(김치)는 재설치 없이 바로 사용하게 되어 중복이 사라진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 통해 도커를 통해 가상화 환경을 구축하면 재사용성이 높아져 자원을 효율적으로 다룰 수 있게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;도커는 만능이 아니다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 장점을 다시 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 한 서버에 여러 개의 컨테이너를 구동해 가상화가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(마치 여러 서버를 구매해서 돌리는 것 같지만, 한 서버 안에 여러 개의 컨테이너를 생성해 다양한 응용프로그램이 독립된 각각의 환경에서 돌아가는 것이 가능해진다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 필요한 리소스를 사용자가 할당 가능하므로 개발 환경에 구애받지 않는다.&amp;nbsp;&lt;br /&gt;(컨테이너 1은 CentOS가 돌아가고.. 컨테이너 2는 Ubuntu가 돌아가게 구현이 가능하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 2번 방법이 가능한 가상머신보다 심지어 훨씬 가볍고 빠르게 경량화가 되어있다.&lt;br /&gt;(HostOS의 커널을 공유하기 때문의 일일이 OS를 새로 만들지 않아도 최소한의 OS로만 동작한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 보면 도커는 완벽해보인다. 완전 짱이잖아..? 하지만 이러한 도커에도 단점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'독립된 환경'이 도커의 장점이자 단점이 될수 있다. 이게 무슨말일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 권장사항은 1컨테이너 = 1응용프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 의미는 개발자가 만들고자 하는 서비스의 규모가 커질수록 구현해야 할 응용프로그램이 늘어나고, 그 만큼 컨테이너의 수가 무수히 많아질 것이다. 문제는 컨테이너가 독립된 환경이기 때문에 각자 도생하는 녀석을 한 데 불러모으기가 상당히 어렵다는 것이다. (다 갠플하는 놈들이라 팀플이 쉽지않다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 갠플하는 녀석들을 하나로 그룹화하여 관리할 수 있는 시스템이 있는데, 그게 바로 쿠버네티스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용은 다음 포스팅에서 다루도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 읽고 조금이라도 도커와 컨테이너의 이해에 도움이 됐으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/Docker</category>
      <category>container</category>
      <category>docker</category>
      <category>LXC</category>
      <category>가상머신</category>
      <category>가상화</category>
      <category>도커</category>
      <category>도커 장점</category>
      <category>도커와 컨테이너 차이점</category>
      <category>컨테이너</category>
      <category>쿠버네티스</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/71</guid>
      <comments>https://born2bedeveloper.tistory.com/71#entry71comment</comments>
      <pubDate>Fri, 12 Aug 2022 14:05:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] 이메일 보내기 (3) - html 템플릿 적용 (feat. Thymeleaf)</title>
      <link>https://born2bedeveloper.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;++) 해당 포스팅은 이전 작성한 포스팅들과 연계되어 있으므로 먼저 보고 오는 것을 추천합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/68?category=1038709&quot;&gt;https://born2bedeveloper.tistory.com/68?category=1038709&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1660191616854&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] 이메일 보내기 (2) - 참조(cc), 첨부 파일&quot; data-og-description=&quot;꽤 오래전에 구글 SMTP 서버를 이용한 Email 전송에 관한 내용을 다룬 적이 있었는데, 생각보다 조회수가 매우 높았다. 아마 이메일 전송이 부가적인 기능으로 다양하게 사용되기 떄문이겠지? 새로&quot; data-og-host=&quot;born2bedeveloper.tistory.com&quot; data-og-source-url=&quot;https://born2bedeveloper.tistory.com/68?category=1038709&quot; data-og-url=&quot;https://born2bedeveloper.tistory.com/68&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HNZge/hyPpx7mPZ4/ooJeUIWlTAjQzFjTk90zw0/img.png?width=800&amp;amp;height=235&amp;amp;face=0_0_800_235,https://scrap.kakaocdn.net/dn/bjOr7A/hyPoiKL7qu/Ruv6Q72tTRde8HnH9qQKrK/img.png?width=800&amp;amp;height=235&amp;amp;face=0_0_800_235,https://scrap.kakaocdn.net/dn/VpbBU/hyPoe9qiak/1EO3i9Q1yihdCrc5FSZ1x0/img.png?width=1167&amp;amp;height=505&amp;amp;face=0_0_1167_505&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/68?category=1038709&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://born2bedeveloper.tistory.com/68?category=1038709&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HNZge/hyPpx7mPZ4/ooJeUIWlTAjQzFjTk90zw0/img.png?width=800&amp;amp;height=235&amp;amp;face=0_0_800_235,https://scrap.kakaocdn.net/dn/bjOr7A/hyPoiKL7qu/Ruv6Q72tTRde8HnH9qQKrK/img.png?width=800&amp;amp;height=235&amp;amp;face=0_0_800_235,https://scrap.kakaocdn.net/dn/VpbBU/hyPoe9qiak/1EO3i9Q1yihdCrc5FSZ1x0/img.png?width=1167&amp;amp;height=505&amp;amp;face=0_0_1167_505');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] 이메일 보내기 (2) - 참조(cc), 첨부 파일&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;꽤 오래전에 구글 SMTP 서버를 이용한 Email 전송에 관한 내용을 다룬 적이 있었는데, 생각보다 조회수가 매우 높았다. 아마 이메일 전송이 부가적인 기능으로 다양하게 사용되기 떄문이겠지? 새로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;born2bedeveloper.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/14?category=1038709&quot;&gt;https://born2bedeveloper.tistory.com/14?category=1038709&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1660191625566&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] EMail 보내기&quot; data-og-description=&quot;기존 프로젝트를 베타 오픈하기 전에, 피드백을 받는 기능을 추가하면 좋겠다는 생각이 들었다. 일종의 고객센터 같은 느낌? 간단한 입력 form에 작성한 후 보내면, 관리자 계정으로 메일이 오도&quot; data-og-host=&quot;born2bedeveloper.tistory.com&quot; data-og-source-url=&quot;https://born2bedeveloper.tistory.com/14?category=1038709&quot; data-og-url=&quot;https://born2bedeveloper.tistory.com/14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dhYVxv/hyPofN3eDU/frphH3bUKvRgUhHzA3iGeK/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/cGzPKG/hyPoiRxtIS/s6bNFvxUh3xHvh7jlyxUc1/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/bcQmUd/hyPpAQzwRI/w7UgSghvcO5eMwfpqSfkrk/img.png?width=1124&amp;amp;height=364&amp;amp;face=0_0_1124_364&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/14?category=1038709&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://born2bedeveloper.tistory.com/14?category=1038709&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dhYVxv/hyPofN3eDU/frphH3bUKvRgUhHzA3iGeK/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/cGzPKG/hyPoiRxtIS/s6bNFvxUh3xHvh7jlyxUc1/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/bcQmUd/hyPpAQzwRI/w7UgSghvcO5eMwfpqSfkrk/img.png?width=1124&amp;amp;height=364&amp;amp;face=0_0_1124_364');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] EMail 보내기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기존 프로젝트를 베타 오픈하기 전에, 피드백을 받는 기능을 추가하면 좋겠다는 생각이 들었다. 일종의 고객센터 같은 느낌? 간단한 입력 form에 작성한 후 보내면, 관리자 계정으로 메일이 오도&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;born2bedeveloper.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 html 템플릿을 적용한 이메일 전송에 대해 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 메일함을 보다 보면, 아래와 같이 텍스트 작성 외에 html문서형으로 작성된 메일을 받아본 적이 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FSnsl/btrJp13iWml/IHrFMyRCJfjdvfcW8ldIdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FSnsl/btrJp13iWml/IHrFMyRCJfjdvfcW8ldIdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FSnsl/btrJp13iWml/IHrFMyRCJfjdvfcW8ldIdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFSnsl%2FbtrJp13iWml%2FIHrFMyRCJfjdvfcW8ldIdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1492&quot; height=&quot;761&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 기반 메일 전송에는 여러 방식이 있지만, 필자는 타임리프 템플릿 엔진을 이용하여 HTML 기반 이메일을 전송해볼것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 전송에 대한 기본적인 개념은 이전에 다뤘으니 생략하고 바로 구현으로 넘어가겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;환경 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 필요한 라이브러리들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;build.gradle&lt;/h4&gt;
&lt;pre id=&quot;code_1660191907572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	implementation 'org.springframework.boot:spring-boot-starter-mail'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 다룬 방식에서 크게 추가한 것은 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.properties&lt;/p&gt;
&lt;pre id=&quot;code_1660192219720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
spring.thymeleaf.order=0

## mail default settings
spring.mail.default-encoding=UTF-8
## mail custom settings
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=[이메일 계정]
spring.mail.password=[앱 비밀번호]
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.debug=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.mime.charset=UTF-8
spring.mail.properties.mail.transport.protocol=smtp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 구글 SMTP를 연동하였다. 이와 관련된 내용은 &lt;a href=&quot;https://born2bedeveloper.tistory.com/14?category=1038709%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳에&lt;/a&gt; 자세히 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VrJs2/btrJpM6p2oe/QT0rngKLmhKW4PTlYYIhxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VrJs2/btrJpM6p2oe/QT0rngKLmhKW4PTlYYIhxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VrJs2/btrJpM6p2oe/QT0rngKLmhKW4PTlYYIhxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVrJs2%2FbtrJpM6p2oe%2FQT0rngKLmhKW4PTlYYIhxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;376&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 더 이상 보안 수준이 낮은 앱 허용을 지원하지 않으므로 앱 비밀번호를 생성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그에 관한 내용도 윗 링크에 나와있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;화면 구현&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POSTMAN을 통해 기능을 검증할 수 있지만, 이왕 타임리프를 쓰는 김에 view 부분도 간단하게 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;templateMail.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660192514330&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Send Mail&amp;lt;/title&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/jquery-ui/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/common/common-ui.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
	&amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;템플릿 메일 보내기&amp;lt;/h1&amp;gt;
 &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;form th:action=&quot;@{/mail/template/send}&quot; method=&quot;post&quot;&amp;gt;
        &amp;lt;table&amp;gt;
            &amp;lt;tr id=&quot;box&quot; class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;메일 주소&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;address&quot; placeholder=&quot;이메일 주소를 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
					&amp;lt;input type=&quot;button&quot; class=&quot;form-control&quot; value=&quot;추가&quot; onclick=&quot;add_textbox(this)&quot;&amp;gt;
				&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr id=&quot;box2&quot; class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;참조 메일 주소&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;ccAddress&quot; placeholder=&quot;참조 수신인을 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
					&amp;lt;input type=&quot;button&quot; class=&quot;form-control&quot; value=&quot;추가&quot; onclick=&quot;add_textbox2(this)&quot;&amp;gt;
				&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;제목&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;title&quot; placeholder=&quot;제목을 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;템플릿 이름&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;template&quot; placeholder=&quot;템플릿 이름을 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/table&amp;gt;
        &amp;lt;button class=&quot;btn btn-default&quot;&amp;gt;발송&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
 &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;script&amp;gt;
	const add_textbox = () =&amp;gt; {
	    const box = document.getElementById(&quot;box&quot;);
	    const newP = document.createElement(&quot;tr&quot;);
	
	    newP.innerHTML = &quot;&amp;lt;th&amp;gt;메일 주소&amp;lt;/th&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='text' name='address' &amp;gt;&amp;lt;input type='button' value='삭제' onclick='opt_remove(this)'&amp;gt;&amp;lt;/td&amp;gt;&quot;;
		box.parentNode.insertBefore(newP, box.nextSibling);
	}
	
	const add_textbox2 = () =&amp;gt; {
	    const box = document.getElementById(&quot;box2&quot;);
	    const newP = document.createElement(&quot;tr&quot;);

	    newP.innerHTML = &quot;&amp;lt;th&amp;gt;참조 메일 주소&amp;lt;/th&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='text' name='ccAddress' &amp;gt;&amp;lt;input type='button' value='삭제' onclick='opt_remove2(this)'&amp;gt;&amp;lt;/td&amp;gt;&quot;;
		box.parentNode.insertBefore(newP, box.nextSibling);
		}
	
	const opt_remove = (obj) =&amp;gt; {
	     document.getElementById('box').parentElement.removeChild(obj.parentElement.parentElement);
	}
	const opt_remove2 = (obj) =&amp;gt; {
	     document.getElementById('box2').parentElement.removeChild(obj.parentElement.parentElement);
	}
&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;result.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660194435778&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Bootstrap Example&amp;lt;/title&amp;gt;
  &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;div class=&quot;container&quot;&amp;gt;
  &amp;lt;h2&amp;gt;Email 전송 완료.&amp;lt;/h2&amp;gt;    
&amp;lt;/div&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(정말 최소한의 디자인만 넣었다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;무료 템플릿 선정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 직접 작성한 html 파일을 사용해서 메일을 보내도 되지만, 무료로 템플릿 테마를 가져올 수 있으니 괜찮은 놈으로 하나 골라서 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트는 많지만 필자는 아래 사이트를 참고하여 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://unlayer.com/templates&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://unlayer.com/templates&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660192729293&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Free HTML Email Templates and Editor&quot; data-og-description=&quot;Browse hundreds of HTML email templates and choose the best for your business. Wide range of templates available for every industry and usage. Start for free.&quot; data-og-host=&quot;unlayer.com&quot; data-og-source-url=&quot;https://unlayer.com/templates&quot; data-og-url=&quot;https://unlayer.com/templates&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2hW17/hyPorVfeop/1t0wTekm0PU4vlA1NOI5t1/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/lN4yg/hyPpGJ1FLX/F1JFuUjBTAG1AaFTUH2MK0/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627&quot;&gt;&lt;a href=&quot;https://unlayer.com/templates&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://unlayer.com/templates&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2hW17/hyPorVfeop/1t0wTekm0PU4vlA1NOI5t1/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/lN4yg/hyPpGJ1FLX/F1JFuUjBTAG1AaFTUH2MK0/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Free HTML Email Templates and Editor&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Browse hundreds of HTML email templates and choose the best for your business. Wide range of templates available for every industry and usage. Start for free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;unlayer.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 로그인으로 무료 템플릿을 본인 입맛에 맞게 수정하여 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이것도 귀찮다.. 싶으신 분들은 필자가 대충 가져온 템플릿을 아래에 둘 테니 파일로 저장해서 사용하시라!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(같이 첨부된 이미지는 src/main/resources/static/images 경로로 폴더를 만들어 넣어두면 된다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/mmnKI/btrJnbFksky/gIllpuJOIDAwL1dZk8PWGk/%ED%85%9C%ED%94%8C%EB%A6%BF%EC%98%88%EC%8B%9C.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;템플릿예시.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.10MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받아온 템플릿의 경우, 위의 환경설정에서 classpath를 templates로 지정했으므로 src/main/resources/templates 경로 아래에 index.html을 넣어두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;잠깐! 만약 템플릿 지정 경로를 내가 원하는대로 지정하고 싶다면?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 아래의 포스팅에서 다뤘으니 참고하여 적용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/69?category=1038709&quot;&gt;https://born2bedeveloper.tistory.com/69?category=1038709&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1660193252207&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] Thymeleaf 경로 변경 &amp;amp; 다중 경로 설정&quot; data-og-description=&quot;이 포스팅은 타임리프 템플릿 엔진 사용 시, html등의 파일을 프로젝트 '내부'가 아닌 '외부' (ex. C드라이브)에 두고 관리할 수 있는 방법을 다룬다. &amp;nbsp;Thymeleaf 경로 변경 별도의 설정이 없다면, 기본&quot; data-og-host=&quot;born2bedeveloper.tistory.com&quot; data-og-source-url=&quot;https://born2bedeveloper.tistory.com/69?category=1038709&quot; data-og-url=&quot;https://born2bedeveloper.tistory.com/69&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cuy8g6/hyPopXrM4y/1OboLx4upvCTSDts6V5iy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bAuoba/hyPos0WD2O/Eyf1tACqGxzTQqJBNvGaG1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/69?category=1038709&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://born2bedeveloper.tistory.com/69?category=1038709&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cuy8g6/hyPopXrM4y/1OboLx4upvCTSDts6V5iy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bAuoba/hyPos0WD2O/Eyf1tACqGxzTQqJBNvGaG1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] Thymeleaf 경로 변경 &amp;amp; 다중 경로 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅은 타임리프 템플릿 엔진 사용 시, html등의 파일을 프로젝트 '내부'가 아닌 '외부' (ex. C드라이브)에 두고 관리할 수 있는 방법을 다룬다. &amp;nbsp;Thymeleaf 경로 변경 별도의 설정이 없다면, 기본&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;born2bedeveloper.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;Service 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 템플릿 적용을 위한 준비가 끝났으니 본격적으로 로직을 구현해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 view의 form에서 받아온 값들을 저장할 DTO부터 생성하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MailDto&lt;/h4&gt;
&lt;pre id=&quot;code_1660193037990&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
 
@Getter
@Setter
@NoArgsConstructor
public class MailDto {
	private String from;
    private String[] address;
    private String[] ccAddress;
    private String title;
    private String content;
    private String template;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 인자로 받아오는 template의 경우 확장성을 고려하여 받아왔는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 template이 있다고 가정하고, 원하는 템플릿 이름(ex.index)을 받아오면 해당 디자인으로 메일을 전송할 수 있도록 고려하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MailService.java&lt;/p&gt;
&lt;pre id=&quot;code_1660193983970&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.IOException;
import java.util.HashMap;
import java.util.List;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

import com.example.demo.dto.MailDto;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor 
public class MailService {
	private final JavaMailSender emailSender;
    private final SpringTemplateEngine templateEngine;
    
        public void sendTemplateMessage(MailDto mailDto) throws MessagingException {	
    	MimeMessage message = emailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, &quot;UTF-8&quot;);
        //메일 제목 설정
        helper.setSubject(mailDto.getTitle());

        //수신자 설정
        helper.setTo(mailDto.getAddress());
        
        //참조자 설정
        helper.setCc(mailDto.getCcAddress());

        //템플릿에 전달할 데이터 설정
        HashMap&amp;lt;String, String&amp;gt; emailValues = new HashMap&amp;lt;&amp;gt;();
    	emailValues.put(&quot;name&quot;, &quot;jimin&quot;);
    	
        Context context = new Context();
        emailValues.forEach((key, value)-&amp;gt;{
            context.setVariable(key, value);
        });
    	              
        //메일 내용 설정 : 템플릿 프로세스
        String html = templateEngine.process(mailDto.getTemplate(), context);
        helper.setText(html, true);
        
        //템플릿에 들어가는 이미지 cid로 삽입
        helper.addInline(&quot;image1&quot;, new ClassPathResource(&quot;static/images/image-1.jpeg&quot;));

        //메일 보내기
        emailSender.send(message);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의깊게 봐야 할 것은 MimeMessageHelper 클래스의 addInline 메소드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 해당 메소드 없이 html 템플릿 메일을 보내게 되면 아래와 같이 이미지가 적용되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYtZM/btrJqalrEo6/9Je9QlnMjoOxNdWnvMRQDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYtZM/btrJqalrEo6/9Je9QlnMjoOxNdWnvMRQDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYtZM/btrJqalrEo6/9Je9QlnMjoOxNdWnvMRQDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYtZM%2FbtrJqalrEo6%2F9Je9QlnMjoOxNdWnvMRQDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1285&quot; height=&quot;228&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(당연한 일임.. 이미지 경로가 프로젝트 내부에 있으니 메일로 보내버리면 받는 서버에서는 이미지 경로를 찾을수가 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html 템플릿 안의 이미지를 전송하는 방법은 여러 가지가 있는데, 필자는 cid를 이용하여 이미지를 html안에 임베딩하여 보내는 방식을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 전송할 html 양식 안의 img 경로를 다음과 같이 설정해둔 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660194257233&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src='cid:image'&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 전송 전 &lt;span&gt;addInline&lt;span&gt; 메소드로 해당 cid를 적용해야한다!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660194294397&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helper.addInline(&quot;image&quot;, new ClassPathResource(&quot;static/images/image-1.jpeg&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cid의 이름과 addInline의 첫 번째 인자를 서로 매칭해주고, 두 번째 인자에는 실제 저장되어 있는 이미지 경로를 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(필자가 올려둔 index.html에서는 첫 번째 이미지만 임베딩 해둔 상태이니, 엑박이 불편한 분들은 직접 addInline을 통해 나머지 이미지를 임베딩 해보셔도 좋습니다 :D)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;Controller 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660194805748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.IOException;
import java.util.List;

import javax.mail.MessagingException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.domain.Email;
import com.example.demo.dto.MailDto;
import com.example.demo.service.MailService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MailController {
	
	private final MailService mailService;
	
	 
    public MailController(MailService mailService) {
        this.mailService = mailService;
    }
	
	@GetMapping(&quot;/templateMail&quot;)
    public String templateMailSend() {
		return &quot;templateMail&quot;;
	}
	
    @PostMapping(&quot;/mail/template/send&quot;)
    public String sendTemplateMail(MailDto mailDto, Model model) throws MessagingException {
    	mailService.sendTemplateMessage(mailDto);
        System.out.println(&quot;메일 전송 완료&quot;);
        return &quot;result&quot;;
    }
    

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 준비가 끝났다! 설레는 테스트의 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;이메일 전송 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xauPV/btrJrQGM87l/4pQ05GNlD0ATSwkkGHn7rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xauPV/btrJrQGM87l/4pQ05GNlD0ATSwkkGHn7rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xauPV/btrJrQGM87l/4pQ05GNlD0ATSwkkGHn7rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxauPV%2FbtrJrQGM87l%2F4pQ05GNlD0ATSwkkGHn7rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1045&quot; height=&quot;364&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;떨리는 마음으로 발송을 눌러보면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1069&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blLRfo/btrJu0BI7bg/VK0NU4AmQdYMAJmg2OPDK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blLRfo/btrJu0BI7bg/VK0NU4AmQdYMAJmg2OPDK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blLRfo/btrJu0BI7bg/VK0NU4AmQdYMAJmg2OPDK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblLRfo%2FbtrJu0BI7bg%2FVK0NU4AmQdYMAJmg2OPDK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1069&quot; height=&quot;139&quot; data-origin-width=&quot;1069&quot; data-origin-height=&quot;139&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료 시 리다이렉트한 페이지로 이동한다! 직접 보낸 계정으로 들어가 메일을 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBdc3j/btrJuSw5JM7/bjK6Hg9O1C3MRB5ZZir4e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBdc3j/btrJuSw5JM7/bjK6Hg9O1C3MRB5ZZir4e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBdc3j/btrJuSw5JM7/bjK6Hg9O1C3MRB5ZZir4e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBdc3j%2FbtrJuSw5JM7%2FbjK6Hg9O1C3MRB5ZZir4e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;671&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 적용한 템플릿에 무사히 이미지까지 잘 임베딩 된 것을 확인할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포스팅을 마지막으로 이메일을 이만 종결하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 정복! 끝!&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>AuthenticationFailedException</category>
      <category>cid</category>
      <category>email</category>
      <category>html 템플릿</category>
      <category>Spring Boot</category>
      <category>이메일</category>
      <category>이메일 템플릿</category>
      <category>이메일 템플릿 이미지</category>
      <category>이미지 임베딩</category>
      <category>타임리프 템플릿</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/70</guid>
      <comments>https://born2bedeveloper.tistory.com/70#entry70comment</comments>
      <pubDate>Thu, 11 Aug 2022 14:18:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Thymeleaf 경로 변경 &amp;amp; 다중 경로 설정</title>
      <link>https://born2bedeveloper.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅은 타임리프 템플릿 엔진 사용 시, html등의 파일을 프로젝트 '내부'가 아닌 '외부' (ex. C드라이브)에 두고 관리할 수 있는 방법을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;Thymeleaf 경로 변경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 설정이 없다면, 기본 경로는 &lt;b&gt;src/main/resources/templates&amp;nbsp;&lt;/b&gt;로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 기본 경로값을 바꾸고 싶다면, application.properties 파일에서 간단하게 변경 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660183658743&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.thymeleaf.prefix=classpath:/other-template/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정하게 되면, 경로는 src/main/resources/other-template 폴더 안에 들어간 html파일을 기본 경로로 읽을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;Thymeleaf 다중 경로 설정&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 기존의 경로를 유지하면서 새로운 경로를 추가하고 싶다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #000000;&quot;&gt;ClassLoaderTemplateResolver 타입의 빈을 생성한 후 등록하면 가능하다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660183884477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public ClassLoaderTemplateResolver secondaryTemplateResolver() {
    ClassLoaderTemplateResolver secondaryTemplateResolver = new ClassLoaderTemplateResolver();
    secondaryTemplateResolver.setPrefix(&quot;other-template/&quot;);
    secondaryTemplateResolver.setSuffix(&quot;.html&quot;);
    secondaryTemplateResolver.setTemplateMode(TemplateMode.HTML);
    secondaryTemplateResolver.setCharacterEncoding(&quot;UTF-8&quot;);
    secondaryTemplateResolver.setOrder(1);
    secondaryTemplateResolver.setCheckExistence(true);
        
    return secondaryTemplateResolver;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setPrefix : &lt;b&gt;src/main/resources 아래의 새로운 경로를 추가해준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 타임리프 엔진이 &lt;b&gt;src/main/resources/templates 과 src/main/resources/other-template&amp;nbsp;&lt;/b&gt;두 가지의 경로를 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;Thymeleaf 외부 경로 설정&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내부가 아닌 C 드라이브와 같이 외부 경로로 설정하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우, 이메일 템플릿 엔진을 따로 외부 폴더를 두고 관리하고 싶었기 때문에 해당 방법을 유용하게 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660184223127&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templateresolver.FileTemplateResolver;

@Configuration
public class ThymeleafExtension {

    @Autowired
    private SpringTemplateEngine templateEngine;

    @PostConstruct
    public void extension() {
        FileTemplateResolver resolver = new FileTemplateResolver();
        resolver.setPrefix(&quot;C:\\template\\&quot;);
        resolver.setSuffix(&quot;.html&quot;);
        resolver.setTemplateMode(&quot;HTML5&quot;);
        resolver.setOrder(templateEngine.getTemplateResolvers().size());
        resolver.setCacheable(false);
        templateEngine.addTemplateResolver(resolver);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileTemplateResolver를 사용하여 템플릿을 외부 파일에서 탐색 가능하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 C드라이브 아래 template폴더에 html파일을 두어 관리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Error resolving template [hello], template might not exist or might not be accessible&lt;br /&gt;&amp;nbsp;by any of the configured Template Resolvers&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임리프를 사용하다보면 위의 에러를 참 많이 마주친다.. 대부분 경로 설정이 잘못됐거나 컨트롤러에서 해당 파일의 확장자 (.html)까지 붙여서 생기는 경우가 많으니 잘 살펴보시길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(특히! &lt;b&gt;src/main/resources&amp;nbsp;&lt;/b&gt;해당 경로 아래에 넣지 않아서 생기는 오류가 많다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/spring-thymeleaf-template-directory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/spring-thymeleaf-template-directory&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660183261022&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Changing the Thymeleaf Template Directory in Spring Boot | Baeldung&quot; data-og-description=&quot;Learn about Thymeleaf template locations.&quot; data-og-host=&quot;www.baeldung.com&quot; data-og-source-url=&quot;https://www.baeldung.com/spring-thymeleaf-template-directory&quot; data-og-url=&quot;https://www.baeldung.com/spring-thymeleaf-template-directory&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/U2ru7/hyPpDzFUeP/7nW8dpNG649wGBuOrdaaXK/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498,https://scrap.kakaocdn.net/dn/rIBLl/hyPpBaMTre/G6PliMMWnWkdQH3iu9G2V1/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/spring-thymeleaf-template-directory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.baeldung.com/spring-thymeleaf-template-directory&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/U2ru7/hyPpDzFUeP/7nW8dpNG649wGBuOrdaaXK/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498,https://scrap.kakaocdn.net/dn/rIBLl/hyPpBaMTre/G6PliMMWnWkdQH3iu9G2V1/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Changing the Thymeleaf Template Directory in Spring Boot | Baeldung&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn about Thymeleaf template locations.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.baeldung.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>Java</category>
      <category>Spring Boot</category>
      <category>Thymeleaf</category>
      <category>타임리프</category>
      <category>타임리프 경로 변경</category>
      <category>타임리프 경로 여러개</category>
      <category>타임리프 외부경로 설정</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/69</guid>
      <comments>https://born2bedeveloper.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 11 Aug 2022 11:21:13 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] 이메일 보내기 (2) - 참조(cc), 첨부 파일</title>
      <link>https://born2bedeveloper.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 오래전에 구글 SMTP 서버를 이용한 Email 전송에 관한 내용을 다룬 적이 있었는데, 생각보다 조회수가 매우 높았다. 아마 이메일 전송이 부가적인 기능으로 다양하게 사용되기 떄문이겠지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트에서 Email 서비스를 다시 구현하게 됐는데, 좀 더 세부적인 기능이 추가적으로 필요하여 두 번째 시리즈를 쓰기로 결심했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 다중 송신, 다중 참조(CC), 첨부파일 전송 등 다양한 옵션을 사용하여 좀 더 세부적으로 이메일 전송을 구현해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;++) 이전 프로젝트를 참고하고 오는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://born2bedeveloper.tistory.com/14&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660112952920&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] EMail 보내기&quot; data-og-description=&quot;기존 프로젝트를 베타 오픈하기 전에, 피드백을 받는 기능을 추가하면 좋겠다는 생각이 들었다. 일종의 고객센터 같은 느낌? 간단한 입력 form에 작성한 후 보내면, 관리자 계정으로 메일이 오도&quot; data-og-host=&quot;born2bedeveloper.tistory.com&quot; data-og-source-url=&quot;https://born2bedeveloper.tistory.com/14&quot; data-og-url=&quot;https://born2bedeveloper.tistory.com/14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d09cQg/hyPooX0cfD/DqjEpEqAIJXv9DUuJVQdv0/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/UHbNy/hyPoowVqYo/BOSrpP0MY5eLTJ8d79l7jk/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/ltv87/hyPojComLu/upN7tsAnqtyzWOxzMcawM1/img.png?width=1124&amp;amp;height=364&amp;amp;face=0_0_1124_364&quot;&gt;&lt;a href=&quot;https://born2bedeveloper.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://born2bedeveloper.tistory.com/14&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d09cQg/hyPooX0cfD/DqjEpEqAIJXv9DUuJVQdv0/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/UHbNy/hyPoowVqYo/BOSrpP0MY5eLTJ8d79l7jk/img.png?width=421&amp;amp;height=219&amp;amp;face=0_0_421_219,https://scrap.kakaocdn.net/dn/ltv87/hyPojComLu/upN7tsAnqtyzWOxzMcawM1/img.png?width=1124&amp;amp;height=364&amp;amp;face=0_0_1124_364');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] EMail 보내기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기존 프로젝트를 베타 오픈하기 전에, 피드백을 받는 기능을 추가하면 좋겠다는 생각이 들었다. 일종의 고객센터 같은 느낌? 간단한 입력 form에 작성한 후 보내면, 관리자 계정으로 메일이 오도&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;born2bedeveloper.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 개념은 이전 포스팅에서 다뤘기 때문에 구현 위주로 가겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;프로젝트 환경구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프로젝트에서 사용한 라이브러리, 설정 외에 더 추가할 것이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle&lt;/p&gt;
&lt;pre id=&quot;code_1660113055933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'

implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commons-io는 첨부 파일 구현에서 편리하게 쓸 메소드를 가져오는 데 쓰인다.&lt;br /&gt;(&lt;a href=&quot;https://born2bedeveloper.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 포스팅&lt;/a&gt; 에서 파일 업로드, 다운로드 관련으로 유용하게 쓰였음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.properties&lt;/p&gt;
&lt;pre id=&quot;code_1660113300849&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;## mail default settings
spring.mail.default-encoding=UTF-8
## mail custom settings
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=[구글계정]
spring.mail.password=[앱 비밀번호]
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.debug=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.mime.charset=UTF-8
spring.mail.properties.mail.transport.protocol=smtp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;잠깐!&lt;br /&gt;기존에는 메일 송신을 위해 보안 수준이 낮은 앱 및 Google계정 을 허용하여 날먹(?)으로 메일 송신이 가능했지만, 이제 더는 해당 방식을 지원하지 않는다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1353&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTSLLW/btrJqJNjdM8/k81ioOY8n4VJpjp59p87i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTSLLW/btrJqJNjdM8/k81ioOY8n4VJpjp59p87i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTSLLW/btrJqJNjdM8/k81ioOY8n4VJpjp59p87i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTSLLW%2FbtrJqJNjdM8%2Fk81ioOY8n4VJpjp59p87i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1353&quot; height=&quot;398&quot; data-origin-width=&quot;1353&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 2단계 인증을 설정한 후, 앱 비밀번호를 생성하여 해당 비밀번호를 기입해야만이 메일 송신이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 방법은 &lt;a href=&quot;https://born2bedeveloper.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 포스팅&lt;/a&gt; 아래 부분에서 자세히 다루고 있으니 참고하자 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;프로젝트 화면 구성 - view&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 타임리프 뷰 템플릿으로 간단하게 화면을 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;js부분을 추가해서 여러 명의 수신인과 참조인을 가능하도록 했으니 편하게 가져다 쓰면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;textMail.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660113757374&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Send Mail&amp;lt;/title&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/jquery-ui/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/common/common-ui.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
	&amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div class=&quot;container&quot;&amp;gt;
&amp;lt;div style=&quot;float: left; width: 50%;&quot;&amp;gt;
    &amp;lt;h1&amp;gt;텍스트 메일 보내기&amp;lt;/h1&amp;gt;
 
    &amp;lt;form th:action=&quot;@{/mail/send}&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
        &amp;lt;table&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;보내는 사람&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;from&quot; placeholder=&quot;이메일 주소를 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr id=&quot;box&quot; class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;받는 사람&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;address&quot; placeholder=&quot;이메일 주소를 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
					&amp;lt;input type=&quot;button&quot; class=&quot;form-control&quot; value=&quot;추가&quot; onclick=&quot;add_textbox(this)&quot;&amp;gt;
				&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr id=&quot;box2&quot; class=&quot;form-group&quot;&amp;gt;        
                &amp;lt;td&amp;gt;참조 메일 주소&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;ccAddress&quot; placeholder=&quot;참조 수신인을 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
					&amp;lt;input type=&quot;button&quot; class=&quot;form-control&quot; value=&quot;추가&quot; onclick=&quot;add_textbox2(this)&quot;&amp;gt;
				&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;제목&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;title&quot; placeholder=&quot;제목을 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;내용&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;textarea class=&quot;form-control&quot; name=&quot;content&quot; placeholder=&quot;보낼 내용을 입력하세요&quot;&amp;gt; &amp;lt;/textarea&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
            	&amp;lt;td&amp;gt;첨부 파일 &amp;lt;/td&amp;gt;
            	&amp;lt;td&amp;gt;
            		&amp;lt;input type=&quot;file&quot; name=&quot;file&quot; class=&quot;file-input&quot; /&amp;gt;
            	&amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/table&amp;gt;
        &amp;lt;button class=&quot;btn btn-default&quot;&amp;gt;발송&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

 &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;script&amp;gt;
	const add_textbox = (obj) =&amp;gt; {
	    const box = obj.parentElement.parentElement; 
	    const newP = document.createElement(&quot;tr&quot;);
	
	    newP.innerHTML = &quot;&amp;lt;tr class='form-group'&amp;gt;&amp;lt;td&amp;gt;메일 주소&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='text' class='form-control' name='address' &amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='button' class='form-control' value='삭제' onclick='opt_remove(this)'&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&quot;;
		box.parentNode.insertBefore(newP, box.nextSibling);
	}
	
	const add_textbox2 = (obj) =&amp;gt; {
	    const box = obj.parentElement.parentElement; 
	    const newP = document.createElement(&quot;tr&quot;);

	    newP.innerHTML = &quot;&amp;lt;tr class='form-group'&amp;gt;&amp;lt;td&amp;gt;참조 메일 주소&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='text' class='form-control' name='ccAddress' &amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type='button' class='form-control' value='삭제' onclick='opt_remove2(this)'&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&quot;;
		box.parentNode.insertBefore(newP, box.nextSibling);
		}
	
	const opt_remove = (obj) =&amp;gt; {
		obj.parentElement.parentElement.parentElement.removeChild(obj.parentElement.parentElement);
	}
	const opt_remove2 = (obj) =&amp;gt; {
		obj.parentElement.parentElement.parentElement.removeChild(obj.parentElement.parentElement);
	}
&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form 타입은 꼭 multipart로 지정해야 첨부파일이 잘 넘어간다. 아니면 null처리되니 주의!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;result.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660113898680&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Bootstrap Example&amp;lt;/title&amp;gt;
  &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;div class=&quot;container&quot;&amp;gt;
  &amp;lt;h2&amp;gt;Email 전송 완료.&amp;lt;/h2&amp;gt;    
&amp;lt;/div&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;DTO 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view에서 받아올 데이터를 맵핑할 DTO를 구현하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660120241015&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.dto;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
 
@Getter
@Setter
@NoArgsConstructor
public class MailDto {
	private String from;
    private String[] address;
    private String[] ccAddress;
    private String title;
    private String content;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;Controller 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660120404218&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.controller;

import java.io.IOException;
import java.util.List;

import javax.mail.MessagingException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import com.example.demo.domain.Email;
import com.example.demo.dto.MailDto;
import com.example.demo.service.MailService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MailController {
	
	private final MailService mailService;
	
	 
    public MailController(MailService mailService) {
        this.mailService = mailService;
    }
	
	@GetMapping(&quot;/textMail&quot;)
    public String mailSend() {
		return &quot;textMail&quot;;
	}
    
    @PostMapping(&quot;/mail/send&quot;)
    public String sendMail(MailDto mailDto, MultipartFile file) throws MessagingException, IOException {
    	mailService.sendMultipleMessage(mailDto, file);
        System.out.println(&quot;메일 전송 완료&quot;);
        return &quot;result&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첨부파일의 경우 MultipartFile 형식으로 받아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;Service 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660120641009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.service;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.example.demo.dto.MailDto;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor 
public class MailService {
 
    private final JavaMailSender emailSender;
    
    public void sendMultipleMessage(MailDto mailDto, MultipartFile file) throws MessagingException, IOException {
    	MimeMessage message = emailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        //메일 제목 설정
        helper.setSubject(mailDto.getTitle());

        //참조자 설정
        helper.setCc(mailDto.getCcAddress());        
        
        helper.setText(mailDto.getContent(), false);
        
        helper.setFrom(mailDto.getFrom());
        
        //첨부 파일 설정
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());
        
        helper.addAttachment(MimeUtility.encodeText(fileName, &quot;UTF-8&quot;, &quot;B&quot;), new ByteArrayResource(IOUtils.toByteArray(file.getInputStream())));
                       
        //수신자 개별 전송       
//        for(String s : mailDto.getAddress()) {
//        	helper.setTo(s);
//        	emailSender.send(message);
//        }
        //수신자 한번에 전송
        helper.setTo(mailDto.getAddress());
        emailSender.send(message);
        log.info(&quot;mail multiple send complete.&quot;);
   
        
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 text기반 메일을 보냈을 때와 달리 첨부파일을 담기 위해 MimeMessage 객체를 사용하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;&lt;b&gt;MimeMessageHelperclass&lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;MIME 메시지를 만들기 위한 클래스&lt;br /&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;HTML 레이아웃에서 이미지, 일반적인 메일 첨부 파일 및 텍스트 내용을 지원&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MIME이란?&lt;br /&gt;Multipurpose Internet Mail Extensions의 약자이다.&lt;br /&gt;기존 UUEncoding 방식은 ASCII(텍스트) 파일만 지원하여 음악, 워드 파일 등의 바이너리 파일을 전송할 수 없음&lt;br /&gt;이러한 방식을 보안하여 나온 인코딩 방식이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조, 수신인을 설정할때도 간단하다. setTo와 setCc 메소드를 활용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 수신인이나 참조인지 다중(여러 명)일 경우와 단일일 경우의 메소드가 둘 다 구현되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zQK5g/btrJqmkQKke/25eGDOkN8kOuWz8vbUZdK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zQK5g/btrJqmkQKke/25eGDOkN8kOuWz8vbUZdK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zQK5g/btrJqmkQKke/25eGDOkN8kOuWz8vbUZdK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzQK5g%2FbtrJqmkQKke%2F25eGDOkN8kOuWz8vbUZdK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;246&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A방식 메일 : 수신인[a, b, c, d....] 제목: &quot;제목&quot; 내용 : &quot;내용&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B방식 메일 : 수신인[a]&amp;nbsp; 제목: &quot;제목&quot; 내용: &quot;내용&quot;&amp;nbsp; ,&amp;nbsp; &amp;nbsp; 수신인[b]&amp;nbsp; 제목: &quot;제목&quot; 내용: &quot;내용&quot;....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 여러 수신인에게 메일을 보내는 A 방식의 경우 setTo에 수신인 리스트를 넣어두고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 수신인에게 하나의 메일을 여러개 보내는 B 방식의 경우 setTo에 개별 수신인을 넣어 전송하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(코드에 주석처리되어있으니 편한대로 사용하시라!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;실행 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 프로젝트를 구동하여 메일이 정상적으로 보내지는지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0MerR/btrJql7m6Qn/YG32B9ujWvXD5bynNahbQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0MerR/btrJql7m6Qn/YG32B9ujWvXD5bynNahbQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0MerR/btrJql7m6Qn/YG32B9ujWvXD5bynNahbQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0MerR%2FbtrJql7m6Qn%2FYG32B9ujWvXD5bynNahbQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1167&quot; height=&quot;505&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료다! 직접 메일을 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVyPbG/btrJm7Csuop/akaJKdtQlbzqMiGsCuokO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVyPbG/btrJm7Csuop/akaJKdtQlbzqMiGsCuokO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVyPbG/btrJm7Csuop/akaJKdtQlbzqMiGsCuokO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVyPbG%2FbtrJm7Csuop%2FakaJKdtQlbzqMiGsCuokO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;153&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGoT7/btrJrfMkYqa/EeqSz0rVJsa3fo2KZKpHwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGoT7/btrJrfMkYqa/EeqSz0rVJsa3fo2KZKpHwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGoT7/btrJrfMkYqa/EeqSz0rVJsa3fo2KZKpHwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGoT7%2FbtrJrfMkYqa%2FEeqSz0rVJsa3fo2KZKpHwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;418&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조도 무사히 달렸고, 파일도 잘 첨부되었다!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅은 html template를 적용한 이메일 전송을 다뤄볼 예정이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>AuthenticationFailedException</category>
      <category>email</category>
      <category>gmail</category>
      <category>Spring Boot</category>
      <category>spring boot email</category>
      <category>spring boot email 첨부파일</category>
      <category>보안 수준이 낮은 앱</category>
      <category>이메일 cc</category>
      <category>이메일 참조</category>
      <category>이메일 첨부파일</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/68</guid>
      <comments>https://born2bedeveloper.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 10 Aug 2022 18:02:26 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] SMS 전송 - NAVER SMS API 연동</title>
      <link>https://born2bedeveloper.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;진행중인 프로젝트 내에 SMS 서비스를 구현할 일이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 공부용 무료 SMS 서비스를 찾다가 NAVER CLOUD PLATFORM에서 지원하는 SMS API 서비스를 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 coolsms를 많이들 이용했는데 개인적으로 네이버가 친숙한 플랫폼이기도 하고, 매월 50건의 무료메세지 발송 및 첫 가입 시 10만 크레딧을 제공한다고 하여 선택하게 됐다. 해당 크레딧은 네이버 클라우드 플랫폼에서 제공하는 다른 솔루션에도 사용할 수 있으니 유용하게 쓰시길 바란다. (단, 실제로 결제가 가능한 카드를 연동해야 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 네이버에서 API 문서를 잘 구성해놓은 것도 채택에 큰 비중을 차지했다. 따라서 필자는 네이버 클라우드 플랫폼에서 제공하는 NAVER API 가이드에 따라 차근차근 진행해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;NAVER SMS API 사용을 위한 환경구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 네이버 클라우드 플랫폼에 가입해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.ncloud.com/&quot;&gt;https://www.ncloud.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660106313288&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;NAVER CLOUD PLATFORM&quot; data-og-description=&quot;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&quot; data-og-host=&quot;www.ncloud.com&quot; data-og-source-url=&quot;https://www.ncloud.com/&quot; data-og-url=&quot;https://www.ncloud.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GoKIf/hyPoq2tk9S/Ve8NttblmQ4SwW9CaXeV5k/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ncloud.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GoKIf/hyPoq2tk9S/Ve8NttblmQ4SwW9CaXeV5k/img.jpg?width=526&amp;amp;height=274&amp;amp;face=0_0_526_274');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;NAVER CLOUD PLATFORM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ncloud.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 링크로 들어가 가입한 후, &lt;a href=&quot;https://console.ncloud.com/sens/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳&lt;/a&gt;을 클릭해 콘솔로 편하게 이동해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Simple &amp;amp; Easy Notification Service (SMS 서비스의 이름이다.) 의 Home을 찾아가 프로젝트 생성하기를 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTfipQ/btrJjLM6ZzV/C3NggsZCTf3tRmyTd8CvVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTfipQ/btrJjLM6ZzV/C3NggsZCTf3tRmyTd8CvVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTfipQ/btrJjLM6ZzV/C3NggsZCTf3tRmyTd8CvVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTfipQ%2FbtrJjLM6ZzV%2FC3NggsZCTf3tRmyTd8CvVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;476&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 설정값을 입력하여 생성해보자. (필자는 SMS만 다룰것이므로 한 곳만 체크했다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byHejo/btrJlOI34GQ/uoQ8zfjXeXD5VEegyuk5w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byHejo/btrJlOI34GQ/uoQ8zfjXeXD5VEegyuk5w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byHejo/btrJlOI34GQ/uoQ8zfjXeXD5VEegyuk5w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyHejo%2FbtrJlOI34GQ%2FuoQ8zfjXeXD5VEegyuk5w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;499&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 후에는 왼쪽 NAV바의 Project를 클릭하면 방금 만든 프로젝트를 조회할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaU1z/btrJm7hnRQP/UJJRK2Qis8BpsBf2jRT9Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaU1z/btrJm7hnRQP/UJJRK2Qis8BpsBf2jRT9Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaU1z/btrJm7hnRQP/UJJRK2Qis8BpsBf2jRT9Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxaU1z%2FbtrJm7hnRQP%2FUJJRK2Qis8BpsBf2jRT9Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;363&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 프로젝트의 SMS 버튼을 눌러 발신 번호를 등록하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(++2015년부터 발신번호 사전등록제가 시행되어 사전등록된 발신번호로만 문자 발송이 가능하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMS &amp;gt; CallingNumber 에서 발신번호 등록이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYgdNw/btrJp1HdOOs/6Ry1BMU1F1M1ZNkDFEgLW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYgdNw/btrJp1HdOOs/6Ry1BMU1F1M1ZNkDFEgLW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYgdNw/btrJp1HdOOs/6Ry1BMU1F1M1ZNkDFEgLW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYgdNw%2FbtrJp1HdOOs%2F6Ry1BMU1F1M1ZNkDFEgLW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;317&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록이 완료되었다. 해당 발신번호는 이후 API 요청에 필요하므로 까먹지 말고 기억해두자~ (보통 본인 번호를 넣을테니 까먹을 이유는 없겠지만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;환경 변수 설정하기 - KEY &amp;amp; ServiceId&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적인 API 요청을 위한 KEY와 ID값을 발급받아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 경우와 다르게, 네이버는 토근 발급 없이 헤더에 명시한 값들만 넣어주면 한번에 API 기능들을 요청할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 대부분의 경우는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;발급받은 key 값으로 서비스에 token 요청&lt;/li&gt;
&lt;li&gt;획득한 token을 헤더에 넣어 API 요청을 위한 입장권처럼 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 SMS API의 경우 1번 요청을 패스하고 헤더에 규정된 값을 넣어 바로 본격적인 요청이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, KEY값을 구해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmAJ9/btrJnbqnlQv/mUDxAPAXdPpPsScOuESCa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmAJ9/btrJnbqnlQv/mUDxAPAXdPpPsScOuESCa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmAJ9/btrJnbqnlQv/mUDxAPAXdPpPsScOuESCa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmAJ9%2FbtrJnbqnlQv%2FmUDxAPAXdPpPsScOuESCa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;680&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이페이지 &amp;gt; 계정 관리 &amp;gt; 인증키관리 &amp;gt; 신규 API 인증키 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 AccessKey와 SecretKey를 잘 백업해두자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/llh15/btrJn7hhsLB/xWhPfyRXOVHWDEIBklaLw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/llh15/btrJn7hhsLB/xWhPfyRXOVHWDEIBklaLw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/llh15/btrJn7hhsLB/xWhPfyRXOVHWDEIBklaLw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fllh15%2FbtrJn7hhsLB%2FxWhPfyRXOVHWDEIBklaLw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1102&quot; height=&quot;236&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 ServiceId를 가져오자. 해당 값은 SMS서비스 프로젝트를 만들때 이미 발급되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://console.ncloud.com/sens/project%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로젝트 콘솔화면&lt;/a&gt; &amp;lt;&amp;lt; 으로 들어가 서비스ID(열쇠모양)를 클릭하면 서비스ID를 발급받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nvvrm/btrJlFF6uWE/ZUxOTNXf4kqyqKiXivVhG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nvvrm/btrJlFF6uWE/ZUxOTNXf4kqyqKiXivVhG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nvvrm/btrJlFF6uWE/ZUxOTNXf4kqyqKiXivVhG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNvvrm%2FbtrJlFF6uWE%2FZUxOTNXf4kqyqKiXivVhG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;439&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 필요한 환경구성은 끝났다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;SMS 전송을 위한 헤더 구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 언급한대로, SMS API 요청을 하기 위해선 NAVER에서 지정해둔 포맷에 따라 헤더를 구현해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp81R7/btrJp0V5z9K/qSThSMN4uCkKKag1aJXK01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp81R7/btrJp0V5z9K/qSThSMN4uCkKKag1aJXK01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp81R7/btrJp0V5z9K/qSThSMN4uCkKKag1aJXK01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp81R7%2FbtrJp0V5z9K%2FqSThSMN4uCkKKag1aJXK01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1239&quot; height=&quot;439&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 헤더를 좀 더 풀어 설명해보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 시간, JAVA에서는 System.currentTimeMillis()로 간단하게 구현 가능&lt;/li&gt;
&lt;li&gt;아까 발급받은 AccessKey값&lt;/li&gt;
&lt;li&gt;아까 발급받은 SecretKey값을 적절하게 암호화 한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;띠용? 암호화라고? 걱정하지마라 링크로 들어가면 암호화하는 코드도 아~주 상세하게 나와있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크의 makeSigniture() 메소드를 거의 그대로 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/common-ncpapi&quot;&gt;https://api.ncloud-docs.com/docs/common-ncpapi&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660109151761&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Ncloud API&quot; data-og-description=&quot; &quot; data-og-host=&quot;api.ncloud-docs.com&quot; data-og-source-url=&quot;https://api.ncloud-docs.com/docs/common-ncpapi&quot; data-og-url=&quot;https://api.ncloud-docs.com/docs/common-ncpapi&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/common-ncpapi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.ncloud-docs.com/docs/common-ncpapi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ncloud API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.ncloud-docs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 헤더에 관련된 자세한 정보도 나와있으니 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로젝트의 환경설정 파일에서 아까 발급받은 KEY값과 serviceID를 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(직접 코드에 그대로 붙여넣어 사용하는 것은 지양하도록 하자.. 아주 별로다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;application.properties&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PMxTg/btrJpeghvyl/oBAWHU9qhYoH2gWCvaDKTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PMxTg/btrJpeghvyl/oBAWHU9qhYoH2gWCvaDKTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PMxTg/btrJpeghvyl/oBAWHU9qhYoH2gWCvaDKTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPMxTg%2FbtrJpeghvyl%2FoBAWHU9qhYoH2gWCvaDKTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;103&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 senderPhone은 아까 프로젝트에서 설정한 발신번호를 기입하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;build.gradle&lt;/h4&gt;
&lt;pre id=&quot;code_1660111385610&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'
    
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 필요한 Dependency들도 미리 설정해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(maven인 경우 httpclient, thymeleaf, spring-web, lombok을 해당 형식에 맞게 설정하면 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 3가지 헤더 중 암호화가 필요한 마지막 헤더 구성을 위한 메소드를 Service단에 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SmsService.java&lt;/h4&gt;
&lt;pre id=&quot;code_1660109807688&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor 
@Service
public class SmsService {
	
	@Value(&quot;${naver-cloud-sms.accessKey}&quot;)
	private String accessKey;
	
	@Value(&quot;${naver-cloud-sms.secretKey}&quot;)
	private String secretKey;
	
	@Value(&quot;${naver-cloud-sms.serviceId}&quot;)
	private String serviceId;

	@Value(&quot;${naver-cloud-sms.senderPhone}&quot;)
	private String phone;

....
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 설정한 환경 변수들을 맨 위에 선언해주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660109832254&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String makeSignature(Long time) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
		String space = &quot; &quot;;
        String newLine = &quot;\n&quot;;
        String method = &quot;POST&quot;;
        String url = &quot;/sms/v2/services/&quot;+ this.serviceId+&quot;/messages&quot;;
        String timestamp = time.toString();
        String accessKey = this.accessKey;
        String secretKey = this.secretKey;

        String message = new StringBuilder()
                .append(method)
                .append(space)
                .append(url)
                .append(newLine)
                .append(timestamp)
                .append(newLine)
                .append(accessKey)
                .toString();

        SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(&quot;UTF-8&quot;), &quot;HmacSHA256&quot;);
        Mac mac = Mac.getInstance(&quot;HmacSHA256&quot;);
        mac.init(signingKey);

        byte[] rawHmac = mac.doFinal(message.getBytes(&quot;UTF-8&quot;));
        String encodeBase64String = Base64.encodeBase64String(rawHmac);

        return encodeBase64String;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signature 필드 값 생성을 위한 메서드를 작성하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 API요청을 위한 헤더 구성이 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;메시지 발송 - Request &amp;amp; Response 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더 구성이 끝났으니, 메시지 요청을 위한 Request 객체와 API 요청 반환값을 담아올 Response 객체를 생성해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청, 반환에 쓰이는 필드값은 공식 문서에 아주 상세히 나와있다. 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송&quot;&gt;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660109983659&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;SMS API&quot; data-og-description=&quot; &quot; data-og-host=&quot;api.ncloud-docs.com&quot; data-og-source-url=&quot;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송&quot; data-og-url=&quot;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#%EB%A9%94%EC%8B%9C%EC%A7%80%EB%B0%9C%EC%86%A1&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SMS API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.ncloud-docs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn3wVz/btrJpSYiiWN/Kv501bsveZJ2ar0qKHKITk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn3wVz/btrJpSYiiWN/Kv501bsveZJ2ar0qKHKITk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn3wVz/btrJpSYiiWN/Kv501bsveZJ2ar0qKHKITk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn3wVz%2FbtrJpSYiiWN%2FKv501bsveZJ2ar0qKHKITk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1562&quot; height=&quot;917&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 요청에 필요한 항목이 많아보이지만, 공식 문서에서 보면 알 수 있듯이 모두 필수 값이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMS전송이기 때문에 LMS, MMS등에 필요한 요청 항목은 모두 제외했다. 또한 요청 항목의 messages부분은&amp;nbsp; DTO로 관리하여 그때 그때 맞는 발신자(to)와 내용(content)를 담도록 구현 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(subject의 경우는 LMS, MMS에서만 사용 가능하므로 제외한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MessageDTO&lt;/h4&gt;
&lt;pre id=&quot;code_1660110192519&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Builder
public class MessageDTO {
	String to;
	String content;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SmsRequestDTO&lt;/h4&gt;
&lt;pre id=&quot;code_1660110208809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Builder
public class SmsRequestDTO {
	String type;
	String contentType;
	String countryCode;
	String from;
	String content;
	List&amp;lt;MessageDTO&amp;gt; messages;
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SmsResponseDTO&lt;/h4&gt;
&lt;pre id=&quot;code_1660110219624&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Builder
public class SmsResponseDTO {
	String requestId;
	LocalDateTime requestTime;
	String statusCode;
	String statusName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;메시지 발송 - Service 메소드 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정말로 메시지 발송을 구현할 시간이다! Service단에 메시지 발송 메소드를 추가하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SmsService.java&lt;/h4&gt;
&lt;pre id=&quot;code_1660110348515&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public SmsResponseDTO sendSms(MessageDTO messageDto) throws JsonProcessingException, RestClientException, URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
		Long time = System.currentTimeMillis();
		
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		headers.set(&quot;x-ncp-apigw-timestamp&quot;, time.toString());
		headers.set(&quot;x-ncp-iam-access-key&quot;, accessKey);
		headers.set(&quot;x-ncp-apigw-signature-v2&quot;, makeSignature(time));
		
		List&amp;lt;MessageDTO&amp;gt; messages = new ArrayList&amp;lt;&amp;gt;();
		messages.add(messageDto);
		
		SmsRequestDTO request = SmsRequestDTO.builder()
				.type(&quot;SMS&quot;)
				.contentType(&quot;COMM&quot;)
				.countryCode(&quot;82&quot;)
				.from(phone)
				.content(messageDto.getContent())
				.messages(messages)
				.build();
		
		ObjectMapper objectMapper = new ObjectMapper();
		String body = objectMapper.writeValueAsString(request);
		HttpEntity&amp;lt;String&amp;gt; httpBody = new HttpEntity&amp;lt;&amp;gt;(body, headers);
		
		RestTemplate restTemplate = new RestTemplate();
	    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
	    SmsResponseDTO response = restTemplate.postForObject(new URI(&quot;https://sens.apigw.ntruss.com/sms/v2/services/&quot;+ serviceId +&quot;/messages&quot;), httpBody, SmsResponseDTO.class);

	    return response;	
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8번째 줄에서 아까 선언해둔 makeSignature 메소드를 사용해 3 번째 헤더를 생성하는 것을 볼 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;메시지 발송 - Controller&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API 동작을 테스트하는 방법은 POSTMAN 등 여러 방식이 있겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 view를 직접 만들어 테스트해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(귀찮은 사람들은 postman을 통해 controller에서 설정한 url로 테스트 가능하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SmsController.java&lt;/h4&gt;
&lt;pre id=&quot;code_1660110611075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.controller;

import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestClientException;

import com.example.demo.dto.MessageDTO;
import com.example.demo.dto.SmsResponseDTO;
import com.example.demo.service.SmsService;
import com.fasterxml.jackson.core.JsonProcessingException;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class SmsController {
	
	private final SmsService smsService;
	
	@GetMapping(&quot;/send&quot;)
	public String getSmsPage() {
		return &quot;sendSms&quot;;
	}
	
	@PostMapping(&quot;/sms/send&quot;)
	public String sendSms(MessageDTO messageDto, Model model) throws JsonProcessingException, RestClientException, URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
		SmsResponseDTO response = smsService.sendSms(messageDto);
		model.addAttribute(&quot;response&quot;, response);
		return &quot;result&quot;;
	}

	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;메시지 발송 - View&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임리프 view 템플릿을 사용하였다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;sendSms.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660110752912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Send Mail&amp;lt;/title&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/jquery-ui/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script type=&quot;text/javascript&quot; src=&quot;/scripts/common/common-ui.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
	&amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;텍스트 메일 보내기&amp;lt;/h1&amp;gt;
 
    &amp;lt;form th:action=&quot;@{/sms/send}&quot; method=&quot;post&quot;&amp;gt;
        &amp;lt;table&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;
                &amp;lt;td&amp;gt;발송할 전화번호&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;to&quot; placeholder=&quot;이메일 주소를 입력하세요&quot;&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            &amp;lt;tr class=&quot;form-group&quot;&amp;gt;        
                &amp;lt;td&amp;gt;내용&amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;
                    &amp;lt;textarea class=&quot;form-control&quot; name=&quot;content&quot; placeholder=&quot;보낼 내용을 입력하세요&quot;&amp;gt; &amp;lt;/textarea&amp;gt;
                &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/table&amp;gt;
        &amp;lt;button class=&quot;btn btn-default&quot;&amp;gt;발송&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;

 &amp;lt;/div&amp;gt;
 &amp;lt;/body&amp;gt;
 &amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;result.html&lt;/h4&gt;
&lt;pre id=&quot;code_1660110787503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Bootstrap Example&amp;lt;/title&amp;gt;
  &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script src=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;div class=&quot;container&quot;&amp;gt;
  &amp;lt;h2&amp;gt;SMS 내역 조회&amp;lt;/h2&amp;gt;        
  &amp;lt;table class=&quot;table table-bordered&quot;&amp;gt;
    &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;requestId&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;요청 시간&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Status Code&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Status Name&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
      &amp;lt;tr &amp;gt;
        &amp;lt;td th:text=&quot;${response.requestId}&quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&quot;${response.requestTime}&quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&quot;${response.statusCode}&quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&quot;${response.statusName}&quot;&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; border-width: 0px 0px 2px 10px; border-bottom-style: solid; border-bottom-color: #6E6E6E; padding: 3px 5px; border-left-style: solid; border-left-color: #6E6E6E; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;실행 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행결과를 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwtL1r/btsD3Mia1Io/awtxeWVwaU4SkeqNtlp0M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwtL1r/btsD3Mia1Io/awtxeWVwaU4SkeqNtlp0M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwtL1r/btsD3Mia1Io/awtxeWVwaU4SkeqNtlp0M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwtL1r%2FbtsD3Mia1Io%2FawtxeWVwaU4SkeqNtlp0M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;236&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgylXA/btrJqreRWIj/wjFDodVchoKuNDkrv5Qupk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgylXA/btrJqreRWIj/wjFDodVchoKuNDkrv5Qupk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgylXA/btrJqreRWIj/wjFDodVchoKuNDkrv5Qupk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgylXA%2FbtrJqreRWIj%2FwjFDodVchoKuNDkrv5Qupk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1218&quot; height=&quot;181&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무사히 전송된 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 NAVER CLOUD PLATFORM 콘솔 화면에 들어가도 전송 내역을 더 깔끔한 ui로 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1733&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LAIF8/btrJqqUzJ7k/MuqeCloJXEAvaRKqvulgFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LAIF8/btrJqqUzJ7k/MuqeCloJXEAvaRKqvulgFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LAIF8/btrJqqUzJ7k/MuqeCloJXEAvaRKqvulgFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLAIF8%2FbtrJqqUzJ7k%2FMuqeCloJXEAvaRKqvulgFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1733&quot; height=&quot;600&quot; data-origin-width=&quot;1733&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하며 외부 API를 사용할수록 기본적인 작동 방식이 다 비슷하구나.. 라는 점을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스 주체에서 발급받은 KEY값을 가지고 제시하는 규격에 맞게 요청하면 손쉽게 받아올 수 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 API 공식문서를 잘! 읽어봐야 한다는 점을 새삼 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사실 필자의 경우 예시에 나와있는 url을 그대로 가져다 써서 401오류로 1시간을 날렸다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAVER SMS API 정복!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <category>Java</category>
      <category>NAVER CLOUD PLATFORM</category>
      <category>SMS</category>
      <category>sms naver api</category>
      <category>SMS 전송</category>
      <category>springboot</category>
      <category>네이버 sms</category>
      <category>메세지 전송</category>
      <author>jimkwon</author>
      <guid isPermaLink="true">https://born2bedeveloper.tistory.com/67</guid>
      <comments>https://born2bedeveloper.tistory.com/67#entry67comment</comments>
      <pubDate>Wed, 10 Aug 2022 15:08:09 +0900</pubDate>
    </item>
  </channel>
</rss>