<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록과 반복</title>
    <link>https://lsh2016.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 00:06:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>HDev</managingEditor>
    <image>
      <title>기록과 반복</title>
      <url>https://tistory1.daumcdn.net/tistory/4703111/attach/ca8f45342a114b75bfbb0d6aa472e391</url>
      <link>https://lsh2016.tistory.com</link>
    </image>
    <item>
      <title>Java) 메모리 가시성 &amp;amp; 자바 메모리 모델(JMM)</title>
      <link>https://lsh2016.tistory.com/186</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 김영한님의 스프링 고급편 강의중 제목과 관련된 부분을 블로그장의 취향대로 요약한 것이며 강의 자료 및 출처는 가장 아래에서 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. CPU 캐시 메모리 이해&lt;/h2&gt;
&lt;pre id=&quot;code_1753247170322&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class VolatileFlagMain {

    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t = new Thread(task, &quot;work&quot;);
        log(&quot;runFlag = &quot; + task.runFlag);
        t.start();

        sleep(1000);
        log(&quot;runFlag를 false로 변경 시도&quot;);
        task.runFlag = false;
        log(&quot;runFlag = &quot; + task.runFlag);
        log(&quot;main 종료&quot;);
    }

    static class MyTask implements Runnable {
        boolean runFlag = true;
//        volatile boolean runFlag = true;

        @Override
        public void run() {
            log(&quot;task 시작&quot;);
            while (runFlag) {
                // runFlag가 false로 변하면 탈출
            }
            log(&quot;task 종료&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main스레드와 work스레드 두 개가 있다.&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;main()와 work()모두 runFlag라는 공통적인 값을 참조하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main()에서 work스레드를 생성 후, work스레드의 run()이 실행되는데 runFlag가 true인 상태이기 때문에,&lt;br /&gt;work스레드는 while()문을 계속 반복할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, main()에서 runFlag를 false로 변경하면서, &quot;main 종료&quot;와 &quot;task 종료&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;실제 결과는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2s2Tr/btsPuqh7tHf/TXdbDUX3cxRYpSGTbsjeJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2s2Tr/btsPuqh7tHf/TXdbDUX3cxRYpSGTbsjeJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2s2Tr/btsPuqh7tHf/TXdbDUX3cxRYpSGTbsjeJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2s2Tr%2FbtsPuqh7tHf%2FTXdbDUX3cxRYpSGTbsjeJ1%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;491&quot; height=&quot;145&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main만 종료되고, task종료는 출력되지 않았다.&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;997&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F1vWI/btsPuluBF1q/rnynyZY2AfULHk7Vhd4O21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F1vWI/btsPuluBF1q/rnynyZY2AfULHk7Vhd4O21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F1vWI/btsPuluBF1q/rnynyZY2AfULHk7Vhd4O21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF1vWI%2FbtsPuluBF1q%2FrnynyZY2AfULHk7Vhd4O21%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;977&quot; height=&quot;997&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 처리 성능을 개선하기 위해 중간에 캐시 메모리라는 것을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BkaFa/btsPun6R93o/Bm4mfo9GOkLV0XcZzSrI60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BkaFa/btsPun6R93o/Bm4mfo9GOkLV0XcZzSrI60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BkaFa/btsPun6R93o/Bm4mfo9GOkLV0XcZzSrI60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBkaFa%2FbtsPun6R93o%2FBm4mfo9GOkLV0XcZzSrI60%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;825&quot; height=&quot;492&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main 스레드에서 runFlag를 false로 변경하면 &lt;b&gt;메인 메모리에 값이 즉시 반영되지 않고 먼저, 캐시 메모리의 runFlag 값만 변환된다.&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;그렇다면, 캐시 메모리에 있는 runFlag값은 메인 메모리에 언제 반영될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 이 부분에 대한 정답은 CPU 설계 방식과 종류의 따라 다르기때문에 알 수 없다. 극단적으로 평생 반영되지 않을 수도 있다.&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgRRJk/btsPt7QJPvy/7XjvnimeMKgDKGsmob0Np1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgRRJk/btsPt7QJPvy/7XjvnimeMKgDKGsmob0Np1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgRRJk/btsPt7QJPvy/7XjvnimeMKgDKGsmob0Np1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgRRJk%2FbtsPt7QJPvy%2F7XjvnimeMKgDKGsmob0Np1%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;773&quot; height=&quot;452&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 메인 메모리에 있는 runFlag 값을 work 스레드의 캐시 메모리에 저장해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 부분 또한 언제 반영되는지 알아야할텐데 CPU 설계 방식과 종류에 따라 다르기때문에 알 수 없다.&amp;nbsp;&lt;/b&gt;&lt;/p&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.메모리 가시성&lt;/h2&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 메모리 가시성 문제 해결 - volatile&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 메모리를 사용하면 CPU 처리 성능을 개선할 수 있지만, 여러 스레드에서 같은 시점에 정확히 같은 데이터를 보는 것이 더 중요한 상황이라면 문제가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 성능을 약간 포기하더라도 자바에서는 이런 경우 volatile 키워드를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1753248284500&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class VolatileFlagMain {

    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t = new Thread(task, &quot;work&quot;);
        log(&quot;runFlag = &quot; + task.runFlag);
        t.start();

        sleep(1000);
        log(&quot;runFlag를 false로 변경 시도&quot;);
        task.runFlag = false;
        log(&quot;runFlag = &quot; + task.runFlag);
        log(&quot;main 종료&quot;);
    }

    static class MyTask implements Runnable {
//        boolean runFlag = true;
        volatile boolean runFlag = true;

        @Override
        public void run() {
            log(&quot;task 시작&quot;);
            while (runFlag) {
                // runFlag가 false로 변하면 탈출
            }
            log(&quot;task 종료&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/smCJd/btsPvYq3MAx/8uPqbBsWMmtaEwK7MKTBY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/smCJd/btsPvYq3MAx/8uPqbBsWMmtaEwK7MKTBY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/smCJd/btsPvYq3MAx/8uPqbBsWMmtaEwK7MKTBY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsmCJd%2FbtsPvYq3MAx%2F8uPqbBsWMmtaEwK7MKTBY1%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;506&quot; height=&quot;163&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;192&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wujQu/btsPu7ChRuY/p63HCLYfkhnsKqVK7ikO51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wujQu/btsPu7ChRuY/p63HCLYfkhnsKqVK7ikO51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wujQu/btsPu7ChRuY/p63HCLYfkhnsKqVK7ikO51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwujQu%2FbtsPu7ChRuY%2Fp63HCLYfkhnsKqVK7ikO51%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;718&quot; height=&quot;409&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main스레드와 work스레드 모두 캐시 메모리가 아닌, 메인 메모리에서 직접 값을 반영하고 조회하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.자바 메모리 모델(Java Memory Model)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 메모리 모델(JMM)은 자바 프로그램이 어떻게 메모리에 접근하고 수정할 수 있는지 규정하는데,&amp;nbsp;&lt;br /&gt;특히 멀티스레드 프로그래밍에서 스레드 간의 상호작용을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMM의 핵심은 여러 스레드들의 작업 순서를 보장하는 happens-before 관계에 대한 정의이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1.happens-before 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;happens-before 관계는 자바 메모리 모델에서 스레드 간의 작업 순서를 정의하는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 A 작업이 B 작업보다 happens-before 관계에 있다면, A 작업에서의 모든 메모리 변경 사항은 B 작업에서 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, A 작업에서 변경된 내용은 B 작업이 시작되기 전에 모두 메모리에 반영된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. happens-before 관계가 발생하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.단일 스레드 내에서, 프로그램의 순서대로 작성된 모든 명령문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;pre id=&quot;code_1753248746364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int a = 1; int b = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경우에서 a = 1은 b = 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;b&gt;2. volatile 변수 규칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 스레드에서 volatile 변수에 대한 쓰기 작업은 해당 변수를 읽는 모든 스레드에 보이도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, volatile변수에 대한 쓰기 작업은 그 변수를 읽는 작업보다 happens-before 관계를 형성한다.&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;3. 스레드 시작 규칙&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753248882820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; Thread t = new Thread(task);
 t.start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start() 호출 전에 수행된 모든 작업은 새로운 스레드가 시작된 후의 작업보다 happens-before 관계를 가진다.&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;4. 모니터 락 규칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 스레드에서 synchronized 블록을 종료한 후, 그 모니터 락을 얻는 모든 스레드는 해당 블록 내의 모든 작업을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, synchronized(lock) { ... } 블록 내에서의 작업은 블록을 나가는 시점에 happens-before 관계가 형성된다. 뿐만 아니라 ReentrantLock과 같이 락을 사용하는 경우에도 happens-before 관계가 성립한다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;* 출처 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753249407600&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;김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5점 수강생 7463명인 강의를 만나보세요. 멀티스레드와 동시성을 기초부터 실무 레벨까지 깊이있게 학습합니다. 멀티스레드, 동시성, 스레드 풀과 Executor 프레임워크, 프로세스와 스레&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k3WOc/hyZqYtzI76/KNRuvryXgY3kyaKpkdAvsk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bzfS1t/hyZqZ0j0eB/fTZcWAiSB7kIHIE013Gzs1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/pkk4A/hyZqVDCrTA/6kFUk4sqK4tMY2ZoGlTCY0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k3WOc/hyZqYtzI76/KNRuvryXgY3kyaKpkdAvsk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bzfS1t/hyZqZ0j0eB/fTZcWAiSB7kIHIE013Gzs1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/pkk4A/hyZqVDCrTA/6kFUk4sqK4tMY2ZoGlTCY0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798');&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;김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5점 수강생 7463명인 강의를 만나보세요. 멀티스레드와 동시성을 기초부터 실무 레벨까지 깊이있게 학습합니다. 멀티스레드, 동시성, 스레드 풀과 Executor 프레임워크, 프로세스와 스레&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/186</guid>
      <comments>https://lsh2016.tistory.com/186#entry186comment</comments>
      <pubDate>Wed, 23 Jul 2025 14:40:33 +0900</pubDate>
    </item>
    <item>
      <title>Java) 프로세스 &amp;amp; 스레드, 컨텍스트 스위칭</title>
      <link>https://lsh2016.tistory.com/185</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 김영한님의 스프링 고급편 강의중 제목과 관련된 부분을 블로그장의 취향대로 요약한 것이며 강의 자료 및 출처는 가장 아래에서 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. CPU가 프로그램을 처리하는 방식&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vm7td/btsPtWHXmdz/ui4QnYUdkTuRTvru7ZOmc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vm7td/btsPtWHXmdz/ui4QnYUdkTuRTvru7ZOmc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vm7td/btsPtWHXmdz/ui4QnYUdkTuRTvru7ZOmc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvm7td%2FbtsPtWHXmdz%2Fui4QnYUdkTuRTvru7ZOmc0%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;172&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 프로그램A와 B예를 들어, 음악 프로그램과 워드 프로그램이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위에서, CPU코어 1번이 프로그램A의 1,2,3,4를 모두 끝낸후에 프로그램 B를 처리한다면 사용자는 어떨까?&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;따라서 멀티태스킹이라는 기술이 도입되게 되는데 CPU가 프로그램 A,B를 번갈아서 실행하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bspbXT/btsPu9lGG8m/2Fc6DB99kTYHMHlzG2R3lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bspbXT/btsPu9lGG8m/2Fc6DB99kTYHMHlzG2R3lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bspbXT/btsPu9lGG8m/2Fc6DB99kTYHMHlzG2R3lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbspbXT%2FbtsPu9lGG8m%2F2Fc6DB99kTYHMHlzG2R3lK%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;481&quot; height=&quot;164&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 프로그램 A의 일부를 실행하고, B의 일부를 번갈아서 실행하여 마치 동시에 실행되는 것 처럼 빠르게 실행한다.&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;CPU가 여러개라면?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pTwtB/btsPuOoFJ2H/nyKR52PoSSInU6Xpwya5ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pTwtB/btsPuOoFJ2H/nyKR52PoSSInU6Xpwya5ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pTwtB/btsPuOoFJ2H/nyKR52PoSSInU6Xpwya5ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpTwtB%2FbtsPuOoFJ2H%2FnyKR52PoSSInU6Xpwya5ck%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;613&quot; height=&quot;258&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;h2 data-ke-size=&quot;size26&quot;&gt;2. 프로세스&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwoeGO/btsPtm1qb36/HvLRIDhFknyRBYs0vcAud0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwoeGO/btsPtm1qb36/HvLRIDhFknyRBYs0vcAud0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwoeGO/btsPtm1qb36/HvLRIDhFknyRBYs0vcAud0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwoeGO%2FbtsPtm1qb36%2FHvLRIDhFknyRBYs0vcAud0%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;290&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 스레드&lt;/h2&gt;
&lt;pre id=&quot;code_1753234683888&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public class Operator {
     public static void main(String[] args) {
         int sum1 = 1;
         int sum2 = sum1 + 1;
         System.out.println(&quot;sum1 = &quot; + sum1);
         System.out.println(&quot;sum2 = &quot; + sum2);
        }
 }&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;하나씩&lt;span&gt; &lt;/span&gt;&lt;/span&gt;실행하는 흐름, 즉 &lt;b&gt;프로세스의 코드를 실행하는 흐름&lt;/b&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;&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;프로그램 A : 워드 프로그램&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드1 : 문서 편집&lt;/li&gt;
&lt;li&gt;스레드2 : 자동 저장&lt;/li&gt;
&lt;li&gt;스레드3 : 맞춤법 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 B : 유튜브&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드1 : 영상 재생&lt;/li&gt;
&lt;li&gt;스레드2 : 댓글&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스와 연관지어 정리하자면 &lt;b&gt;프로세스는 실행 환경과 자원을 제공하고 스레드는 CPU를 사용해서 코드를 하나하나 실행한다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  멀티 스레드는 동시에 실행되기 때문에 각 스레드간 순서와 실행 기간 모두 보장하지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨텍스트 스위칭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 스위칭은 &lt;b&gt;CPU가 현재 실행 중인 프로세스의 상태를 저장하고 다른 프로세스의 상태를 복원하여 실행하는 것&lt;/b&gt;이다.&lt;span style=&quot;background-color: #1f1f1f; color: #bfbfbf; text-align: start;&quot;&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;1~10000까지 더해야 한다. 스레드를 두개 사용하여 스레드1은 1~5000까지 더하고 스레드2는 5001~10000까지 더해야한다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU코어가 1개고 스레드를 두개 사용한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드1은 1~1000 까지 연산한 상태에서 잠시 멈추고,스레드2를 5001~6001까지 연산하는 식으로 반복할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, CPU는 스레드1을 멈추고 다시 실행할 때 어디까지 연산했는지 알아야하기 때문에 이런 반복에서 시간 비용이 들게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 실제로 컨텍스트 스위칭에 걸리는 시간은 아주 짧지만, 스레드가 매우 많다면 비용이 커질 수 있다.&lt;/blockquote&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;b&gt;실무 팁&lt;/b&gt;&lt;br /&gt;1.CPU 4개, 스레드 2개&lt;br /&gt;스레드의 숫자가 너무 적으면 모든 CPU를 100%활용할 수 없다. 다만, 스레드가 몇 개 없으므로 컨텍스트 스위칭 비용이 줄어든다.&lt;br /&gt;&lt;br /&gt;2. CPU 4개, 스레드 100개&lt;br /&gt;스레드의 숫자가 너무 많으면 CPU를 100% 다 활용할 수 있지만 컨텍스트 스위칭 비용이 늘어난다.&lt;br /&gt;&lt;br /&gt;3. CPU 4개, 스레드 4개&lt;br /&gt;스레드와 CPU의 숫자가 동일하여 CPU를 100%활용할 수 있고, 컨텍스트 스위칭 비용도 자주 발생하지 않기에 최적의 상태가 된다.&lt;br /&gt;이상적으로 CPU 코어 수 + 1개 정도로 스레드를 맞추면 특정 스레드가 잠시 대기할 때 남은 스레드를 활용할 수 있다.&lt;br /&gt;&lt;br /&gt;그렇다면 CPU 코어 수 + 1개가 무조건 이상적인가? 그렇지않다.&lt;br /&gt;&lt;br /&gt;각각의 스레드가 하는 작업은 크게 &lt;b&gt;CPU 바운드 작업 vs I/O 바운드&lt;/b&gt; 작업 두개로 나뉜다.&lt;br /&gt;CPU 바운드 작업은 CPU의 연산 능력을 많이 요구하는 작업(계산,데이터 처리,알고리즘 실행 등)을 의미하고&lt;br /&gt;I/O 바운드 작업은 입출력 작업(디스크,네트워크,파일 시스템 등)을 많이 요구하는 작업을 의미한다.&lt;br /&gt;&lt;br /&gt;실제로 실무에서는 CPU-바운드 작업보다는 데이터베이스 쿼리 처리,네트워크 통신등 I/O 바운드 작업이 더 많다.&lt;br /&gt;예를 들어, 스레드는 CPU를 1% 정도 사용하고, 대부분 DB 서버에 어떤 결과를 조회하면서 기다린다고 가정해보자.&lt;br /&gt;CPU 코어가 4개있다고해서 스레드 숫자도 CPU코어에 맞추게 되면 CPU를 4%정도만 사용할 것이며, 결국 사용자는&lt;br /&gt;동시에 4명밖에 못받지만 CPU는 4%만 사용하여 CPU가 놀고 있는 사태가 벌어질 수 있다.&lt;br /&gt;결국 이를 인지하지 못하고 스레드 숫자를 늘리면 되는데 스케일업을 하는 사태가 발생하게 된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;그래서 어떻게 설정하라는 것인가?&lt;/b&gt;&lt;br /&gt;CPU-바운드 작업이 많다면 CPU 코어 수 + 1개로 설정하여 스레드를 CPU 숫자에 최적화하고,&lt;br /&gt;I/O바운드 작업이 많다면 성능 테스트를 통해 CPU를 최대한 활용하는 숫자까지 스레드를 생성하되, 너무 많은 스레드를 생성하면 컨텍스트 스위칭 비용도 함께 증가하니 적절한 성능 테스트가 필요하다.&amp;nbsp;&lt;/blockquote&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;* 출처 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753236155619&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;김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성| 김영한 - 인프런 강의&quot; data-og-description=&quot;현재 평점 5점 수강생 7463명인 강의를 만나보세요. 멀티스레드와 동시성을 기초부터 실무 레벨까지 깊이있게 학습합니다. 멀티스레드, 동시성, 스레드 풀과 Executor 프레임워크, 프로세스와 스레&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k3WOc/hyZqYtzI76/KNRuvryXgY3kyaKpkdAvsk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bzfS1t/hyZqZ0j0eB/fTZcWAiSB7kIHIE013Gzs1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/pkk4A/hyZqVDCrTA/6kFUk4sqK4tMY2ZoGlTCY0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k3WOc/hyZqYtzI76/KNRuvryXgY3kyaKpkdAvsk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bzfS1t/hyZqZ0j0eB/fTZcWAiSB7kIHIE013Gzs1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/pkk4A/hyZqVDCrTA/6kFUk4sqK4tMY2ZoGlTCY0/img.jpg?width=1439&amp;amp;height=1441&amp;amp;face=171_677_281_798');&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;김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성| 김영한 - 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 평점 5점 수강생 7463명인 강의를 만나보세요. 멀티스레드와 동시성을 기초부터 실무 레벨까지 깊이있게 학습합니다. 멀티스레드, 동시성, 스레드 풀과 Executor 프레임워크, 프로세스와 스레&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/185</guid>
      <comments>https://lsh2016.tistory.com/185#entry185comment</comments>
      <pubDate>Wed, 23 Jul 2025 11:00:37 +0900</pubDate>
    </item>
    <item>
      <title>Java) 인스턴스화를 막으려거든 private 생성자를 사용하라</title>
      <link>https://lsh2016.tistory.com/182</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhM2uU/btsKyWzzk5S/5B4CB9BF8oSiKFytcpKB8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhM2uU/btsKyWzzk5S/5B4CB9BF8oSiKFytcpKB8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhM2uU/btsKyWzzk5S/5B4CB9BF8oSiKFytcpKB8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhM2uU%2FbtsKyWzzk5S%2F5B4CB9BF8oSiKFytcpKB8K%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;257&quot; height=&quot;420&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&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;예를 들어 java.lang.Math와 java.util.Arrays 처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 수 있다.&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;또한, java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(또는 팩토리)를 모아놓을 수도 있다.&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;static메서드만 가지고 있는 어떤 클래스가 있다고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1730953607104&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UtilityClass {
    public UtilityClass(){}

    public static Character getFirstIndexChar(String s){
        return s.charAt(0);
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730953678047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        UtilityClass utilityClass = new UtilityClass();

        //UtilityClass.getFirstIndexChar(&quot;Hello&quot;); 
        utilityClass.getFirstIndexChar(&quot;Hello&quot;); // 인스턴스 생성 후 호출
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 static 메서드를 인스턴스 생성 후 호출하는 것은 권장하는 방식은 아니지만, 문법적으로는 문제가 없다.&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;h2 data-ke-size=&quot;size26&quot;&gt;인스턴스화를 막는법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법1. abstract 키워드를 사용한다.&lt;/h3&gt;
&lt;pre id=&quot;code_1730954502191&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class UtilityClass {
    public UtilityClass(){}

    public static Character getFirstIndexChar(String s){
        return s.charAt(0);
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에선 abstract 키워드를 붙히면 인스턴스 생성이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 추상 클래스로 만들어도 인스턴스가 만들어질 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1730954682223&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class IntegerUtilityClass extends UtilityClass{
	// 기본 생성자
    public IntegerUtilityClass() {
        super();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스를 상속받으면 기본 생성자가 만들어지고 생성자 내부에서 자동으로 super()를 호출하기 때문에, 부모 인스턴스가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이는 완벽하게 인스턴스화를 막을수 있는 방법이 아니며 정작 사용자는 이 클래스를 상속해서 쓰라는 뜻으로 오해할 수 있어서 문제가 발생할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법2. 생성자를 private으로 제한&lt;/h3&gt;
&lt;pre id=&quot;code_1730954923271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UtilityClass {

	// Don't let anyone instantiate this class. (실제 java.lang에 명시된 주석)
    private UtilityClass(){
    	throw new AssertionError(); 
    }

    public static Character getFirstIndexChar(String s){
        return s.charAt(0);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자가 private이라면 그 어떤 외부에서도 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 안에서 실수로라도 생성자를 호출했을때, Error를 호출하게 한다면 완벽하게 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &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;또한 이 방식은 상속을 불가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데, 이를 private으로 선언하게 되면 하위 클래스가 상위 클래스의 생성자에 접근할 길이 없다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 참고로 Spring 대부분의 유틸리티 클래스는 방법2가 적용되어 있지 않고, 방법1의 abstract로 인스턴스화를 막고 있다.&lt;br /&gt;아래는 Spring에서 지원하는 StringUtils 클래스이다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh4dcl/btsKyTQrbBh/SOGRM3Tuq8pFZIanxSWUTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh4dcl/btsKyTQrbBh/SOGRM3Tuq8pFZIanxSWUTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh4dcl/btsKyTQrbBh/SOGRM3Tuq8pFZIanxSWUTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh4dcl%2FbtsKyTQrbBh%2FSOGRM3Tuq8pFZIanxSWUTk%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;532&quot; height=&quot;581&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
실제로 abstract를 사용하고 있는 것을 확인할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/182</guid>
      <comments>https://lsh2016.tistory.com/182#entry182comment</comments>
      <pubDate>Thu, 7 Nov 2024 13:52:40 +0900</pubDate>
    </item>
    <item>
      <title>Java) 생성자나 열거 타입으로 싱글턴임을 보증하라</title>
      <link>https://lsh2016.tistory.com/181</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wd4BU/btsKwuh6f2C/A1cvPhWlKSPCb1P1umVqwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wd4BU/btsKwuh6f2C/A1cvPhWlKSPCb1P1umVqwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wd4BU/btsKwuh6f2C/A1cvPhWlKSPCb1P1umVqwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWd4BU%2FbtsKwuh6f2C%2FA1cvPhWlKSPCb1P1umVqwk%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;257&quot; height=&quot;420&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&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;왜냐하면 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이다.&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;1. private 생성자 + public static 멤버 변수&lt;/h2&gt;
&lt;pre id=&quot;code_1730781096845&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis{
    public static final Elvis INSTANCE = new Elvis();

    private Elvis(){}

    public void leaveTheBuilding(){}
}

// client
public static void main(String[] args) {
        Elvis elvis1 = Elvis.INSTANCE;
        Elvis elvis2 = Elvis.INSTANCE;

        System.out.println(elvis1.equals(elvis2)); //true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 private 생성자로 외부 생성을 막고, static final변수에 인스턴스를 할당한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;2. private 생성자 + 정적 팩토리 메서드로 멤버 변수 접근&lt;/h2&gt;
&lt;pre id=&quot;code_1730782704132&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis{
    private static final Elvis INSTANCE = new Elvis();

    private Elvis(){}

    public static Elvis getInstance(){
        return INSTANCE;
    }
    public void leaveTheBuilding(){}
}

//client
public static void main(String[] args) {
        Elvis elvis1 = Elvis.getInstance();
        Elvis elvis2 = Elvis.getInstance();

        System.out.println(elvis1.equals(elvis2)); // true
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 접근 변수를 private으로 선언하고, 정적 팩토리 메서드로 가져오게 하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식은 1번보다 장점이 몇 가지 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점1. API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 클라이언트 코드에서 아래와 같이 사용중이라고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1730782870052&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        Elvis elvis1 = Elvis.getInstance();
        elvis1.leaveTheBuilding();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상태에서 싱글턴이 아니게 변경하려면 클라이언트 코드가 아닌 정적 팩토리 메서드만 변경시켜주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1730782956676&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis{
    private static final Elvis INSTANCE = new Elvis();

    private Elvis(){}

    public static Elvis getInstance(){
	  //return INSTANCE;  // 변경 전
        return new Elvis(); // 변경 후
    }
    public void leaveTheBuilding(){}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 후에 팩토리 메서드에서 직접 생성하도록 수정하도록 하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점2. 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다.&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 제네릭 싱글톤 팩토리&lt;br /&gt;제네릭+싱글톤 팩토리로 제네릭을 사용하여 하나의 싱글톤 인스턴스를 다양한 타입으로 제공하는 정적 팩토리 메서드를 의미한다.&lt;br /&gt;&lt;br /&gt;사용 이유&lt;br /&gt;보통 싱글톤 클래스는 하나의 인스턴스만 제공하기 때문에 특정 타입에 고정되기 쉽다. 따라서 싱글턴 팩토리를 사용하면 제네릭 타입을 활용하여 다양한 타입에 대해 단일 인스턴스를 제공할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 타 강의에서 가져온 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730786760596&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MetaElvis&amp;lt;T&amp;gt; {

    private static final MetaElvis&amp;lt;Object&amp;gt; INSTANCE = new MetaElvis&amp;lt;&amp;gt;();

    private MetaElvis() { }

    @SuppressWarnings(&quot;unchecked&quot;) // 제네릭 싱글턴 팩토리
    public static &amp;lt;E&amp;gt; MetaElvis&amp;lt;E&amp;gt; getInstance() {
        return (MetaElvis&amp;lt;E&amp;gt;) INSTANCE;
    }

    public void say(T t) {
        System.out.println(t);
    }

    public void leaveTheBuilding() {
        System.out.println(&quot;Whoa baby, I'm outta here!&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;제네릭 문법이 생소한데 팩토리 메서드 구조만 조금만 짚고 가보자&lt;/p&gt;
&lt;pre id=&quot;code_1730786869572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SuppressWarnings(&quot;unchecked&quot;) // 제네릭 싱글턴 팩토리
    public static &amp;lt;E&amp;gt; MetaElvis&amp;lt;E&amp;gt; getInstance() {
        return (MetaElvis&amp;lt;E&amp;gt;) INSTANCE;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;E&amp;gt;는 해당 메서드에서만 사용되는 로컬 타입 파라미터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말그대로 파라미터로, &amp;lt;A&amp;gt;던지 &amp;lt;B&amp;gt; 뭐든 가능하다. 다만 &amp;lt;T&amp;gt; 또는 &amp;lt;E&amp;gt;로 많이 나타내는 것은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들 간의 관습적인 표현으로 T는 Type을 의미하고 E는 Element를 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730786805492&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class main {

    public static void main(String[] args) {
        MetaElvis&amp;lt;String&amp;gt; elvis1 = MetaElvis.getInstance();
        MetaElvis&amp;lt;Integer&amp;gt; elvis2 = MetaElvis.getInstance();

        System.out.println(elvis1.equals(elvis2)); // true (제네릭 타입이 다르기 때문에 equals로 비교)

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스는 동일하지만 각각의 타입은 다른것을 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점3. 정적 팩토리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 달리말해 정적 팩토리 메서드를 Supplier과 같은 함수형 인터페이스에 전달할 수 있다라는 의미이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730787861380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FunctionalInterface
public interface Supplier&amp;lt;T&amp;gt; {
    T get();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Supplier&amp;lt;T&amp;gt;는 자바에서 타입 T의 객체를 반환하는 함수형 인터페이스이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730788407490&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Person {
    void run();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run() 메서드를 가지는 Person 인터페이스를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1730788469387&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis implements Person{
    private static final Elvis INSTANCE = new Elvis();

    private Elvis(){}

    public static Elvis getInstance(){
        return new Elvis();
    }
    public void leaveTheBuilding(){}

    @Override
    public void run() {
        System.out.println(&quot;run start&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elvis는 Person인터페이스를 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730788494331&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Gym {
    void exercise(Supplier&amp;lt;Elvis&amp;gt; elvisSupplier){
        Elvis elvis = elvisSupplier.get();
        elvis.run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gym 클래스는 exercise() 메서드를 가진다. 이때 인수로 함수형 인터페이스 Supplier를 받도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730788578562&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        Gym gym = new Gym();

        gym.exercise(Elvis::getInstance);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 Elvis::getInstance와 같이 메서드 참조로 사용할 수 있다.&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;* 1,2번 방식의 단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점1. 싱글톤을 사용하는 클라이언트 코드를 테스트하기 어려워진다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 데이터베이스 연결을 관리하는 DatabaseConnectionManager 싱글톤 클래스가 있다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1730788904723&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DatabaseConnectionManager {
    private static final DatabaseConnectionManager INSTANCE = new DatabaseConnectionManager();

    private DatabaseConnectionManager() {
        // 실제 DB 연결 설정
    }

    public static DatabaseConnectionManager getInstance() {
        return INSTANCE;
    }

    public void connect() {
        System.out.println(&quot;Connecting to the real database...&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 클래스를 통해 테스트 해보고 싶은 어떤 비즈니스 로직이 있다고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1730788997867&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SomeService {
    public void performDatabaseOperation() {
        DatabaseConnectionManager connection = DatabaseConnectionManager.getInstance(); // 변경 불가
        connection.connect();
        ... // 여러 비즈니스 로직 수행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 구조에선 항상 DatabaseConnectionManager의 인스턴스를 사용하기 때문에, 테스트 환경에서 DatebaseConnectionManager를 모의 객체로 바꿀 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 테스트 코드는 아래와 같이 작성될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730789557275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SomeServiceTest {
    public static void main(String[] args) {
        SomeService service = new SomeService();

        // 테스트 환경에서 performDatabaseOperation 호출 시 실제 DB 연결이 시도됨
        service.performDatabaseOperation(); // &quot;Connecting to the real database...&quot; 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이를 해결하기 위해 인터페이스를 생성하자.&lt;/p&gt;
&lt;pre id=&quot;code_1730789599890&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface DatabaseConnection {
    void connect();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730789612026&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DatabaseConnectionManager implements DatabaseConnection {
    private static final DatabaseConnectionManager INSTANCE = new DatabaseConnectionManager();

    private DatabaseConnectionManager() {}

    public static DatabaseConnectionManager getInstance() {
        return INSTANCE;
    }

    @Override
    public void connect() {
        System.out.println(&quot;Connecting to the real database...&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DatabaseConnectionManager는 인터페이스를 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730790017090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SomeService {
    public void performDatabaseOperation(DatabaseConnection databaseConnection) {
        connection.connect();
        ... // 여러 비즈니스 로직 수행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트할 서비스 메서드는 파라미터로 인터페이스를 받게한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730789985977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SomeServiceTest {
    public static void main(String[] args) {
        // 모의 객체 생성
        DatabaseConnection mockConnection = new MockDatabaseConnection(); 
        SomeService service = new SomeService();

        // performDatabaseOperation 호출 시 모의 객체 전달
        service.performDatabaseOperation(mockConnection); // &quot;Pretending to connect to a database for testing...&quot; 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 메서드에 인터페이스를 넘김으로써 실제 DB에 연결하지 않고도 로직을 검증할 수 있게된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점2.리플렉션으로 private 생성자를 호출할 수 있다.&lt;/h3&gt;
&lt;pre id=&quot;code_1730790181306&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public static void main(String[] args) {
      try {
         Constructor&amp;lt;Elvis&amp;gt; defaultConstructor = Elvis.class.getDeclaredConstructor();
         defaultConstructor.setAccessible(true); // private 생성자 호출 가능하게 설정

         Elvis elvis1 = defaultConstructor.newInstance();
         Elvis elvis2 = defaultConstructor.newInstance();

         System.out.println(elvis1 == elvis2); // false
      } catch (NoSuchMethodException e) {
         ...
      } ...
   }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션을 사용하면 private 생성자에 접근할 수 있기에 싱글톤으로 설계해도 위 처럼 여러개의 인스턴스가 만들어질 수 있다.&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;pre id=&quot;code_1730790412299&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis implements Person{
    private static final Elvis INSTANCE = new Elvis();

    private static boolean isCreated = false;

    private Elvis(){
        if(isCreated){
            throw new UnsupportedOperationException(&quot;...&quot;);
        }

        isCreated = true;
    }

    public static Elvis getInstance(){
        return INSTANCE;
    }
    public void leaveTheBuilding(){}

    @Override
    public void run() {
        System.out.println(&quot;run start&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isCreated라는 플래그를 두어 생성한 이력이 있다면 에러를 내게하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼하게 되면 리플렉션을 통해 싱글톤을 깨뜨릴 수 없게된다. 다만 코드가 간결해지는 장점은 사라진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점3. 역직렬화 할 때 새로운 인스턴스가 생길 수 있다.&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 직렬화&lt;br /&gt;직렬화는 객체를 바이트 스트림으로 상호 변환하는 기술이다.&lt;br /&gt;변환한 바이트 스트림은 어떤 파일로 저장하거나 네트워크를 통해 다른 시스템으로 전송할 수 있다.&lt;br /&gt;간단하게 이사를 가는 것으로 비유할 수 있다.&lt;br /&gt;짐을 포장하는 과정을 직렬화 , 이삿짐을 푸는 과정을 역 직렬화 라고 생각하면 된다.&lt;br /&gt;&lt;br /&gt;보통 10여년 전에는 자바 객체를 직렬화하거나 역직렬화 하는 일이 많았지만 오늘날에는 JSON을 많이 사용한다.&lt;br /&gt;받아서 처리할 곳이 JVM 이라면 직렬화가 유용하지만 다른 시스템이라면 굳이 사용하지 않는다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1730790837786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
      try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream(&quot;elvis.obj&quot;))) {
         out.writeObject(Elvis.getInstance());
      } catch (IOException e) {
         e.printStackTrace();
      }

      try (ObjectInput in = new ObjectInputStream(new FileInputStream(&quot;elvis.obj&quot;))) {
         Elvis elvis3 = (Elvis) in.readObject();
         System.out.println(elvis3 == Elvis.getInstance()); // false , 역직렬화시 새로운 인스턴스가 생긴다.
      } catch (IOException | ClassNotFoundException e) {
         e.printStackTrace();
      }
   }&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;그런데 역직렬화시 새로운 인스턴스가 생기게 된다.&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;먼저 , 직렬화를 하려면 Serializable 인터페이스를 구현해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730791080530&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Elvis implements Person, Serializable {
    private static final Elvis INSTANCE = new Elvis();

    private static boolean isCreated = false;

    private Elvis(){
        if(isCreated){
            throw new UnsupportedOperationException(&quot;...&quot;);
        }

        isCreated = true;
    }

    public static Elvis getInstance(){
        return INSTANCE;
    }
    public void leaveTheBuilding(){}

    @Override
    public void run() {
        System.out.println(&quot;run start&quot;);
    }

    private Object readResolve() {
        return INSTANCE;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역직렬화시 호출되는 메서드인 readResolve()내부에서 기존에 사용하던 인스턴스를 반환하게 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법을 따르면 역직렬화시 싱글턴이 깨지는것을 방지할 수 있지만 단점2번과 마찬가지로 간결한 코드의 장점이 사라지게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 열거 타입으로 싱글톤 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 세번째 방법은 열거 타입으로 싱글톤을 생성하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거 타입으로 싱글톤을 생성하면 리플렉션 및 직렬화 &amp;amp; 역직렬화에 대해 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enum 타입은 리플렉션을 내부코드로 막아 놓았기 때문에 생성자를 불러오려고 하면 에러가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730791624073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Elvis implements Person{
    INSTANCE;

    public void leaveTheBuilding(){

    }

    @Override
    public void run() {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 인터페이스 또한 구현할 수 있기때문에 테스트 코드 작성시의 문제점까지 해결할 수 있다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/181</guid>
      <comments>https://lsh2016.tistory.com/181#entry181comment</comments>
      <pubDate>Tue, 5 Nov 2024 16:28:42 +0900</pubDate>
    </item>
    <item>
      <title>Java) 생성자에 매개변수가 많다면 빌더를 고려하라</title>
      <link>https://lsh2016.tistory.com/180</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xIObJ/btsKvX41pdt/xN1u7RSGLEfCt5DLdCLdt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xIObJ/btsKvX41pdt/xN1u7RSGLEfCt5DLdCLdt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xIObJ/btsKvX41pdt/xN1u7RSGLEfCt5DLdCLdt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxIObJ%2FbtsKvX41pdt%2FxN1u7RSGLEfCt5DLdCLdt1%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;257&quot; height=&quot;420&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&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;a href=&quot;https://lsh2016.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lsh2016.tistory.com/101&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730695735008&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;빌더 패턴(Builder Pattern) 을 사용하는 이유&quot; data-og-description=&quot;먼저 , 학생이라는 객체를 생성하는 코드가 있고 , 아래와 같은 생성자 정의해두었다고 가정한다. public Student(long id,String name,String major,int age,String address){ this.id=id; this.name=name; this.major=major; this.ag&quot; data-og-host=&quot;lsh2016.tistory.com&quot; data-og-source-url=&quot;https://lsh2016.tistory.com/101&quot; data-og-url=&quot;https://lsh2016.tistory.com/101&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ubMvr/hyXsRKNMyH/NGFnoEZd3dXQfPrCfu6lK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bREilt/hyXsYXr93j/xRrKJK0TybieXmvo5VhC1k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://lsh2016.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lsh2016.tistory.com/101&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ubMvr/hyXsRKNMyH/NGFnoEZd3dXQfPrCfu6lK0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bREilt/hyXsYXr93j/xRrKJK0TybieXmvo5VhC1k/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;빌더 패턴(Builder Pattern) 을 사용하는 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;먼저 , 학생이라는 객체를 생성하는 코드가 있고 , 아래와 같은 생성자 정의해두었다고 가정한다. public Student(long id,String name,String major,int age,String address){ this.id=id; this.name=name; this.major=major; this.ag&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lsh2016.tistory.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;간략히 설명하자면 점층적 생성자 패턴의 심각한 가독성과 자바빈즈 패턴의 무너진 객체 일관성을 지적하며 빌더 패턴을 사용해야 하는 이유에 대해 포스팅한 글이다.&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;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;잘 이해가 안가서 비슷한 예제를 만들어보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1730695971082&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Adventurer {

    public enum Ability { STR, DEX, INT, LUK }
    final Set&amp;lt;Ability&amp;gt; abilities;

    abstract static class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{ // 재귀적 타입 한정
        EnumSet&amp;lt;Ability&amp;gt; abilities = EnumSet.noneOf(Ability.class);
        public T increaseAbility(Ability ability){
            abilities.add(Objects.requireNonNull(ability));
            return self();
        }

        abstract Adventurer build();

        protected abstract T self();
    }

    Adventurer(Builder&amp;lt;?&amp;gt; builder){
        abilities = builder.abilities.clone(); // clone &amp;gt; 원래의 EnumSet과 동일한 열거형 상수를 복사, 원본과는 서로 독립적인 인스턴스임.
    }
    
class Warrior extends Adventurer{
    public enum Job { FIGHTER, SPEARMAN, PAGE }
    private final Job job; // 필수 매개변수
    private String name; // 선택 매개변수

    public static class Builder extends Adventurer.Builder&amp;lt;Builder&amp;gt;{
        private final Job job;
        private String name;

        public Builder(Job job){
            this.job = Objects.requireNonNull(job);
        }

        public Builder name(String value){
            this.name = value;
            return this;
        }

        public Warrior build(){
            return new Warrior(this);
        }

        protected Builder self() {
            return this;
        }
    }

    Warrior(Builder builder) {
        super(builder);
        job = builder.job;
        name = builder.name;
    }
}

class Mage extends Adventurer{
    public enum Element { FLAME, ICE, LIGHT }
    private final Element element;

    public static class Builder extends Adventurer.Builder&amp;lt;Builder&amp;gt;{
        private final Element element;

        public Builder(Element element){
            this.element = Objects.requireNonNull(element);
        }

        @Override
        Mage build() {
            return new Mage(this);
        }

        @Override
        protected Builder self() {
            return this;
        }

    }
    Mage(Builder builder){
        super(builder);
        element = builder.element;
    }
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 모험가 추상 클래스 Adventurer는 4가지의 능력치 enum값을 가질수 있고, 내부 정적 클래스 Builder가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전사 Warrior 클래스와 마법사 Mage 클래스는 Adventurer 클래스를 상속받고 있고, 하위 클래스는 모두 Adventurer.Builder를 각각 상속받아 Builder 내부 클래스를 가지고 있다.&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 style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;1.제네릭(Generic)&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;2.시뮬레이트된 셀프 타입 (simulated self-type)&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;3.재귀적 타입 한정(Recursive Type Bound)&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차근차근 하나씩 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 제네릭(Generic)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭을 사용하면 타입 안정성이 유지되고, 타입 캐스팅이 불필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1730702542175&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
  abstract static class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{
        EnumSet&amp;lt;Ability&amp;gt; abilities = EnumSet.noneOf(Ability.class);
        public T increaseAbility(Ability ability){
            abilities.add(Objects.requireNonNull(ability));
            return self();
        }

        abstract Adventurer build();

        protected abstract T self();
    }
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 T는 아직 어떤 값이 들어올지 모르지만, 상속받은 클래스를 생성하는 순간 구체화된다.&lt;/p&gt;
&lt;pre id=&quot;code_1730702716079&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        Warrior warrior =  new Warrior.Builder(Warrior.Job.FIGHTER) // 1번
                .increaseAbility(STR)
                .increaseAbility(DEX)
                .name(&quot;최강전사&quot;)
                .build();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new Warrior.Builder()호출 시 내부 생성자는 암묵적으로 super()를 호출하여 부모 생성자를 호출할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 생성자 호출시점에 T는 자동으로 Warrior.Builder타입으로 변경될 것이기 때문에 부모 클래스에 있는 메서드를 자식 타입으로 확정지을 수 있다.&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.시뮬레이트된 셀프 타입(simulated self-type)&lt;/h3&gt;
&lt;pre id=&quot;code_1730704418150&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
  abstract static class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{
        EnumSet&amp;lt;Ability&amp;gt; abilities = EnumSet.noneOf(Ability.class);
        public T increaseAbility(Ability ability){
            abilities.add(Objects.requireNonNull(ability));
            return self(); // 자식 타입을 계속 리턴하므로 메서드 체이닝 유지 가능
        }

        abstract Adventurer build();

        protected abstract T self(); // 자식 타입을 리턴
    }
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self()는 나중에 자식 클래스가 위 Builder 클래스를 구현했을 때, 자식 객체 본인을 리턴하게 하도록 하는 메서드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self()의 반환 타입은 T로, 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;span style=&quot;text-align: start;&quot;&gt;Self 타입이 없는 자바를 위한 우회 방법을 &quot;시뮬레이트한 셀프타입&quot;(simulated self-type) 관용구라고 명시하기 때문에 따로 빼두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 제네릭을 쓰지 않는다면 아래와 같은 문제가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730704690190&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract static class Builder{
        EnumSet&amp;lt;Ability&amp;gt; abilities = EnumSet.noneOf(Ability.class);
        public Builder increaseAbility(Ability ability){
            abilities.add(Objects.requireNonNull(ability));
            return self();
        }

        abstract Adventurer build();

        protected abstract Builder self();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭을 없애고 다시 Warrior 객체를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730704718815&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
        Warrior warrior =  new Warrior.Builder(Warrior.Job.FIGHTER)
                .increaseAbility(STR) // 반환타입 &amp;gt; Adventurer.Builder
                .increaseAbility(DEX) // 반환타입 &amp;gt; Adventurer.Builder
                .name(&quot;최강 전사&quot;) // CompileError
                .build();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 .increaseAbility()시점에 반환 타입은 Warrior.Builder타입이 아닌 Adventurer.Builder()타입이 될 것이기에 메서드 체이닝을 이어갈 수 없고, 타입변경이 필요하다는 에러와 함께 컴파일 에러가 발생한다.&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.재귀적 타입 한정(Recursive Type Bound)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀적 타입 한정은 클래스가 자기 자신을 매개변수화할 때 사용되며, 클래스가 자기 자신 또는 자신을 상속한 클래스를 제한하는데 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1730705083750&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  abstract static class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{
 		...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T는 Builder의 하위 클래스이며, Builder&amp;lt;T&amp;gt;타입의 인스턴스여야 한다. 그런데 여기서 또다시 T는 builder 클래스의 하위 클래스의 타입을 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 구조는 재귀적 구조로 Builder 클래스는 T를 통해 자신을 재귀적으로 참조하는 형태가 된다.&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;pre id=&quot;code_1730706053525&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Builder&amp;lt;T extends Builder&amp;gt;{ // Raw use of parameterized class 'Builder' 경고,참고로 재귀적 타입 한정 X
	...
}
class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{ // 타입 안정성을 가짐
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌다보니 본문은 자바를 해석하며 주르륵 써내려간 코드 해석문이지만 책에서 보여지는 큰틀에서 정리하자면 &lt;u&gt;&lt;b&gt;생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 적극 활용하자&lt;/b&gt;&lt;/u&gt;. 이다&lt;/p&gt;</description>
      <category>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/180</guid>
      <comments>https://lsh2016.tistory.com/180#entry180comment</comments>
      <pubDate>Mon, 4 Nov 2024 17:03:15 +0900</pubDate>
    </item>
    <item>
      <title>Java) 생성자 대신 정적 팩토리 메서드</title>
      <link>https://lsh2016.tistory.com/179</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfDvz1/btsKoOgCrDE/zRBjl0KDJKXn5sFls5jYn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfDvz1/btsKoOgCrDE/zRBjl0KDJKXn5sFls5jYn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfDvz1/btsKoOgCrDE/zRBjl0KDJKXn5sFls5jYn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfDvz1%2FbtsKoOgCrDE%2FzRBjl0KDJKXn5sFls5jYn1%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;257&quot; height=&quot;420&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 팩토리 메서드는 생성자 대신 다양한 장점을 제공할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 여기서 말하는 정적 팩토리 메서드는 디자인 패턴에 나오는 팩토리 메서드와 다른것이다.&lt;br /&gt;디자인 패턴에서 정적 팩토리 메서드와 관련된 패턴은 없다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점 1. 이름을 가질 수 있다.&lt;/h3&gt;
&lt;pre id=&quot;code_1730247579108&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Person{

	private String name;
    
	private Person(String name){ // 생성자를 private으로 제한
		this.name = name;
	}

	public static Person from(String name){ // from이라는 이름을 가짐
		return new Person(name);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 생성자는 private으로 두면 클라이언트가 정적 팩토리 메서드를 사용하도록 유도할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정적 팩토리 메서드 예제는 인자를 하나만 받고있기 때문에 네이밍을 from으로 설정했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점 2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.&lt;/h3&gt;
&lt;pre id=&quot;code_1730248719762&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CachingInstance{

    private static final Map&amp;lt;Integer, CachingInstance&amp;gt; CACHE_MAP = new HashMap&amp;lt;&amp;gt;();
    
    private final int key;
    
    private CachingInstance(int key){ // 생성자 private으로 제한
    	this.key = key;
    }
    
    public static cachingInstance instance(int key){
    	if(!CACHE_MAP.containskey(key){
        	CACHE_MAP.put(key,new CachingInstance(key));
        }
        return CACHE_MAP.get(key);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시기능을 구현하기 위해 Map을 만들고, 정적 팩토리 메서드 내에서 들어온 key값에 따라 key값이 동일하다면 기존에 만들어진 인스턴스를 반환하고, 없다면 새로 만들어서 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730248982602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args){
	CachingInstance instance1 = CachingInstance.instance(1);
        CachingInstance instance2 = CachingInstance.instance(2);
        CachingInstance instance3 = CachingInstance.instance(1);
        CachingInstance instance4 = CachingInstance.instance(2);

        System.out.println(instance1.equals(instance3));    // true
        System.out.println(instance2.equals(instance4));    // true
        System.out.println(instance1.equals(instance2));    // false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 1,3과 2,4는 서로 같은 인스턴스인것을 확인할 수 있다.&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. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1730261679110&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Car {
  void drive();

  static Sedan createSedan(){
      return new Sedan();
  }

  static SUV createSUV(){
      return new SUV();
  }
}

class Sedan implements Car {
    @Override
    public void drive() {
        System.out.println(&quot;Driving a Sedan!&quot;);
    }

    public Sedan(){
    }
}

class SUV implements Car {
    @Override
    public void drive() {
        System.out.println(&quot;Driving an SUV!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sedan과 SUV클래스는 Car 인터페이스를 확장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에선 생성자를 만들수없기에, 정적 팩토리 메서드를 이용해서 각 하위객체로 리턴하게 하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730262997253&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {

        Car sedan = Car.createSedan();
        Car suv = Car.createSUV();

        sedan.drive(); // Driving a Sedan!
        suv.drive(); // Driving an SUV!
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 개발자는 Sedan과 SUV의 구현체를 찾아볼 필요가 없게되고, API는 경량화 된다.&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;장점4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 3번과 유사한데, 3번의 Car에 새로운 정적 팩토리 메서드를 하나 생성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1730263844771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Car {
  void drive();

  static Sedan createSedan(){
      return new Sedan();
  }

  static SUV createSUV(){
      return new SUV();
  }

  static Car fromCost(Long cost){ // 새로운 정적 팩토리 메서드 추가
      if(cost&amp;lt;9000L){
          return new Sedan();
      }
      else if(cost&amp;gt;9000L &amp;amp;&amp;amp; cost&amp;lt;12000L){
          return new SUV();
      }
      else{
          return new Van();
      }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fromCost()는 들어온 매개변수 cost에 따라 다른 객체를 반환하는 것을 확인할 수 있다.&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;장점5. 정적 팩토리 메서드를 작성하는 시점에서 반환할 객체의 클래스가 존재하지 않아도 된다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 &quot;반환할 객체의 클래스가 존재하지 않아도 된다.&quot;라는 말이 무슨말인지 잘 이해가 안가서 GPT한테 물어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT는 &quot;정적 팩토리 메서드를 정의할 때 특정 클래스의 구현이 반드시 필요하지 않다&quot;라고 답변을 줬는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;존재하지 않아도 된다&quot;와 &quot;존재하지만 구현이 필요하지 않다&quot;는 다른 의미인 것 같아서 여러 블로그를 뒤져보았으나 딱히 이해가는 글은 찾지 못 했다. 이는 나중에 따로 찾아서 이해가 필요한 부분인 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단점1. 정적 팩토리 메서드를 구현한 클래스는 상속이 불가능 &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터는 &quot;&lt;b&gt;생성자 대신&lt;/b&gt; 정적 팩토리 메서드&quot;를 사용하는 것에 초점이 맞춰져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정적 팩토리 메서드를 사용하도록 유도하기 위해 부모의 기본 생성자를 private으로 설정하게 되면&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;을 이용하도록 유도될 수 있고, '&lt;b&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;/p&gt;
&lt;pre id=&quot;code_1730265323873&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Book {
    private final String title;

    private Book(String title) {
        this.title = title;
    }

    public static Book create(String title) {
        return new Book(title);
    }

    public String description() {
        return &quot;Book Title: &quot; + title;
    }
}

class Library {
    private final Book book;

    public Library() {
        this.book = Book.create(&quot;The Great Gatsby&quot;);
    }

    public String description() {
        return book.description();
    }
}

public class Main {
    public static void main(String[] args) {
        Library library = new Library();
        System.out.println(library.description()); // 출력: Book Title: The Great Gatsby
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;정적 팩토리 메서드의 네이밍 규약&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 98.7211%; height: 158px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 22px;&quot;&gt;메서드명&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 22px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;from&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;of&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;valueOf&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;from과 of의 더 자세한 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;instance / getInstance&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;create / newInstance&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;instance 또는 getInstance와 같으나, 매번 새로운 인스턴스를 생성해 반환함을 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;getType&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용. &amp;ldquo;Type&amp;rdquo;은 팩토리 메서드가 반환할 객체의 타입을 의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;newType&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용. &amp;ldquo;Type&amp;rdquo;은 팩토리 메서드가 반환할 객체의 타입을 의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.4884%; height: 17px;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 75.2455%; height: 17px;&quot;&gt;getType과 newType의 간결한 버전&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;</description>
      <category>Java</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/179</guid>
      <comments>https://lsh2016.tistory.com/179#entry179comment</comments>
      <pubDate>Wed, 30 Oct 2024 14:19:53 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 정리 1) 외부 설정과 프로필</title>
      <link>https://lsh2016.tistory.com/178</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 김영한님의 스프링 부트편 강의중 제목과 관련된 부분을 블로그장의 취향대로 요약한 것이며 강의 자료 및 출처는 가장 아래에서 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 외부설정을 왜하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트에서 외부설정을 지원하게된 배경을 아래에서 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. 개발과 운영환경 각각 빌드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCx59M/btsI0zlTh0u/AQGX3mS5YLMcXPsgkcnVr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCx59M/btsI0zlTh0u/AQGX3mS5YLMcXPsgkcnVr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCx59M/btsI0zlTh0u/AQGX3mS5YLMcXPsgkcnVr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCx59M%2FbtsI0zlTh0u%2FAQGX3mS5YLMcXPsgkcnVr0%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;714&quot; height=&quot;313&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;404&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;만약 추후에 운영 환경에 문제가 생겼을때 개발 환경과 같은 소스코드에서 나온 결과물인지 검증하기가 어렵다.&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;b&gt;* 유지보수하기 좋은 애플리케이션 개발의 기본 원칙은 변하는 것과 변하지 않는 것을 분리하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. 개발과 운영환경 모두 같은 빌드 결과물을 사용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w6Wnl/btsI02U8YhB/1Rk4BG0VJ1fCcoT87pVUY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w6Wnl/btsI02U8YhB/1Rk4BG0VJ1fCcoT87pVUY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w6Wnl/btsI02U8YhB/1Rk4BG0VJ1fCcoT87pVUY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw6Wnl%2FbtsI02U8YhB%2F1Rk4BG0VJ1fCcoT87pVUY1%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;723&quot; height=&quot;348&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 같은 빌드 결과물을 가지고 각 환경에따라 외부 설정(ex:db 정보)을 주입하는것이 이상적이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;2.외부 설정의 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 외부 설정은 일반적으로 4가지 방법이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4wghx/btsI2D7rjzp/knFKbUahBwOav06NdZuw0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4wghx/btsI2D7rjzp/knFKbUahBwOav06NdZuw0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4wghx/btsI2D7rjzp/knFKbUahBwOav06NdZuw0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4wghx%2FbtsI2D7rjzp%2FknFKbUahBwOav06NdZuw0k%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;703&quot; height=&quot;300&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. OS 환경 변수 : OS에서 지원하는 외부 설정이다. 해당 OS를 사용하는 모든 프로세스에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 자바 시스템 속성: 자바에서 지원하는 외부 속성이다. 해당 JVM안에서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정이다. 실행시 main(args) 메서드에서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 외부 파일(설정 데이터) : 프로그램에서 외부 파일을 직접 읽어서 사용한다. 애플리케이션에서 특정 위치의 파일을 읽게해둔다. 예를 들어 data/hello.txt를 읽게 하는식이다.&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-1. OS 환경 변수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값으로 다른 외부 설정과 비교해서 사용범위가 가장 넓다.&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-2. 자바 시스템 속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 안에서 접근 가능한 외부 설정이다. 아래와 같이 사용가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1723439841749&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -Durl=dev -jar app.jar // -D 옵션이 -jar 앞에 있음을 주의&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-D VM 옵션을 통해서 key=value 형식을 주면 된다.&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-3.커맨드 라인 인수(Command line arguments)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 라인 인수는 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 사용가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1723440023554&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -jar app.jar dataA dataB&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;위 같은 경우 dataA, dataB 2개의 문자가 args에 전달된다.&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=value 형식으로 데이터를 받는 것이 편리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 커맨드 라인 인수에 아래와 같이 입력한다면?&lt;/p&gt;
&lt;pre id=&quot;code_1723440162049&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -jar project.jar url=devdb username=dev_user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 라인 인수는 key=value 형식이 아니기때문에 위 예제는 단순히 문자로 인식된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 개발자가 =을 기준으로 직접 데이터를 파싱해서 key=value 형식으로 분리해줘야한다.&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;* 커맨드 라인 옵션 인수(command line option arguments)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서는 위같은 불편함을 해소하기 위해 표준 방식을 정의했는데, 그것이 커맨드 라인 옵션 인수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 라인에 - (dash) 2개를 연결해서 시작하면 key=value 형식으로 정하고 이것을 커맨드 라인 옵션 인수라 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723440494315&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--username=userA --username=userB&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;h3 data-ke-size=&quot;size23&quot;&gt;2-4. 외부 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말그대로 외부에 파일을 선언하고 해당 파일을 읽는 방법이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 스프링의 외부 설정 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 같은 방식은 아래와같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4가지의 외부 설정 방식들은 각각 읽는법이 모두 다르고, 만약 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;따라서 스프링은 Environment와 PropertySource 라는 추상화를 통해서 이를 해결한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjT6tA/btsI1eHR8O0/MuyzCQ3YG5ldKXlNbsKyMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjT6tA/btsI1eHR8O0/MuyzCQ3YG5ldKXlNbsKyMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjT6tA/btsI1eHR8O0/MuyzCQ3YG5ldKXlNbsKyMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjT6tA%2FbtsI1eHR8O0%2FMuyzCQ3YG5ldKXlNbsKyMK%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;233&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 로딩 시점에 필요한 PropertySource들을 생성하고, Environment에서 사용할 수 있게 연결해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 설명해서 PropertySource는 실제 인수이고, Environment는 조회를 위한 객체이다.&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;즉, JVM 안에서 모두 접근하는것 보다 main의 args를 통해서 들어오는 것이 더 범위가 좁기때문에 커맨드 라인 옵션 인수 설정이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 스프링이 제공하는 설정 데이터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에선 수십개의 설정값을 사용하기도 하므로 직접 일일이 커맨드 라인 속성, 자바 시스템 속성을 줘서 사용하는것은 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 스프링은 application.properties으로 선언된 파일을 자바를 실행하는 위치에 두면 설정 데이터(config data)로 읽어들이는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 설정 데이터를 만들어두면 스프링이 해당 파일을 읽고 PropertySource 구현체를 제공하고 , 이 설정 데이터를 Environment를 통해 조회할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 내부 파일의 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 파일을 외부에 두는것은 불편할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 10대일때 변경사항이 있다면, 10대 서버의 설정 파일을 모두 각각 변경해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이럴땐 외부 설정을 프로젝트 내부에 포함해서 관리하면 좋을것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM7KP7/btsI2p9zU9g/IBb4tHweINTScqxCP3kKwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM7KP7/btsI2p9zU9g/IBb4tHweINTScqxCP3kKwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM7KP7/btsI2p9zU9g/IBb4tHweINTScqxCP3kKwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM7KP7%2FbtsI2p9zU9g%2FIBb4tHweINTScqxCP3kKwK%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;685&quot; height=&quot;281&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발용 설정 파일을 application-dev.properties로 두고, 운영용 설정 파일을 application-prod.properties로 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.jar는 두 설정 파일을 모두 가지고 배포되며, 실행할 때 &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;아래와 같은 규칙으로 설정 파일을 생성하면 추후 프로필을 사용해서 맞는 내부 파일을 조회한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723442084915&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;application-{profile}.properties&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 개발서버의 설정 파일은 application-dev.properties가 되고, 운영서버의 설정 파일은 application-prod.properties가&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;이후 개발서버의 실행은 커맨드 라인 옵션 인수로 실행한다면 --spring.profiles.active=dev, 자바 시스템 속성으로 실행한다면 -Dspring.profiles.active=dev 와 같이 실행하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 내부 파일의 합체&lt;/h2&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbma1N/btsI0LGd8qv/kAHs1omWiKBiibkOpN5oZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbma1N/btsI0LGd8qv/kAHs1omWiKBiibkOpN5oZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbma1N/btsI0LGd8qv/kAHs1omWiKBiibkOpN5oZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbma1N%2FbtsI0LGd8qv%2FkAHs1omWiKBiibkOpN5oZk%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;688&quot; height=&quot;270&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 application.properties안에서 &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;spring.config.active.on-profile에 프로필 값을 지정하면된다.&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개의 하이픈 문자가 있어야한다.&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;이제 실행은 커맨드 라인 옵션 인수로 실행한다면 --spring.profiles.active=dev , 자바 시스템 속성으로 실행한다면 -Dspring.profiles.active=dev로 실행하면 된다.&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;만약 프로필을 지정하지 않는다면 두가지 모두 읽지 않고 default 프로필이 활성화 되는데 이럴땐 아래와 같이 선언하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1723442639421&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;url=local.db.com
username=local_user
password=local_pw
#---
spring.config.activate.on-profile=dev
url=dev.db.com
username=dev_user
password=dev_pw
#---
spring.config.activate.on-profile=prod
url=prod.db.com
username=prod_user
password=prod_pw&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장위에 있는 3줄은 프로필을 설정하지 않았으므로 default 프로필이 된다.&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;* 출처 자료&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1726723300015&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;스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕!&amp;nbsp;실무에 필요한 내용을 모두 담았습니다.&amp;nbsp; [임베딩 영상] 김영한의 스&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/스프링부트-핵심원리-활용&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/btpjsc/hyW6Kq8Ij2/2kIbqtj5zLUQUaqjWKBp80/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/NmOjv/hyW2UoEkz3/XezCxFu7Yec2GYykDRAc41/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/3Y8XL/hyW2ZXNAqn/n6ewLULkHfHli59Q4wW67k/img.png?width=1200&amp;amp;height=740&amp;amp;face=984_526_1051_599&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/btpjsc/hyW6Kq8Ij2/2kIbqtj5zLUQUaqjWKBp80/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/NmOjv/hyW2UoEkz3/XezCxFu7Yec2GYykDRAc41/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/3Y8XL/hyW2ZXNAqn/n6ewLULkHfHli59Q4wW67k/img.png?width=1200&amp;amp;height=740&amp;amp;face=984_526_1051_599');&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;스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕!&amp;nbsp;실무에 필요한 내용을 모두 담았습니다.&amp;nbsp; [임베딩 영상] 김영한의 스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/178</guid>
      <comments>https://lsh2016.tistory.com/178#entry178comment</comments>
      <pubDate>Mon, 12 Aug 2024 14:17:38 +0900</pubDate>
    </item>
    <item>
      <title>AWS) ECS(Elastic Container Service)</title>
      <link>https://lsh2016.tistory.com/175</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;ECS란(Elastic&amp;nbsp;Container&amp;nbsp;Service)?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS는 *클러스터에서 컨테이너를 쉽게 실행, 중지 및 관리할 수 있게 해주는 &lt;b&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;/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;h3 data-ke-size=&quot;size23&quot;&gt;시작 유형(Launch Type)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS서비스를 이용하기 위해 클러스터를 생성하면 시작 유형(Launch Type)을 선택해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말그대로 클러스터에 포함될 컴퓨터를 어떤 타입으로 선택할지 고르는것이다. 두 가지 모두 선택한다면 클러스터 내에서 Fargate와 EC2 인스턴스를 함께 사용할 수 있게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhlAln/btsGS6KHIaz/yV9eJfqjE90AdaiK9i5Ck0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhlAln/btsGS6KHIaz/yV9eJfqjE90AdaiK9i5Ck0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhlAln/btsGS6KHIaz/yV9eJfqjE90AdaiK9i5Ck0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhlAln%2FbtsGS6KHIaz%2FyV9eJfqjE90AdaiK9i5Ck0%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;705&quot; height=&quot;278&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 EC2와 Fargate가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2(Elastic Compute Cloud) : AWS 한번만 해보면 모두가 아는 그 유명한 EC2이다. 가상 서버를 제공한다.&lt;/li&gt;
&lt;li&gt;Fargate : EC2 서버를 관리할 필요 없이 컨테이너를 실행하기 위해 ECS에 사용할 수 있는 기술이다. 쉽게말해 따로 서버를 만들지 않고 간단하게 실행할 수 있게해준다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명만 봤을때 EC2를 굳이 왜쓰나 싶지만, 정책과 관련한 문제가 있을 수 있고, GPU사용을 필요로 할시 Fargate는 해당 기능을 지원하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvqZqM/btsGS7JxDAi/Cz95V19NfFSXFERwtu5ZUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvqZqM/btsGS7JxDAi/Cz95V19NfFSXFERwtu5ZUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvqZqM/btsGS7JxDAi/Cz95V19NfFSXFERwtu5ZUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvqZqM%2FbtsGS7JxDAi%2FCz95V19NfFSXFERwtu5ZUk%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;683&quot; height=&quot;243&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진은 ECS가 EC2와 Fargate 두 가지 Launch Type을 이용하여 컨테이너를 관리해주는 모습을 나타낸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작업 정의(Task Definition)&lt;/h3&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 정의에선 빌드할 도커이미지를 설정할 수 있으며 보통 ECR을 사용한다면 ECR의 도커 이미지 URL을 입력받아 사용한다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u95vw/btsGQX2Szmi/yqpJ0xBUsQKDZLLex1Ogb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u95vw/btsGQX2Szmi/yqpJ0xBUsQKDZLLex1Ogb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u95vw/btsGQX2Szmi/yqpJ0xBUsQKDZLLex1Ogb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu95vw%2FbtsGQX2Szmi%2FyqpJ0xBUsQKDZLLex1Ogb1%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;361&quot; height=&quot;104&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 작업 정의 설정시 Task execution role 즉 권한을 설정하는 부분이 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS를 처음 사용하는 경우 escTaskExcutionRole 권한이 없기 때문에 Create new role을 선택하면 자동으로 escTaskExcutionRole IAM 권한이 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;서비스(Service)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 생성시 배포 구성에서 어플리케이션 유형을 선택할 수 있는데 유형은 서비스와 태스크 두 가지가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;903&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHvRan/btsGQlppphl/CGFTRvHQQfu5MggbzevnJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHvRan/btsGQlppphl/CGFTRvHQQfu5MggbzevnJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHvRan/btsGQlppphl/CGFTRvHQQfu5MggbzevnJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHvRan%2FbtsGQlppphl%2FCGFTRvHQQfu5MggbzevnJK%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;650&quot; height=&quot;585&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;903&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Application type에 있는 서비스를 선택했을때, 이것이 ECS에서 말하는 서비스를 의미하며&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;서비스는 미리 만든 작업 정의를 기반으로 한 논리적인 개념으로, 여러 작업을 실행하고 관리하는 역할을 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;간단하게 &lt;b&gt;태스크를 관리&lt;/b&gt;한다고 보면된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;예를 들어 Desired tasks를 2개로 생성시 하나가 꺼졌다고 가정하면 다른 하나의 태스크를 가져다 사용할 수 있게한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GVmD7/btsGPrpP1vO/MagRdngs9G5tVZP5I1qkJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GVmD7/btsGPrpP1vO/MagRdngs9G5tVZP5I1qkJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GVmD7/btsGPrpP1vO/MagRdngs9G5tVZP5I1qkJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGVmD7%2FbtsGPrpP1vO%2FMagRdngs9G5tVZP5I1qkJK%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;649&quot; height=&quot;273&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서비스 생성시 로드 밸런싱을 선택할 수 있는데 이를 사용하여 서비스의 네트워크 트래픽을 분산시킬 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 그림으로 표현하자면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3657&quot; data-origin-height=&quot;2047&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/naVeh/btsGS4gIDRT/y7nb03TT9aAmQcMSltLSJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/naVeh/btsGS4gIDRT/y7nb03TT9aAmQcMSltLSJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/naVeh/btsGS4gIDRT/y7nb03TT9aAmQcMSltLSJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnaVeh%2FbtsGS4gIDRT%2Fy7nb03TT9aAmQcMSltLSJK%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;398&quot; data-origin-width=&quot;3657&quot; data-origin-height=&quot;2047&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;1. 클러스터는 여러대의 서버로 이루어져 있고 그 서버는 Fargate 또는 EC2가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 작업 정의(Task Definitions)는 컨테이너 환경을 구성하고 가져올 도커 이미지의 경로를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 작업 정의(Task Definitions)를 바탕으로 클러스터에서 서비스(Service) 또는 작업(Task)을 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 작업(Task)은 하나의 컨테이너 인스턴스를 의미한다. 간단하게 &lt;b&gt;작업당 하나의 컨테이너!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 서비스(Service)는 작업(Task)들을 관리해주는 기능이며 Replica같은 기능을 지원해준다.&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;참고로 위에선 서비스가 동일한 인스턴스 내에서 task를 관리해주는 경우이다. 이는 서비스 구성시 Service type - Deamon의 경우이며, Replica의 경우 다른 EC2에도 동일한 task가 복제된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt;ECR에 빌드된 이미지가 들어있는것 까진 알겠는데 ECR에 있는 이미지를 PULL하고 실행하는 주체는 누구고 언제 일어날까?&lt;/b&gt;&lt;br /&gt;실제 이미지 pull 및 컨테이너 실행은 &lt;b&gt;&lt;u&gt;ECS 에이전트&lt;/u&gt;&lt;/b&gt;에 의해 이루어진다.&lt;br /&gt;ECS 에이전트는&amp;nbsp;클러스터 내부에 있는 서버에 모두 설치되어 있으며, 작업 정의에 명시된대로 ECR에서 이미지를 가져와 컨테이너를 실행한다.&lt;br /&gt;실행 시점은&amp;nbsp;작업(Task)을 시작할 때, 위 작업을 실행하게 된다.&lt;br /&gt;&lt;br /&gt;결론적으로 ECS를 사용하게 되면 단 하나의 서버도 구성하지 않고 관리,패치,리부팅할 필요 없이 간단한 컨테이너 어플리케이션을 배포할 수 있다.&lt;br /&gt;&lt;br /&gt;  &lt;b&gt;만약 새로운 소스 반영으로 도커를 재시작하고 싶다면?&lt;/b&gt;&lt;br /&gt;다시 새로운 작업 정의를 생성하여 ECR의 이미지 경로를 지정해주고 작업 정의를 토대로 작업(Task)을 생성하면 된다.&lt;br /&gt;&lt;br /&gt; &lt;b&gt;자동으로 배포할순 없을까?&lt;/b&gt;&lt;br /&gt;이는 AWS에서 제공하는 파이프라인을 이용하면 된다.&lt;br /&gt;소스가 수정될시 CodeCommit에서 변경을 감지하게 되고, 파이프라인 설정의 Deploy stage에서 ECS로 설정해준다면 자동화할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/175</guid>
      <comments>https://lsh2016.tistory.com/175#entry175comment</comments>
      <pubDate>Tue, 23 Apr 2024 14:08:52 +0900</pubDate>
    </item>
    <item>
      <title>시간 복잡도 알고리즘 선택 기준</title>
      <link>https://lsh2016.tistory.com/173</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUTtpn/btsF92jYaGd/f0kCmKmj6LrOJkAbltmVNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUTtpn/btsF92jYaGd/f0kCmKmj6LrOJkAbltmVNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUTtpn/btsF92jYaGd/f0kCmKmj6LrOJkAbltmVNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUTtpn%2FbtsF92jYaGd%2Ff0kCmKmj6LrOJkAbltmVNk%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;332&quot; height=&quot;426&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;따라서 코딩 테스트의 경우 연산 횟수는 1,000~3,000만 정도로 고려해서 시간 복잡도를 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 제한 시간이 1초인 문제는 연산 횟수가 3,000만이 넘는 알고리즘은 사용하면 안된다.&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;color: #ffffff; background-color: #ef5369;&quot;&gt;제한 시간이 1초&lt;/span&gt;인 문제에 각 시간 복잡도별로 허용할 수 있는 &lt;span style=&quot;background-color: #ef5369; color: #ffffff;&quot;&gt;N의 가용 범위&lt;/span&gt;는 아래와 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 36.5116%; height: 231px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;시간 복잡도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;N의 가용 범위&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(N!)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(2ⁿ)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;20~25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(N&amp;sup3;)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;200~300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;3,000 ~ 5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(NlogN)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;100만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(N)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;1,000~2,000만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;O(logN)&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;10억&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;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도 초과 예제)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제약 조건&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수 배열의 길이는 2이상 10⁵입니다.&lt;/li&gt;
&lt;li&gt;정수 배열의 각 데이터 값은 -100,000 이상 100,000 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 배열의 길이는 최대 10⁵ 즉, 10만이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 O(N&amp;sup2;)은 사용할수가 없다.&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;그럴리는 없지만 제한 시간이 34초 이상이라면 3,000 x 34가 10만을 초과하므로 가능할것이다.&lt;/p&gt;</description>
      <category>코딩 테스트</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/173</guid>
      <comments>https://lsh2016.tistory.com/173#entry173comment</comments>
      <pubDate>Fri, 29 Mar 2024 00:11:54 +0900</pubDate>
    </item>
    <item>
      <title>Spring Advanced 정리 4) 스프링의 프록시 등록 방식</title>
      <link>https://lsh2016.tistory.com/172</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 김영한님의 스프링 고급편 강의중 제목과 관련된 부분을 블로그장의 취향대로 요약한 것이며 강의 자료 및 출처는 가장 아래에서 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.리플렉션(reflection) 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 클래스 A,B가 있을때 프록시를 적용하고 싶다면 프록시 A클래스와 프록시 B클래스를 만들어서 적용하면 될 것이다. 그런데 이는 굉장히 불편하다. 프록시 객체를 동적으로 변경할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자바에서 지원하는 리플렉션(reflection)기술을 사용하여 공통으로 프록시 객체를 생성하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션은 클래스나 메서드의 메타정보를 사용해서 동적으로 호출하는 메서드를 변경할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1711614443659&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
    void reflection() throws Exception{
        Class classHello = Class.forName(&quot;hello.proxy.jdkdynamic.ReflectionTest$Hello&quot;);

        Hello target = new Hello();
        Method methodCallA = classHello.getMethod(&quot;callA&quot;);
        dynamicCall(methodCallA, target);

        Method methodCallB = classHello.getMethod(&quot;callB&quot;);
        dynamicCall(methodCallB, target);
    }
    
 // 공통 로직1,2를 한번에 처리할 수 있는 통합된 공통 처리 로직
    private void dynamicCall(Method method, Object target) throws Exception{
        log.info(&quot;start&quot;);
        Object result = method.invoke(target); // 메서드를 실행시킴
        log.info(&quot;result={}&quot;, result);
    }&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;target과 method정보를 파라미터로 넘겨 메서드를 실행한다. 여기서 중요한 점은 callA와 callB메서드를 직접 호출하는 부분이 Method하나로 대체되면서 공통 로직을 만들 수 있게되었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.동적 프록시(JDK Dynamic Proxy) 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 리플렉션 기술을 사용하여 자동으로 프록시 객체를 생성할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK 동적 프록시 기술을 사용하면 &lt;span style=&quot;background-color: #ef5369; color: #ffffff;&quot;&gt;개발자가 직접 프록시 객체를 생성하지 않아도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용하는 방법은 InvocationHandler 인터페이스를 구현하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1711615038580&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class TimeInvocationHandler implements InvocationHandler {

    private final Object target;

    public TimeInvocationHandler(Object target){
        this.target = target;
    }
    /**
     * Object proxy : 프록시 자신
     * Method method : 호출한 메서드
     * Object[] args = 메서드를 호출할 때 전달한 인수
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info(&quot;TimeProxy 실행&quot;);
        long startTime = System.currentTimeMillis();

        Object result = method.invoke(target, args);

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info(&quot;TimeProxy 종료 resultTime={}&quot;, resultTime);
        return result;
    }
}&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;InvocationHandler&lt;span&gt; 인터페이스를 &lt;/span&gt;&lt;/span&gt;구현한 예제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 생성할때 target 대상 객체를 받도록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711615158164&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    void dynamicA(){
        AInterface target = new AImpl();
        TimeInvocationHandler handler = new TimeInvocationHandler(target); // 동적 프록시에 적용할 핸들러 로직
        /**
         * 동적 프록시는 java.lang.reflect.Proxy 를 통해서 실행할 수 있다.
         * 클래스 로더 정보, 인터페이스, 핸들러 로직을 넣어주면 해당 인터페이스를 기반으로 동적 프록시를 생성하고 그 결과를 반환한다.
         */
        AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class},handler);
        proxy.call();
        log.info(&quot;targetClass={}&quot;, target.getClass());
        log.info(&quot;proxyClass={}&quot;, proxy.getClass());
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target 객체를 넣어 TimeInvocationHandler를 생성한뒤, Proxy.newProxyInstance()를 통하여 동적 프록시를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltTvV/btsF9BM628z/kuA4Lj3tE1l8zR30m2MCyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltTvV/btsF9BM628z/kuA4Lj3tE1l8zR30m2MCyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltTvV/btsF9BM628z/kuA4Lj3tE1l8zR30m2MCyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FltTvV%2FbtsF9BM628z%2FkuA4Lj3tE1l8zR30m2MCyk%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;742&quot; height=&quot;82&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 보면 알 수 있듯이, Proxy.newProxyInstance()로 생성된 프록시는 클래스 정보에 $Proxy를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. CGLIB 기술 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 JDK동적 프록시는 사실 인터페이스가 필수이기 때문에 인터페이스에만 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스가 없고 클래스만 있는 경우는 CGLIB라이브러리를 적용하여 동적 프록시를 적용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK동적 프록시 적용을 위해 InvocationHandler를 사용했듯이, CGLIB는 MethodInterceptor인터페이스를 구현하면 동적 프록시 적용이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1711615611899&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class TimeMethodInterceptor implements MethodInterceptor {

    private final Object target;

    public TimeMethodInterceptor(Object target){
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        log.info(&quot;TimeProxy 실행&quot;);
        long startTime = System.currentTimeMillis();

        Object result = proxy.invoke(target, args);

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info(&quot;TimeProxy 종료 resultTime={}&quot;, resultTime);
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK동적 프록시와 모습이 유사한 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.프록시 팩토리(Proxy Factory) 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK동적 프록시에선 InvocationHandler를 구현하여 내부에 부가기능을 추가하였고, CGLIB는 MethodInterceptor를 구현하여 부가기능을 추가하였다. 그런데 이처럼 인터페이스에는 JDK 동적 프록시를 써야하고 없을땐 CGLIB를 써야한다면 중복으로 관리되어 불편할 것이다.&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;따라서 부가 기능을 적용하기 위해 이를 하나로 합치는 Advice라는 개념이 등장하고, 스프링은 프록시 팩토리(ProxyFactory)를 통해 이 Advice를 사용할 수 있도록한다. 프록시 팩토리는 자동으로 인터페이스가 있다면 JDK 동적 프록시를 사용하고 없다면 CGLIB를 사용한다.&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;프록시 팩토리를 사용하면 아래와 같이 Advice를 호출하는 전용 InvocationHandler와 MethodInterceptor를 내부에서 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q3z4p/btsF88qV4Kp/EThp5N15iuP3AK6Lh2LBQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q3z4p/btsF88qV4Kp/EThp5N15iuP3AK6Lh2LBQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q3z4p/btsF88qV4Kp/EThp5N15iuP3AK6Lh2LBQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ3z4p%2FbtsF88qV4Kp%2FEThp5N15iuP3AK6Lh2LBQK%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;631&quot; height=&quot;248&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프록시 팩토리는 특정 조건에 맞을 때 프록시 로직을 적용하는 기능도 제공하는데 Pointcut이라는 개념을 도입하여 이 문제를 해결한다.&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;먼저 Advice를 구현하는 방법은 아래와같이 MethodInterceptor인터페이스를 구현하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(CGLIB를 구현할때도 MethodInterceptor를 구현하였는데 이름만 같고 서로 패키지가 다르다.)&lt;/p&gt;
&lt;pre id=&quot;code_1711616656166&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TimeAdvice implements MethodInterceptor{
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable { // target 클래스의 정보는 MethodInvocation안에 모두 포함되어 있음
        log.info(&quot;TimeProxy 실행&quot;);
        long startTime = System.currentTimeMillis();

        Object result = invocation.proceed();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info(&quot;TimeProxy 종료 resultTime={}ms&quot;, resultTime);
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Advice를 생성하였다. 이제 프록시 팩토리에서 위 Advice를 실행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1711616735805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    @DisplayName(&quot;인터페이스가 있으면 JDK 동적 프록시 사용&quot;)
    void interfaceProxy(){
        ServiceInterface target = new ServiceImpl();
        /**
         * 프록시 팩토리를 생성할 때, 생성자에 호출 대상을 함께 넘겨준다.
         * 프록시 팩토리는 이 인스턴스 정보를 기반으로 프록시를 생성한다.
         * 이 인스턴스에 인터페이스가 있다면 JDK 동적 프록시를 사용하고, 없고 구체 클래스만 있다면 CGLIB를 통해서 동적 프록시를 생성한다.
         */
        ProxyFactory proxyFactory = new ProxyFactory(target);
        /**
         * 프록시 팩토리를 통해서 만든 프록시가 사용할 부가 기능 로직을 설정한다.
         * 이와 같이 프록시가 제공하는 부가 기능 로직을 &quot;어드바이스(Advice)&quot;라 한다. 조언을 해준다고 생각하면 된다.
         */
        proxyFactory.addAdvice(new TimeAdvice());

        // 프록시 객체를 생성하고 그 결과를 받는다.
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info(&quot;targetClass={}&quot;, target.getClass());
        log.info(&quot;proxyClass={}&quot;, proxy.getClass());

        proxy.save();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
        assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
    }&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;간단하게 요약하자면 프록시 팩토리를 생성할때 target을 지정해주고 앞에서 생성한 Advice를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target은 인터페이스이므로 JDK동적 프록시가 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1861&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7wX1V/btsF9M1UR6X/fVO9nwLa32JsY9aYKck7C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7wX1V/btsF9M1UR6X/fVO9nwLa32JsY9aYKck7C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7wX1V/btsF9M1UR6X/fVO9nwLa32JsY9aYKck7C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7wX1V%2FbtsF9M1UR6X%2FfVO9nwLa32JsY9aYKck7C1%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;780&quot; height=&quot;88&quot; data-origin-width=&quot;1861&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 문제없이 잘 실행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.빈 후처리기(BeanPostProcessor) 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 프록시 팩토리에 직접 Target을 지정하여 프록시를 적용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여러개의 Target이 있을 때, 예를 들어 인터페이스가 100개가 있다면 프록시를 통해 부가 기능을 적용할때 100개의 Target을 지정하는 코드가 필요할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 컴포넌트 스캔을 사용하는 경우 수동으로 Config클래스에서 빈을 프록시로 등록하지 못할것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이럴때 두 가지를 모두 해결하기 위해 스프링은 빈 후처리기(BeanPostProcessor)를 적용한다.&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;빈 후처리기를 적용하려면 BeanPostProcessor 인터페이스를 구현하고, 스프링 빈으로 등록하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1711675807069&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
static class BeanPostProcessorConfig{
        @Bean(name = &quot;beanA&quot;) // 빈 이름 지정
        public A a(){
            return new A();
        }

        @Bean // 빈 후처리기를 빈으로 등록
        public AToBPostProcessor helloPostProcessor(){
            return new AToBPostProcessor();
        }
    }

static class A {
        public void helloA() {
            log.info(&quot;hello A&quot;);
        }
    }
static class B {
        public void helloB() {
            log.info(&quot;hello B&quot;);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 A,B 클래스가 있고 빈 후처리기를 적용하기 위해 BeanPostProcessor를 구현한 AToBPostProcessor()를 빈으로 등록하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1711675663603&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static class AToBPostProcessor implements BeanPostProcessor{
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if(bean instanceof A){
                return new B();
            }
            return bean;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AToBPostProcessor 클래스는 타입이 A인 빈이 온다면 B로 바꿔치기 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711675824381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    void postProcessor(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);

        //beanA 이름으로 B 객체가 빈으로 등록된다.
        B b = applicationContext.getBean(&quot;beanA&quot;, B.class);
        b.helloB();

        //A는 빈으로 등록되지 않는다.
        Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -&amp;gt; applicationContext.getBean(A.class));
    }&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;p data-ke-size=&quot;size16&quot;&gt;이제 100개의 Target 인터페이스를 실행할 동적 프록시 코드는 위 빈 후처리기 내에서 포인트컷을 지정하여 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 컴포넌트 스캔을 쓰지않는 부분이 있다고 가정할때, 하나씩 모두 수동으로 Bean으로 등록할것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711676540484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public OrderControllerV1 orderControllerV1(){
        return new OrderControllerV1Impl(orderServiceV1());
    }

    @Bean
    public OrderServiceV1 orderServiceV1(){
        return new OrderServiceV1Impl(orderRepositoryV1());
    }

    @Bean
    public OrderRepositoryV1 orderRepositoryV1(){
        return new OrderRepositoryV1Impl();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 빈 후처리기가 없고 프록시 팩토리만 적용한다고 했을때 아래와 같이 어드바이저(Advisor = Advice+Pointcut)를 직접 등록하여 하나씩 모두 중복으로 등록해줘야 할것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711676645040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class ProxyFactoryConfig {
    @Bean
    public OrderControllerV1 orderControllerV1(LogTrace logTrace){
        OrderControllerV1 orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));

        ProxyFactory factory = new ProxyFactory(orderController);
        factory.addAdvisor(getAdvisor(logTrace));
        OrderControllerV1 proxy = (OrderControllerV1) factory.getProxy();
        log.info(&quot;ProxyFactory proxy={}, target={}&quot;, proxy.getClass(), orderController.getClass());
        return proxy;
    }

    @Bean
    public OrderServiceV1 orderServiceV1(LogTrace logTrace){
        OrderServiceV1 orderService = new OrderServiceV1Impl(orderRepositoryV1(logTrace));

        ProxyFactory factory = new ProxyFactory(orderService);
        factory.addAdvisor(getAdvisor(logTrace));
        OrderServiceV1 proxy = (OrderServiceV1) factory.getProxy();
        log.info(&quot;ProxyFactory proxy={}, target={}&quot;, proxy.getClass(), orderService.getClass());
        return proxy;
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 후처리기를 적용하면 아래와 같이 적용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1711676716574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class BeanPostProcessorConfig {

    @Bean
    public PackageLogTraceProxyPostProcessor logTraceProxyPostProcessor(LogTrace logTrace){
    // 패키지 명을 받도록하여 빈 후처리기 내에서 넘겨준 패키지에 속하지 않을경우 빈을 변경하지 않도록 설정
        return new PackageLogTraceProxyPostProcessor(&quot;hello.proxy.app&quot;, getAdvisor(logTrace));
    }

    private Advisor getAdvisor(LogTrace logTrace){
        //pointcut
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames(&quot;request*&quot;, &quot;order*&quot;, &quot;save*&quot;);
        //advice
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);
        //advisor = pointcut + advice
        return new DefaultPointcutAdvisor(pointcut, advice);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 설정을 @Import로 불러와서, 포인트컷을 지정한 뒤 공통으로 처리할 수 있게되었다.&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;6. 스프링의 자동 프록시 생성기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 아래 의존성을 추가하면 AOP 관련 클래스를 자동으로 스프링 빈에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711678198555&quot; class=&quot;clean&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-aop'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 의존성을 추가하면 @Aspect가 적용된 곳을 자동으로 인식해서 프록시를 만들고 AOP를 적용해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 등록되는 빈 후처리기는 아래와 같은 구조를 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw8e1l/btsGcPXlPn1/7wgYF6PuDY4qUHkx59UK91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw8e1l/btsGcPXlPn1/7wgYF6PuDY4qUHkx59UK91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw8e1l/btsGcPXlPn1/7wgYF6PuDY4qUHkx59UK91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw8e1l%2FbtsGcPXlPn1%2F7wgYF6PuDY4qUHkx59UK91%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;653&quot; height=&quot;271&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Aspect 및 스프링 AOP는 다음 포스팅에서 제대로 설명한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;*포인트컷(Pointcut)의 두 가지 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일일이 Target을 지정하여 프록시를 지정하는것이 아닌, 포인트 컷으로 처리하면 깔끔할 것 같다.&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;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;즉 포인트 컷은 &lt;span style=&quot;background-color: #ef5369; color: #ffffff;&quot;&gt;빈 후처리기 내부에서도 사용하고, 빈이 생성되고 프록시 내부에서도 메서드를 걸러내는 두 가지 기능을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어본다면 hello.proxy.app 패키지 아래에 orderController가 있고 request()와 noLog()가 있다고 가정하고,포인트 컷의 표현식은 아래라고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711677838657&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pointcut.setExpression(&quot;execution(* hello.proxy.app..*(..)) &amp;amp;&amp;amp; !execution(* hello.proxy.app..noLog(..))&quot;);&lt;/code&gt;&lt;/pre&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;사용 : 적용되어 있는 프록시에서 다시 포인트컷을 확인한다. 두번째 조건식에서 noLog()는 허용하지 않기때문에, 어드바이스를 호출하지 않고 바로 Target을 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;* 출처 자료&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711678639220&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;스프링 핵심 원리 - 고급편 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iBUET/hyVGDOeF55/1R1J9lMcQR69B1wglrq2p0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bpAnK9/hyVGJHFkiz/R08ePa5UnRcUAYJ13meJak/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/HMjS1/hyVGEM8mXn/RbViPg8cpdL5Iiws9njqNk/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iBUET/hyVGDOeF55/1R1J9lMcQR69B1wglrq2p0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bpAnK9/hyVGJHFkiz/R08ePa5UnRcUAYJ13meJak/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/HMjS1/hyVGEM8mXn/RbViPg8cpdL5Iiws9njqNk/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&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;스프링 핵심 원리 - 고급편 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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</category>
      <author>HDev</author>
      <guid isPermaLink="true">https://lsh2016.tistory.com/172</guid>
      <comments>https://lsh2016.tistory.com/172#entry172comment</comments>
      <pubDate>Thu, 28 Mar 2024 18:08:28 +0900</pubDate>
    </item>
  </channel>
</rss>