<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>for_develop</title>
    <link>https://fordevelop.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 27 Jul 2026 23:37:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sw_develop</managingEditor>
    <image>
      <title>for_develop</title>
      <url>https://tistory1.daumcdn.net/tistory/4437725/attach/a41a4616cdb14b9aadade5339bb69ded</url>
      <link>https://fordevelop.tistory.com</link>
    </image>
    <item>
      <title>[Spring DB] 커넥션풀과 데이터소스 이해</title>
      <link>https://fordevelop.tistory.com/309</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;커넥션 풀 이해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 커넥션을 매번 획득&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nJ36Z/btsOXPurAjX/zwH1NkIgIkaAVXZNbD0f40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nJ36Z/btsOXPurAjX/zwH1NkIgIkaAVXZNbD0f40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nJ36Z/btsOXPurAjX/zwH1NkIgIkaAVXZNbD0f40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnJ36Z%2FbtsOXPurAjX%2FzwH1NkIgIkaAVXZNbD0f40%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;400&quot; height=&quot;117&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 커넥션을 획득할 때는 아래 과정을 거침&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회한다.&lt;/li&gt;
&lt;li&gt;DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. 이 과정에서 3 Way Handshake 같은 TCP/IP 연결을 위한 네트워크 동작이 발생한다.&lt;/li&gt;
&lt;li&gt;DB 드라이버는 TCP/IP 커넥션이 연결되면 ID, PW와 기타 부가정보를 DB에 전달한다.&lt;/li&gt;
&lt;li&gt;DB는 ID, PW를 통해 내부 인증을 완료하고, 내부에 DB 세션을 생성한다.&lt;/li&gt;
&lt;li&gt;DB는 커넥션 생성이 완료되었다는 응답을 보낸다.&lt;/li&gt;
&lt;li&gt;DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&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;DB와 어플리케이션 서버 모두 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용해야 함&lt;/li&gt;
&lt;li&gt;SQL을 실행하는 시간 뿐 아니라 커넥션을 생성하는 시간이 추가되어 결과적으로 응답 속도에 영향을 줌&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 문제를 해결하는 아이디어가 &lt;b&gt;커넥션을 미리 생성하고 사용하는 '커넥션 풀' 방법&lt;/b&gt;임&lt;/li&gt;
&lt;li&gt;커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있기 때문에 DB에 무한정 연결이 생성되는 것을 막아줌&lt;/li&gt;
&lt;li&gt;최근에는 &lt;b&gt;hikariCP를 주로 사용&lt;/b&gt;함 (Spring Boot 2.0부터 기본 커넥션 풀로 hikariCP 제공)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커넥션 풀 초기화 및 연결 상태&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/roBV2/btsOUXA5NO8/uBbrriqp9IQVyPKokkq8kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/roBV2/btsOUXA5NO8/uBbrriqp9IQVyPKokkq8kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/roBV2/btsOUXA5NO8/uBbrriqp9IQVyPKokkq8kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FroBV2%2FbtsOUXA5NO8%2FuBbrriqp9IQVyPKokkq8kK%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;400&quot; height=&quot;175&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;어플리케이션을 시작하는 시점에 필요한 만큼 커넥션을 미리 생성해서 커넥션 풀에 보관&lt;/u&gt;함&lt;/li&gt;
&lt;li&gt;&lt;u&gt;커넥션 풀에 들어있는 커넥션은 TCP/IP로 DB와 커넥션이 연결되어 있는 상태&lt;/u&gt;이기 때문에 언제든지 SQL을 DB에 전달할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커넥션 풀 사용&lt;/h4&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;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QkQwB/btsOYfTU98t/DUbsKnbj0ltNe5WTZVZCdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QkQwB/btsOYfTU98t/DUbsKnbj0ltNe5WTZVZCdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QkQwB/btsOYfTU98t/DUbsKnbj0ltNe5WTZVZCdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQkQwB%2FbtsOYfTU98t%2FDUbsKnbj0ltNe5WTZVZCdK%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;400&quot; height=&quot;265&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플리케이션 로직에서 이제는 DB 드라이버를 통해 새로운 커넥션을 획득하는 것이 아님&lt;/li&gt;
&lt;li&gt;이제는 커넥션 풀을 통해 이미 생성되어 있는 커넥션을 객체 참조로 가져다 쓰기만 하면 됨&lt;/li&gt;
&lt;li&gt;커넥션 풀에 커넥션을 요청하면 커넥션 풀은 자신이 가지고 있는 커넥션 중 하나를 반환함&lt;/li&gt;
&lt;/ul&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;978&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JVnsH/btsOUW3dep7/eY84HOIsD7hBKrsm6k7iBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JVnsH/btsOUW3dep7/eY84HOIsD7hBKrsm6k7iBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JVnsH/btsOUW3dep7/eY84HOIsD7hBKrsm6k7iBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJVnsH%2FbtsOUW3dep7%2FeY84HOIsD7hBKrsm6k7iBK%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;400&quot; height=&quot;263&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플리케이션 로직은 커넥션 풀에서 받은 커넥션을 사용해 SQL을 DB에 전달하고 그 결과를 받아서 처리함&lt;/li&gt;
&lt;li&gt;커넥션을 모두 사용하고 나면 이제는 커넥션을 종료하는 것이 아니라, 다음에 다시 사용할 수 있도록 해당 커넥션을 그대로 커넥션 풀에 반환하면 됨. 이때 &lt;u&gt;커넥션을 종료하는 것이 아니라 커넥션이 살아있는 상태로 커넥션 풀에 반환&lt;/u&gt;해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DataSource 이해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커넥션을 획득하는 방법을 추상화&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecpDSP/btsOVYl3RbP/t46F7LjWjZSRe1XAZG7yN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecpDSP/btsOVYl3RbP/t46F7LjWjZSRe1XAZG7yN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecpDSP/btsOVYl3RbP/t46F7LjWjZSRe1XAZG7yN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecpDSP%2FbtsOVYl3RbP%2Ft46F7LjWjZSRe1XAZG7yN0%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;400&quot; height=&quot;181&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 &lt;u&gt;javax.sql.DataSource라는 인터페이스를 제공&lt;/u&gt;함&lt;/li&gt;
&lt;li&gt;DataSource는 &lt;u&gt;커넥션을 획득하는 방법을 추상화하는 인터페이스&lt;/u&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 커넥션 풀은 DataSource 인터페이스를 이미 구현해두었으므로, DataSource 인터페이스에만 의존하도록 어플리케이션 로직을 작성하면 됨&lt;/li&gt;
&lt;li&gt;DriverManager는 DataSource 인터페이스를 구현하지 않지만 &lt;u&gt;스프링은 DriverManager도 DataSource를 통해서 사용할 수 있도록 DriverManagerDataSource라는 DataSource 인터페이스를 구현한 클래스를 제공&lt;/u&gt;함&lt;/li&gt;
&lt;li&gt;&lt;u&gt;자바는 DataSource를 통해 커넥션을 획득하는 방법을 추상화&lt;/u&gt;함. 이제 &lt;u&gt;어플리케이션 로직은 DataSource 인터페이스에만 의존&lt;/u&gt;하면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 - DriverManager, 커넥션 풀&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wonlog-g/spring-db-jdbc/blob/main/src/test/java/hello/jdbc/repository/MemberRepositoryV1Test.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스프링 DB 1편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/309</guid>
      <comments>https://fordevelop.tistory.com/309#entry309comment</comments>
      <pubDate>Sat, 12 Jul 2025 15:35:31 +0900</pubDate>
    </item>
    <item>
      <title>[Spring DB] 데이터 접근 핵심 원리 - JDBC 이해</title>
      <link>https://fordevelop.tistory.com/308</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;JDBC 이해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC 등장 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어플리케이션 서버와 DB - 일반적인 사용법&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xhqq5/btsOMePokc9/D77tXEq27rVEaTuvMlsWL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xhqq5/btsOMePokc9/D77tXEq27rVEaTuvMlsWL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xhqq5/btsOMePokc9/D77tXEq27rVEaTuvMlsWL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxhqq5%2FbtsOMePokc9%2FD77tXEq27rVEaTuvMlsWL0%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;400&quot; height=&quot;143&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;커넥션 연결 : 주로 TCP/IP를 사용해서 커넥션을 연결한다.&lt;/li&gt;
&lt;li&gt;SQL 전달 : 어플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션을 통해 DB에 전달한다.&lt;/li&gt;
&lt;li&gt;결과 응답 : DB는 전달된 SQL을 수행하고 그 결과를 응답한다. 어플리케이션 서버는 응답 결과를 활용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어플리케이션 서버와 DB - DB 변경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 데이터베이스마다 커넥션을 연결하는 방법, SQL을 전달하는 방법, 결과를 응답 받는 방법이 모두 다름&lt;/li&gt;
&lt;li&gt;이런 문제를 해결하기 위해 JDBC라는 자바 표준이 등장함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC 표준 인터페이스&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCp1jU/btsOM1ogCmK/J5nUpgulDpC3tJlRP32Zr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCp1jU/btsOM1ogCmK/J5nUpgulDpC3tJlRP32Zr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCp1jU/btsOM1ogCmK/J5nUpgulDpC3tJlRP32Zr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCp1jU%2FbtsOM1ogCmK%2FJ5nUpgulDpC3tJlRP32Zr0%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;400&quot; height=&quot;197&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표적으로 다음 3가지 기능을 표준 인터페이스로 정의해서 제공함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.sql.Connection - 연결&lt;/li&gt;
&lt;li&gt;java.sql.Statement - SQL을 담은 내용&lt;/li&gt;
&lt;li&gt;java.sql.ResultSet - SQL 요청 응답&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JDBC 인터페이스를 각각의 DB 벤더에서 자신의 DB에 맞도록 구현해서 라이브러리로 제공하고 이것을 'JDBC 드라이버'라고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JDBC와 최신 데이터 접근 기술&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC를 직접 사용하기 보다 &lt;b&gt;JDBC를 편리하게 사용하는 다양한 기술이 존재&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;대표적으로 &lt;b&gt;SQL Mapper와 ORM 기술&lt;/b&gt;로 나눌 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1) JDBC 직접 사용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xfmwq/btsONwn2zwl/ydFcr7jv5gGiYrwRuevR50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xfmwq/btsONwn2zwl/ydFcr7jv5gGiYrwRuevR50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xfmwq/btsONwn2zwl/ydFcr7jv5gGiYrwRuevR50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXfmwq%2FbtsONwn2zwl%2FydFcr7jv5gGiYrwRuevR50%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;400&quot; height=&quot;85&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2) SQL Mapper&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPYnIt/btsOMRzp0iL/g9zv1SqN9hTEh5jgHwqEU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPYnIt/btsOMRzp0iL/g9zv1SqN9hTEh5jgHwqEU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPYnIt/btsOMRzp0iL/g9zv1SqN9hTEh5jgHwqEU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPYnIt%2FbtsOMRzp0iL%2Fg9zv1SqN9hTEh5jgHwqEU0%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;400&quot; height=&quot;92&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC를 편리하게 사용하도록 도와준다.&lt;/li&gt;
&lt;li&gt;SQL 응답 결과를 객체로 편리하게 변환해준다.&lt;/li&gt;
&lt;li&gt;JDBC의 반복 코드를 제거해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL을 직접 작성해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대표 기술 : 스프링 JdbcTemplate, MyBatis&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3) ORM 기술&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh8Xqw/btsOMVV59H2/kYCtDAyv8kBatYEdHTp41k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh8Xqw/btsOMVV59H2/kYCtDAyv8kBatYEdHTp41k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh8Xqw/btsOMVV59H2/kYCtDAyv8kBatYEdHTp41k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh8Xqw%2FbtsOMVV59H2%2FkYCtDAyv8kBatYEdHTp41k%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;400&quot; height=&quot;522&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ORM은 &lt;b&gt;객체를 관계형 DB 테이블과 매핑&lt;/b&gt;해주는 기술&lt;/li&gt;
&lt;li&gt;반복적인 SQL을 직접 작성하지 않고, ORM 기술이 &lt;b&gt;개발자 대신에 SQL을 동적으로 만들어 실행&lt;/b&gt;해줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추가로 각각의 DB마다 다른 SQL을 사용하는 문제도 중간에서 해결해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대표 기술 : JPA, Hibernate, EclipseLink
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA는 자바 진영의 ORM 표준 인터페이스이고, 이것을 구현한 기술로 Hibernate와 EclipseLink 등이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;JDBC DriverManager 연결 이해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JDBC 커넥션 인터페이스와 구현&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FusaY/btsOOfN8W6j/pQfRYuJ4dJbivUVJKYcc2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FusaY/btsOOfN8W6j/pQfRYuJ4dJbivUVJKYcc2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FusaY/btsOOfN8W6j/pQfRYuJ4dJbivUVJKYcc2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFusaY%2FbtsOOfN8W6j%2FpQfRYuJ4dJbivUVJKYcc2k%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;350&quot; height=&quot;176&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC는 java.sql.Connection 표준 커넥션 인터페이스를 정의함&lt;/li&gt;
&lt;li&gt;H2 데이터베이스 드라이버는 JDBC Connection 인터페이스를 구현한 org.h2.jdbc.JdbcConnection 구현체를 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DriverManager 커넥션 요청 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXlpWM/btsON0X31dM/qqdFITM17HJpCJfNxuv0N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXlpWM/btsON0X31dM/qqdFITM17HJpCJfNxuv0N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXlpWM/btsON0X31dM/qqdFITM17HJpCJfNxuv0N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXlpWM%2FbtsON0X31dM%2FqqdFITM17HJpCJfNxuv0N0%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;400&quot; height=&quot;178&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC가 제공하는 DriverManager는 라이브러리에 등록된 DB 드라이버들을 관리하고, 커넥션을 획득하는 기능을 제공함&lt;/li&gt;
&lt;li&gt;흐름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플리케이션 로직에서 커넥션이 필요하면 DriverManager.getConnection()을 호출한다.&lt;/li&gt;
&lt;li&gt;DriverManager는 라이브러리에 등록된 드라이버 목록을 자동으로 인식한다. 이 드라이버들에게 순서대로 다음 정보를 넘겨서 커넥션을 획득할 수 있는지 확인한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL, 이름, 비밀번호 등 접속에 필요한 추가 정보 전달&lt;/li&gt;
&lt;li&gt;각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이렇게 찾은 커넥션 구현체가 클라이언트에 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스프링 DB 1편 - 데이터 접근 핵심 원리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/308</guid>
      <comments>https://fordevelop.tistory.com/308#entry308comment</comments>
      <pubDate>Sat, 28 Jun 2025 23:15:59 +0900</pubDate>
    </item>
    <item>
      <title>[stack] valid parentheses</title>
      <link>https://fordevelop.tistory.com/307</link>
      <description>&lt;h3 id=&quot;✅-problem&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;Problem&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/valid-parentheses/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/valid-parentheses/description/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;✅&amp;nbsp;approach--solution&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Approach &amp;amp; Solution&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;방식1) stack, map 사용&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1749264137920&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public boolean isValid(String s) {
    	// 유효성 검증을 위한 스택 선언
        Deque&amp;lt;Character&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();

        // 매핑 테이블 선언
        Map&amp;lt;Character, Character&amp;gt; table = new HashMap&amp;lt;&amp;gt;(){{
            put(')', '(');
            put('}', '{');
            put(']', '[');
        }};

        // 한 문자씩 처리
        for (int i = 0; i &amp;lt; s.length(); i++) {
            char c = s.charAt(i);
            // 열림 괄호일 때
            if (!table.containsKey(c)) {
                stack.push(c);
            }
            // 닫힘 괄호일 때
            else if (stack.isEmpty() || table.get(c) != stack.pop()) {
                return false;
            }
        }

        // 문자열 순회 완료 후 빈 스택이어야 함
        return stack.isEmpty();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;방식2) 배열 사용&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;char[]의 시작 idx = -1&lt;/li&gt;
&lt;li&gt;열림 괄호일 때 대응하는 닫힘 괄호를 배열에 저장&lt;/li&gt;
&lt;li&gt;닫힘 괄호일 때 배열의 마지막 원소가 본인과 일치하는지 확인&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1749264032411&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public boolean isValid(String s) {
        int idx = -1;
        char[] bracketArr = new char[s.length()];

        for (int i = 0; i &amp;lt; s.length(); i++) {
            char c = s.charAt(i);

            if (c == '(') {
                bracketArr[++idx] = ')';
            } else if (c == '{') {
                bracketArr[++idx] = '}';
            } else if (c == '[') {
                bracketArr[++idx] = ']';
            } else {
                if (idx == -1 || bracketArr[idx--] != c) {
                    return false;
                }
            }
        }
        
        return idx == -1; // 문자열 모두 순회 후 빈 배열이어야 함
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Algorithm/LeetCode</category>
      <category>stack</category>
      <category>string</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/307</guid>
      <comments>https://fordevelop.tistory.com/307#entry307comment</comments>
      <pubDate>Sat, 7 Jun 2025 11:42:49 +0900</pubDate>
    </item>
    <item>
      <title>[linked list] reverse linked list 2</title>
      <link>https://fordevelop.tistory.com/306</link>
      <description>&lt;h3 id=&quot;✅-problem&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;Problem&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/reverse-linked-list-ii/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/reverse-linked-list-ii/description/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;✅&amp;nbsp;approach--solution&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Approach &amp;amp; Solution&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;head 노드를 가리키는 임시 노드를 생성한다.&lt;/li&gt;
&lt;li&gt;start와 end 노드를 고정하고 한 단계씩 뒤집기를 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1749179252109&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if (head == null) {
            return null;
        }

        ListNode root = new ListNode(0); // 임시 노드 선언
        root.next = head;

        // 임시 노드부터 시작해 변경 필요한 위치 앞으로 이동
        ListNode start = root;
        for (int i = 0; i &amp;lt; left - 1; i++) {
            start = start.next;
        }
        
        // 변경 필요한 마지막 위치 선언
        ListNode end = start.next;

        // right - left만큼 위치 변경 진행
        // start와 end가 가리키는 노드를 고정하고 한 단계씩 뒤집기 수행
        for (int i = 0; i &amp;lt; right - left; i++) {
            ListNode tmp = start.next;
            start.next = end.next;
            end.next = end.next.next;
            start.next.next = tmp;
        }

        // 첫 번째 노드는 임시 노드이므로 그 다음부터 결과로 리턴
        return root.next;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Algorithm/LeetCode</category>
      <category>linked list</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/306</guid>
      <comments>https://fordevelop.tistory.com/306#entry306comment</comments>
      <pubDate>Fri, 6 Jun 2025 12:11:19 +0900</pubDate>
    </item>
    <item>
      <title>[linked list] odd even linked list</title>
      <link>https://fordevelop.tistory.com/305</link>
      <description>&lt;h3 id=&quot;✅-problem&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;Problem&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/odd-even-linked-list/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/odd-even-linked-list/description/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;✅&amp;nbsp;approach--solution&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Approach &amp;amp; Solution&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어진 노드를 사용해 odd 노드는 odd 노드끼리, even 노드는 even 노드끼리 연결한다.&lt;/li&gt;
&lt;li&gt;odd 노드의 마지막과 even 노드의 첫 번째 연결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1748134997474&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null) {
            return null;
        }
        
        ListNode odd = head;
        ListNode even = head.next;
        ListNode evenHead = even;

        while(even != null &amp;amp;&amp;amp; even.next != null) {
            odd.next = even.next;
            even.next = even.next.next;

            odd = odd.next;
            even = even.next;
        }

        // odd 노드의 마지막과 even 노드의 첫 번째 연결
        odd.next = evenHead;

        return head; // head가 가리키는 노드는 odd의 첫 번째 노드
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Algorithm/LeetCode</category>
      <category>linked list</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/305</guid>
      <comments>https://fordevelop.tistory.com/305#entry305comment</comments>
      <pubDate>Sun, 25 May 2025 10:03:52 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 핵심 원리 - 스프링 AOP 주의사항</title>
      <link>https://fordevelop.tistory.com/304</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시와 내부 호출 - 문제&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링은 프록시 방식의 AOP를 사용&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP를 적용하려면 항상 프록시를 통해서 대상 객체(Target)를 호출해야&lt;/b&gt; 함&lt;/li&gt;
&lt;li&gt;만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않으므로 Advice(부가 기능)도 호출되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일반적인 상황
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AOP를 적용하면 스프링은 대상 객체 대신에 &lt;b&gt;프록시 객체를 스프링 빈으로 등록&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;따라서 스프링은 &lt;b&gt;의존관계 주입 시 항상 프록시 객체를 주입&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문제 상황
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생&lt;/b&gt;함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시 방식의 AOP 한계 : &lt;b&gt;메서드 내부 호출에 프록시를 적용할 수 없음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc6LPP/btsNxWuL0bV/Nm4NzPIFGwbXMKyG4aadwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc6LPP/btsNxWuL0bV/Nm4NzPIFGwbXMKyG4aadwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc6LPP/btsNxWuL0bV/Nm4NzPIFGwbXMKyG4aadwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc6LPP%2FbtsNxWuL0bV%2FNm4NzPIFGwbXMKyG4aadwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;146&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;380&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 호출을 해결하는 가장 간단한 방법은 &lt;b&gt;자기 자신을 의존관계 주입 받는 것&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성자 주입 방식은 순환 사이클을 만들기 때문에 사용 불가하므로 setter 주입 사용&lt;/b&gt;해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745424364617&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Slf4j
@Aspect
public class CallLogAspect {

    @Before(&quot;execution(* com.example.demo.proxy.internalcall..*(..))&quot;)
    public void doLog(JoinPoint joinPoint) {
        log.info(&quot;app={}&quot;, joinPoint.getSignature());
    }
}&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;아래 CallServiceV1에 AOP를 적용하기 위해 Aspect 정의함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1745424092243&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CallServiceV1 {

    private CallServiceV1 callServiceV1;

    @Autowired
    public void setCallServiceV1(CallServiceV1 callServiceV1) {
        this.callServiceV1 = callServiceV1;
    }

    public void external() {
        log.info(&quot;call external&quot;);
        callServiceV1.internal(); // 외부 메서드 호출로 변경
    }

    private void internal() {
        log.info(&quot;call internal&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;자기자신을 의존관계 주입으로 받는 경우 생성자 주입은 순환 사이클을 만들기 때문에 사용할 수 없음 (위에서는 setter 주입 대신 사용)&lt;/li&gt;
&lt;li&gt;스프링에서 AOP가 적용된 대상을 의존관계 주입 받으면 주입 받은 대상은 실제 자신이 아니라 프록시 객체임&lt;/li&gt;
&lt;li&gt;위에서 수정자를 통해 주입받은 callServiceV1은 프록시이므로 AOP를 적용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uETia/btsNwcl92ph/IENkLICvNTbtWhm1UNjbVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uETia/btsNwcl92ph/IENkLICvNTbtWhm1UNjbVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uETia/btsNwcl92ph/IENkLICvNTbtWhm1UNjbVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuETia%2FbtsNwcl92ph%2FIENkLICvNTbtWhm1UNjbVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;185&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;2. 지연 조회&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링 빈을 지연해서 조회하는 방식&lt;/b&gt;으로, &lt;b&gt;ObjectProvider 혹은 ApplicationContext를 사용&lt;/b&gt;하면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ObjectProvider는 &lt;b&gt;스프링 컨테이너에서 객체를 조회하는 것&lt;/b&gt;을 스프링 빈 생성 시점이 아니라 &lt;b&gt;실제 객체를 사용하는 시점으로 지연&lt;/b&gt;할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 CallServiceV2에 AOP를 적용하기 위해 Aspect 정의한 상태라고 가정&lt;/p&gt;
&lt;pre id=&quot;code_1745426231902&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV2 {

    // private final ApplicationContext applicationContext;
    private final ObjectProvider&amp;lt;CallServiceV2&amp;gt; callServiceProvider;

    public void external() {
        log.info(&quot;call external&quot;);
        // CallServiceV2 callServiceV2 = applicationContext.getBean(CallServiceV2.class);
        CallServiceV2 serviceV2 = callServiceProvider.getObject();
        serviceV2.internal(); // 외부 메서드 호출
    }

    private void internal() {
        log.info(&quot;call internal&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;`callServiceProvider.getObject()`를 호출하는 시점에 스프링 컨테이너에서 빈을 조회함 (프록시 객체가 반환됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 나은 대안은 &lt;b&gt;내부 호출이 발생하지 않도록 구조를 변경&lt;/b&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745426990696&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV3 {

    private final InternalService internalService;

    public void external() {
        log.info(&quot;call external&quot;);
        internalService.internal(); // 외부 메서드 호출
    }
}&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;내부 호출을 InternalService라는 별도의 클래스로 분리함&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxmxI9/btsNxSlDwj8/QISerfitaExkvJ1TUgdDpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxmxI9/btsNxSlDwj8/QISerfitaExkvJ1TUgdDpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxmxI9/btsNxSlDwj8/QISerfitaExkvJ1TUgdDpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxmxI9%2FbtsNxSlDwj8%2FQISerfitaExkvJ1TUgdDpK%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;450&quot; height=&quot;229&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;h4 data-ke-size=&quot;size20&quot;&gt;JDK 동적 프록시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구체 클래스로 타입 캐스팅 불가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;인터페이스를 기반으로 프록시를 생성하기 때문 (인터페이스를 구현하여 프록시 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQRmdf/btsNDRt1xNU/lJ6V8BSOYeo1oNBEzkESSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQRmdf/btsNDRt1xNU/lJ6V8BSOYeo1oNBEzkESSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQRmdf/btsNDRt1xNU/lJ6V8BSOYeo1oNBEzkESSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQRmdf%2FbtsNDRt1xNU%2FlJ6V8BSOYeo1oNBEzkESSk%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;400&quot; height=&quot;143&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&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;/ul&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IZj9F/btsNDqcqpj9/zUQxHyMG6qln2yeULMsFh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IZj9F/btsNDqcqpj9/zUQxHyMG6qln2yeULMsFh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IZj9F/btsNDqcqpj9/zUQxHyMG6qln2yeULMsFh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIZj9F%2FbtsNDqcqpj9%2FzUQxHyMG6qln2yeULMsFh0%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;400&quot; height=&quot;592&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&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;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CGLIB&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구체 클래스를 상속받기 때문에 다음과 같은 문제&lt;/b&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;대상 클래스의 생성자 2번 호출&lt;/li&gt;
&lt;li&gt;final 키워드 클래스, 메서드 사용 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대상 클래스에 기본 생성자 필수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 언어에서 상속을 받으면 자식 클래스의 생성자를 호출할 때 자식 클래스의 생성자에서 부모 클래스의 생성자도 호출해야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 부분이 코드상 생략되어 있다면 컴파일 시 컴파일러가 자식 클래스의 생성자 첫 줄에 부모 클래스의 기본 생성자를 호출하는 super()가 자동으로 추가함 (이것은 자바 문법 규약)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CGLIB 프록시는 대상 클래스를 상속 받고, 생성자에서 대상 클래스의 기본 생성자를 호출함. 따라서 대상 클래스에 기본 생성자를 만들어야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 생성자는 파라미터가 하나도 없는 생성자&lt;/li&gt;
&lt;li&gt;생성자가 하나도 없으면 자동으로 만들어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대상 클래스의 생성자 2번 호출 문제&lt;/b&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번 호출됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 target의 객체를 생성할 때&lt;/li&gt;
&lt;li&gt;프록시 객체를 생성할 때 부모 클래스의 생성자 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDrjC/btsNC15bAai/aTmyutLR0pqjJF4V8Jgro1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDrjC/btsNC15bAai/aTmyutLR0pqjJF4V8Jgro1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDrjC/btsNC15bAai/aTmyutLR0pqjJF4V8Jgro1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDrjC%2FbtsNC15bAai%2FaTmyutLR0pqjJF4V8Jgro1%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;400&quot; height=&quot;191&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;final 키워드 클래스, 메서드 사용 불가&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;final 키워드가 클래스에 있으면 상속이 불가능하고, 메서드에 있으면 오버라이딩 불가능함&lt;/li&gt;
&lt;li&gt;CGLIB는 상속을 기반으로 하기 때문에 두 경우 프록시가 생성되지 않거나 정상 동작하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;h4 data-ke-size=&quot;size20&quot;&gt;스프링의 기술 선택 변화&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 3.2부터 CGLIB를 스프링 내부에 함께 패키징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은 CGLIB 라이브러리를 스프링 내부에 함께 패키징해서 별도의 라이브러리 추가 없이 CGLIB를 사용할 수 있게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CGLIB 기본 생성자 필수 문제 해결
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 4.0부터 CGLIB의 기본 생성자가 필수인 문제가 해결됨&lt;/li&gt;
&lt;li&gt;objenesis라는 특별한 라이브러리를 사용해 기본 생성자 없이 객체 생성이 가능해짐 (해당 라이브러리는 생성자 호출 없이 객체를 생성할 수 있게 해줌)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성자 2번 호출 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 4.0부터 CGLIB의 생성자 2번 호출 문제가 해결됨 (target 객체 생성할 때만 1번 호출됨)&lt;/li&gt;
&lt;li&gt;이것도 objenesis 라이브러리 덕분에 가능해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 부트 2.0부터 CGLIB 기본 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 부트는 별도의 설정이 없다면 AOP를 적용할 때 기본적으로 proxyTargetClass=true로 설정해서 사용함 (CGLIB 사용)&lt;/li&gt;
&lt;li&gt;인터페이스가 있어도 JDK 동적 프록시가 아닌 항상 CGLIB를 사용해 구체 클래스를 기반으로 프록시를 생성함&lt;/li&gt;
&lt;li&gt;다음과 같이 설정하면 JDK 동적 프록시도 사용할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application.properties &amp;gt; spring.aop.proxy-target-class=false&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리하자면,&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링 부트 2.0에서부터 CGLIB를 기본으로 사용&lt;/b&gt;하도록 함&lt;/li&gt;
&lt;li&gt;CGLIB를 사용하면 JDK 동적 프록시에서 동작하지 않는 구체 클래스 주입이 가능함&lt;/li&gt;
&lt;li&gt;추가로 objenesis 라이브러리를 사용해 CGLIB의 단점들이 많이 해결됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;참고&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; 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&quot;&gt;스프링 핵심 원리 - 고급편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/304</guid>
      <comments>https://fordevelop.tistory.com/304#entry304comment</comments>
      <pubDate>Wed, 7 May 2025 06:47:51 +0900</pubDate>
    </item>
    <item>
      <title>[linked list] swap nodes in pairs</title>
      <link>https://fordevelop.tistory.com/303</link>
      <description>&lt;h3 id=&quot;✅-problem&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;Problem&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/swap-nodes-in-pairs/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/swap-nodes-in-pairs/description/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;✅&amp;nbsp;approach--solution&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;✅&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Approach &amp;amp; Solution&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&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;ex) dummy -&amp;gt; 노드1 -&amp;gt; 노드2 -&amp;gt; 노드3 -&amp;gt; 노드4&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패턴화 시키기 위해 dummy 노드 추가&lt;/li&gt;
&lt;li&gt;노드1과 노드2 swap&lt;/li&gt;
&lt;li&gt;dummy가 노드2를 가리키도록 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1746326644182&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0, head);
        ListNode prev = dummy; // swap 대상 노드의 이전 노드
        ListNode cur = head; // swap 대상 노드

        while (cur != null &amp;amp;&amp;amp; cur.next != null) {
            ListNode nextPairNode = cur.next.next; // 다음 swap 대상 노드 저장

            ListNode second = cur.next; // 노드2 저장
            second.next = cur; // 노드2 -&amp;gt; 노드1 연결
            cur.next = nextPairNode; // 노드1 -&amp;gt; 노드3 연결

            prev.next = second; // swap 대상 노드의 이전 노드 -&amp;gt; 노드2 연결

            prev = cur;
            cur = nextPairNode;
        }

        return dummy.next;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Algorithm/LeetCode</category>
      <category>linked list</category>
      <category>Recursion</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/303</guid>
      <comments>https://fordevelop.tistory.com/303#entry303comment</comments>
      <pubDate>Sun, 4 May 2025 11:45:09 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 핵심 원리 - 스프링 AOP</title>
      <link>https://fordevelop.tistory.com/302</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;AOP 소개 - 핵심 기능과 부가 기능&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;부가 기능 : 핵심 기능을 보조하기 위해 제공되는 기능 ex) 로그 추적 로직, 트랜잭션 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단독으로 사용되지 않고, 핵심 기능과 함께 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부가 기능 적용 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 부가 기능은 여러 클래스에 걸쳐서 함께 사용됨. 따라서 이러한 부가 기능은 횡단 관심사(cross-cutting concerns)가 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 하나의 부가 기능이 여러 곳에 동일하게 사용되다는 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아래와 같은 문제 발생&lt;/b&gt;함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부가 기능이 여러 곳에 퍼져 중복 코드를 만들어냄&lt;/li&gt;
&lt;li&gt;중복으로 인해 부가 기능을 변경하거나 부가 기능의 적용 대상을 변경할 때 많은 수정이 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byY6x2/btsNvyuJY9a/sF6cbp8mSGjEJKhok2IYU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byY6x2/btsNvyuJY9a/sF6cbp8mSGjEJKhok2IYU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byY6x2/btsNvyuJY9a/sF6cbp8mSGjEJKhok2IYU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyY6x2%2FbtsNvyuJY9a%2FsF6cbp8mSGjEJKhok2IYU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;101&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;250&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;h2 data-ke-size=&quot;size26&quot;&gt;AOP 소개 - Aspect&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 같은 &lt;b&gt;부가 기능의 문제를 보완하기 위해 Aspect라는 개념을 도입&lt;/b&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;Aspect는 &lt;b&gt;부가 기능과, 해당 부가 기능을 어디에 적용할지 정의한 것&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;스프링이 제공하는 Advisor도 Advice(부가 기능)와 Pointcut(적용 대상)을 가지고 있어서 개념상 하나의 Aspect임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Aspect는 '관점'이라는 뜻으로, 이름 그대로 &lt;b&gt;어플리케이션을 바라보는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting concerns) 관점으로 달리 보는 것&lt;/b&gt;임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Aspect를 사용한 프로그래밍 방식&lt;/b&gt;을 '&lt;b&gt;관점 지향 프로그래밍 AOP&lt;/b&gt;(Aspect-Oriented Programming)'이라 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;횡단 관심사를 깔끔하게 처리하기 어려운 OOP의 부족한 부분을 보조하는 목적으로 개발됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP의 대표적인 구현으로 AspectJ 프레임워크&lt;/b&gt;가 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링도 AOP를 지원하지만 대부분 AspectJ의 문법을 차용하고, AspectJ가 제공하는 기능의 일부만 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bekmkp/btsNviFVPZ8/PKFCxZ2ti6fWwyf5RRUGM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bekmkp/btsNviFVPZ8/PKFCxZ2ti6fWwyf5RRUGM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bekmkp/btsNviFVPZ8/PKFCxZ2ti6fWwyf5RRUGM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbekmkp%2FbtsNviFVPZ8%2FPKFCxZ2ti6fWwyf5RRUGM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;222&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AOP 적용 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링&lt;/b&gt;은 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;런타임 시점에 프록시 방식의 AOP&lt;/span&gt;&amp;nbsp;를 사용&lt;/b&gt;함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blfvq0/btsNvDQR31w/prmmUN9fAq3EMWvL0BHXkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blfvq0/btsNvDQR31w/prmmUN9fAq3EMWvL0BHXkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blfvq0/btsNvDQR31w/prmmUN9fAq3EMWvL0BHXkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblfvq0%2FbtsNvDQR31w%2FprmmUN9fAq3EMWvL0BHXkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;234&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;런타임 시점&lt;/b&gt;은 &lt;b&gt;컴파일과 클래스 로딩이 끝나서 이미 자바가 실행되고 난 다음&lt;/b&gt;을 말함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 자바의 메인(main) 메서드가 이미 실행된 후&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서 자바 언어가 제공하는 범위 안에서 부가 기능을 적용해야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링과 같은 컨테이너의 도움을 받고, 프록시과 DI, Bean Post Processor 같은 기능들을 동원해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;최종적으로 &lt;b&gt;프록시를 통해 스프링 빈에 부가 기능을 적용&lt;/b&gt;할 수 있음&lt;/li&gt;
&lt;li&gt;참고)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은 AspectJ의 문법을 차용하고 프록시 방식의 AOP를 적용함. AspectJ를 직접 사용하는 것이 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프록시 방식을 사용하는 스프링 AOP의 장단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;스프링만 있으면 얼마든지 AOP를 적용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AOP 기능에 일부 제약&lt;/b&gt;이 있음&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메서드 실행 지점에만 AOP를 적용&lt;/b&gt;할 수 있음&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프록시는 메서드 오버라이딩 개념으로 동작&lt;/b&gt;함. 따라서 생성자나 static 메서드, 필드 값 접근에는 프록시 개념이 적용될 수 없음&lt;/li&gt;
&lt;li&gt;즉, 스프링 AOP의 조인 포인트는 메서드 실행으로 제한됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP를 적용&lt;/b&gt;할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BeanPostProcessor를 사용해 프록시 객체를 스프링 빈으로 등록하기 때문&lt;/li&gt;
&lt;li&gt;대상 객체가 스프링 빈이 아니라면 프록시 객체를 런타임에 생성하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링이 제공하는 AOP는 프록시를 사용&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(스프링 빈을 대상으로) 프록시를 통해 메서드를 실행하는 시점에만 AOP가 적용&lt;/b&gt;됨&lt;/li&gt;
&lt;li&gt;스프링 AOP는 별도의 추가 자바 설정 없이 스프링만 있으면 편리하게 AOP를 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;추가)&lt;/b&gt; 컴파일 시점, 클래스 로딩 시점의 AOP 적용 방식&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일 시점&lt;/h3&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mfkzd/btsNxSeL0zj/C5kxImqor0luTC4UkgQ4DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mfkzd/btsNxSeL0zj/C5kxImqor0luTC4UkgQ4DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mfkzd/btsNxSeL0zj/C5kxImqor0luTC4UkgQ4DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmfkzd%2FbtsNxSeL0zj%2FC5kxImqor0luTC4UkgQ4DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;206&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;.java 소스 코드를 컴파일러를 사용해 .class를 만드는 시점에 부가 기능 로직을 추가&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AspectJ가 제공하는 특별한 컴파일러를 사용&lt;/b&gt;해야 함&lt;/li&gt;
&lt;li&gt;컴파일된 .class를 디컴파일 해보면 Aspect 관련 호출 코드가 들어가 있음&lt;/li&gt;
&lt;li&gt;AspectJ 컴파일러는 Aspect를 확인해 해당 클래스가 적용 대상인지 먼저 확인하고, 적용 대상인 경우에 부가 기능 로직을 적용함&lt;/li&gt;
&lt;li&gt;이렇게 원본 로직에 부가 기능 로직이 추가되는 것을 '위빙(Weaving)'이라 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 시점에 부가 기능을 적용하려면 특별한 컴파일러도 필요하고 복잡함&lt;/li&gt;
&lt;/ul&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;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QMjAx/btsNw0EA8V3/Tm8MvzDkdeGN5X3nzaiit0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QMjAx/btsNw0EA8V3/Tm8MvzDkdeGN5X3nzaiit0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QMjAx/btsNw0EA8V3/Tm8MvzDkdeGN5X3nzaiit0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQMjAx%2FbtsNw0EA8V3%2FTm8MvzDkdeGN5X3nzaiit0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;476&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자바를 실행하기 위해 클래스 파일을 JVM의 런타임 데이터 영역에 로딩&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;이때&amp;nbsp;중간에서 &lt;b&gt;.class 파일을 조작한 다음 JVM에 올릴 수 있음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;자바 언어는 .class를 JVM에 로딩하기 전에 조작할 수 있는 기능을 제공함&lt;/li&gt;
&lt;li&gt;이 시점에 Aspect를 적용하는 것을 '로드 타임 위빙'이라 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바를 실행할 때 특별한 옵션(java -javaagnet)을 통해 클래스 로더 조작기를 지정해야 하는데, 이 부분이 번거롭고 운영하기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;런타임 시점에 적용하는 AOP와의 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;부가 기능 적용 차이&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 시점, 클래스 로딩 시점에 적용하는 AOP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실제 대상 코드에&lt;/b&gt; Aspect를 통한 부가 기능 호출 코드가 포함됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AspectJ를 직접 사용&lt;/b&gt;해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;런타임 시점에 적용하는 AOP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 대상 코드는 그대로 유지됨. 대신 프록시를 통해 부가 기능이 적용됨&lt;/li&gt;
&lt;li&gt;따라서 항상 프록시를 통해야 부가 기능을 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP 적용 위치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: left;&quot;&gt;컴파일 시점, 클래스 로딩 시점에 적용하는 AOP&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: left;&quot;&gt;AspectJ를 사용해 바이트코드를 실제 조작하기 때문에 모든 지점에 적용할 수 있음&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: left;&quot;&gt;적용 가능 지점(join point) : 생성자, 필드 값, static 메서드, 메서드&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: left;&quot;&gt;런타임 시점에 적용하는 AOP&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: left;&quot;&gt;프록시 방식을 사용하기 때문에 메서드 실행 지점에만 적용할 수 있음 (메서드 오버라이딩 불가능한 static 메서드 제외)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AOP 용어 정리&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egfJrc/btsNx2V0qUo/mqAhNjHYiV4WSI36rNwFA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egfJrc/btsNx2V0qUo/mqAhNjHYiV4WSI36rNwFA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egfJrc/btsNx2V0qUo/mqAhNjHYiV4WSI36rNwFA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegfJrc%2FbtsNx2V0qUo%2FmqAhNjHYiV4WSI36rNwFA0%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;450&quot; height=&quot;352&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Join Point
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Advice가 적용될 수 있는 위치, 메서드 실행, 생성자 호출, 필드 값 접근, static 메서드 접근 같은 프로그램 실행 중 지점&lt;/li&gt;
&lt;li&gt;추상적인 개념으로, AOP를 적용할 수 있는 모든 지점을 의미함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 AOP는 프록시 방식을 사용하므로 Join Point는 항상 메서드 실행 지점으로 제한&lt;/b&gt;됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pointcut
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Join Point 중에서 Advice가 적용될 위치를 선별함&lt;/li&gt;
&lt;li&gt;주로 AspectJ 표현식을 사용해 지정함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 Pointcut으로 선별 가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Target
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Advice를 받는 객체, Pointcut으로 결정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advice
&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;Around, Before, After와 같은 다양한 종류의 Advice가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Aspect
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Advice + Pointcut을 모듈화한 것&lt;/li&gt;
&lt;li&gt;@Aspect를 생각하면 됨&lt;/li&gt;
&lt;li&gt;여러 Advice와 Pointcut이 함께 존재함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advisor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Advice와 하나의 Pointcut으로 구성&lt;/li&gt;
&lt;li&gt;스프링 AOP에서만 사용되는 특별한 용어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weaving
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pointcut으로 결정한 Target의 Join Point에 Advice를 적용하는 것&lt;/li&gt;
&lt;li&gt;핵심 기능 코드에 영향을 주지 않고 부가 기능을 추가할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP 프록시&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AOP 기능을 구현하기 위해 만든 프록시 객체&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시&lt;/b&gt;임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/302</guid>
      <comments>https://fordevelop.tistory.com/302#entry302comment</comments>
      <pubDate>Wed, 23 Apr 2025 21:45:40 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 핵심 원리 - @Aspect AOP</title>
      <link>https://fordevelop.tistory.com/301</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Aspect 프록시 - 적용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은 &lt;b&gt;@Aspect 어노테이션으로 Pointcut과 Advice로 구성되어 있는 Advisor 생성 기능을 지원&lt;/b&gt;함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745330198422&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Slf4j
@Aspect
public class LogTraceAspect {

    private final LogTrace logTrace;

    public LogTraceAspect(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    @Around(&quot;execution(* com.example.demo.proxy..*(..))&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {

        TraceStatus status = null;

        try {
            String message = joinPoint.getSignature().toShortString();
            status = logTrace.begin(message);

            Object result = joinPoint.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}&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;@Aspect : 어노테이션 기반 프록시를 적용할 때 필요함&lt;/li&gt;
&lt;li&gt;@Around(...)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Around의 값에 Pointcut 표현식을 넣음 (표현식은 AspectJ 표현식을 사용함)&lt;/li&gt;
&lt;li&gt;@Around의 메서드는 Advice가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ProceedingJoinPoint joinPoint : Advice에서 살펴본 `MethodInvocation methodInvocation`과 유사한 기능임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부에 실제 호출 대상, 전달 인자, 어떤 객체와 어떤 메서드가 호출되었는지 정보가 포함되어 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;joinPoint.proceed() : 실제 호출 대상(target)을 호출함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Aspect가 있어도 스프링 빈으로 등록해줘야 함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;@Aspect 프록시 - 설명&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링의 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)는 2가지 역할을 수행&lt;/b&gt;함
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;@Aspect를 보고 Advisor(Pointcut + Advice)로 변환해서 저장&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Advisor를 기반으로 프록시를 생성&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) @Aspect를 Advisor로 변환해서 저장하는 과정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDLG9F/btsNu1kfJrJ/21wETSN3VHzMWrGETplllk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDLG9F/btsNu1kfJrJ/21wETSN3VHzMWrGETplllk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDLG9F/btsNu1kfJrJ/21wETSN3VHzMWrGETplllk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDLG9F%2FbtsNu1kfJrJ%2F21wETSN3VHzMWrGETplllk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;196&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;실행&lt;/b&gt; : 스프링 어플리케이션 로딩 시점에 자동 프록시 생성기를 호출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모든 @Aspect 빈 조회&lt;/b&gt; : 자동 프록시 생성기는 스프링 컨테이너에서 @Aspect 어노테이션이 붙은 스프링 빈을 모두 조회한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Advisor 생성&lt;/b&gt; : @Aspect Advisor Builder를 통해 @Aspect 어노테이션 정보를 기반으로 Advisor를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Aspect 기반 Advisor 저장&lt;/b&gt; : 생성한 Advisor를 @Aspect Advisor Builder 내부에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가) @Aspect Advisor Builder&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BeanFactoryAspectJAdvisorsBuilder 클래스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Aspect의 정보를 기반으로 Pointcut, Advice, Advisor를 생성하고 보관하는 것을 담당&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Aspect의 정보를 기반으로 Advisor를 만들고,&lt;/b&gt; &lt;b&gt;@Aspect Advisor Builder 내부 저장소에 캐시&lt;/b&gt;함&lt;/li&gt;
&lt;li&gt;캐시에 Advisor가 이미 만들어져 있는 경우 캐시에 저장된 Advisor를 반환함&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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) Advisor를 기반으로 프록시 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bovpdb/btsNu394CDP/GC7nPawcCCOW7KjUtDUwM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bovpdb/btsNu394CDP/GC7nPawcCCOW7KjUtDUwM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bovpdb/btsNu394CDP/GC7nPawcCCOW7KjUtDUwM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbovpdb%2FbtsNu394CDP%2FGC7nPawcCCOW7KjUtDUwM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;220&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;574&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;생성&lt;/b&gt; : 스프링 빈 대상이 되는 객체를 생성함 (@Bean, 컴포넌트 스캔 모두 포함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전달&lt;/b&gt; : &lt;u&gt;생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달&lt;/u&gt;함 (여기서는 &lt;u&gt;스프링의 자동 프록시 생성기에 전달&lt;/u&gt;됨)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Advisor 조회&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Advisor 빈 조회 : 스프링 컨테이너에서 Advisor 빈을 모두 조회함&lt;/li&gt;
&lt;li&gt;@Aspect Advisor 조회 : @Aspect Advisor Builder 내부에 저장된 Advisor를 모두 조회함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 적용 대상 체크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3-1, 3-2에서 조회한 Advisor에 포함되어 있는 Pointcut을 사용해서 해당 객체가 프록시를 적용할 대상인지 판단함&lt;/li&gt;
&lt;li&gt;이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 Pointcut에 하나하나 모두 매칭해봄&lt;/li&gt;
&lt;li&gt;&lt;u&gt;조건이 하나라도 만족하면 프록시 적용 대상&lt;/u&gt;이 됨 (메서드 하나만 Pointcut 조건에 만족해도 프록시 적용 대상이 됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 생성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 적용 대상이면 프록시를 생성하고 프록시를 반환하고 해당 프록시를 스프링 빈으로 등록함&lt;/li&gt;
&lt;li&gt;프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빈 등록&lt;/b&gt; : 반환된 객체는 스프링 빈으로 등록됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;참고&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; 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&quot;&gt;스프링 핵심 원리 - 고급편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/301</guid>
      <comments>https://fordevelop.tistory.com/301#entry301comment</comments>
      <pubDate>Tue, 22 Apr 2025 23:33:34 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 핵심 원리 - 빈 후처리기</title>
      <link>https://fordevelop.tistory.com/300</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빈 후처리기 (BeanPostProcessor)&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Bean이나 컴포넌트 스캔에 의해 스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 &lt;b&gt;빈 저장소에 등록하기 직전에 조작하고 싶다면 BeanPostProcessor를 사용&lt;/b&gt;하면 됨&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;빈 객체를 프록시로 교체하는 것도 가능&lt;/b&gt;함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BeanPostProcessor에 의한 스프링 빈 등록 과정&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OD5Z3/btsNtgtLLoz/x2LC6JJ6CrlkiPH9FEDTLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OD5Z3/btsNtgtLLoz/x2LC6JJ6CrlkiPH9FEDTLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OD5Z3/btsNtgtLLoz/x2LC6JJ6CrlkiPH9FEDTLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOD5Z3%2FbtsNtgtLLoz%2Fx2LC6JJ6CrlkiPH9FEDTLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;145&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;생성&lt;/b&gt; : 스프링 빈 대상이 되는 객체를 생성한다. (@Bean, 컴포넌트 스캔 모두 포함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;전달&lt;/b&gt; : 생성된 객체를 빈 저장소에 등록하기 직전에 BeanPostProcessor에 전달한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;후 처리 작업&lt;/b&gt; : BeanPostProcessor는 전달받은 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;등록&lt;/b&gt; : BeanPostProcessor는 빈을 반환한다. 전달된 빈을 그대로 반환하면 해당 빈이 등록되고, 바꿔치기 하면 다른 객체가 빈 저장소에 등록된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;참고)&lt;/b&gt; BeanPostProcessor 인터페이스 - 스프링 제공&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745159239550&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.springframework.beans.factory.config;

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}&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;span style=&quot;color: #000000;&quot;&gt;빈 후처리기를 사용하려면 해당 인터페이스를 구현하고, 스프링 빈으로 등록하면 됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;postProcessBeforeInitialization : 객체 생성 이후 @PostConstruct 같은 초기화 발행하기 전에 호출되는 포스트 프로세서&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;postProcessAfterInitialization : &lt;span style=&quot;background-color: #fafafa; text-align: left;&quot;&gt;객체 생성 이후 @PostConstruct 같은 초기화 발행한 다음에 호출되는 포스트 프로세서&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;스프링이 제공하는 빈 후처리기&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스프링은 프록시를 생성하기 위한 빈 후처리기를 이미 만들어서 제공&lt;/b&gt;함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;build.gradle 추가&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745161166153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-aop'&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;span style=&quot;color: #000000;&quot;&gt;해당 라이브러리를 추가하면 aspectjweaver라는 aspectJ 관련 라이브러리를 등록하고, 스프링부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록함&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링부트가 활성화하는 빈은 AopAutoConfiguration 클래스 참고&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링부트가 없던 시절에는 @EnableAspectJAutoProxy를 직접 사용해야 했는데, 이 부분을 스프링부트가 자동으로 처리해줌&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;color: #000000;&quot;&gt;자동 프록시 생성기 (AutoProxyCreator)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;위 스프링부트 자동 설정으로 AnnotationAwareAspectJAutoProxyProcessor 라는 빈 후처리기가 스프링 빈에 자동 등록&lt;/b&gt;됨&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이름 그대로 &lt;b&gt;자동으로 프록시를 생성해주는 빈 후처리기&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스프링 빈으로 등록된 Advisor들을 자동으로 찾아서 프록시가 필요한 곳에 프록시를 적용&lt;/b&gt;해줌&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Advisor 안에는 Pointcut과 Advice가 이미 포함되어 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 Advisor만 알고 있으면 Pointcut으로 어떤 스프링 빈에 프록시를 적용해야 할지 알 수 있고, Advice로 부가 기능을 적용하면 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작동 과정&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD8Wxf/btsNspkMtH5/A76BJOcU00uoOXkpETeQ21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD8Wxf/btsNspkMtH5/A76BJOcU00uoOXkpETeQ21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD8Wxf/btsNspkMtH5/A76BJOcU00uoOXkpETeQ21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD8Wxf%2FbtsNspkMtH5%2FA76BJOcU00uoOXkpETeQ21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;215&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #000000;&quot;&gt;생성 : 스프링이 스프링 빈 대상이 되는 객체를 생성한다. (@Bean, 컴포넌트 스캔 모두 포함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전달 : 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 Advisor 빈 조회 : 스프링 컨테이너에서 모든 Advisor를 조회한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 적용 대상 체크 : 앞서 조회한 Advisor에 포함되어 있는 Pointcut를 사용해서 해당 객체가 프록시 적용 대상인지 판단한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대상 객체의 클래스 정보와 모든 메서드를 Pointcut에 모두 매칭하고 조건이 하나라도 만족하면 프록시 적용 대상이 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 생성 : 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록함&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빈 등록 : 반환된 객체는 스프링 빈으로 등록됨&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성된 프록시&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHMFYh/btsNsRA2yYF/wrWQVfZOIvk3amuzkzPbO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHMFYh/btsNsRA2yYF/wrWQVfZOIvk3amuzkzPbO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHMFYh/btsNsRA2yYF/wrWQVfZOIvk3amuzkzPbO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHMFYh%2FbtsNsRA2yYF%2FwrWQVfZOIvk3amuzkzPbO1%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;400&quot; height=&quot;238&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 내부에 Advisor와 실제 호출해야 할 대상 객체(target)을 알고 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Advisor Bean 등록&lt;/h4&gt;
&lt;pre id=&quot;code_1745249023434&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AutoProxyConfig {

    @Bean
    public Advisor advisor1() {
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames(&quot;request*&quot;, &quot;order*&quot;, &quot;save*&quot;);

        TimeAdvice advice = new TimeAdvice();
        
        // advisor = pointcut + advice
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}&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;BeanPostProcessor는 등록하지 않고 Advisor만 Bean으로 등록함&lt;/li&gt;
&lt;li&gt;스프링은 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)라는 빈 후처리기를 자동으로 등록해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;중요: Pointcut의 2가지 용도&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프록시 적용 여부 판단 - 빈 생성 단계&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;자동 프록시 생성기는 Pointcut을 사용해 해당 빈이 프록시를 생성할 필요가 있는지/없는지 체크&lt;/u&gt;함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클래스 + 메서드 조건을 모두 비교함&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 메서드를 체크하기 위해 Pointcut 조건에 하나하나 매칭해봄&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조건에 부합하는 것이 하나라도 있으면 프록시를 생성함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 조건에 부합하는 것이 하나도 없으면 프록시를 생성할 필요가 없으므로 프록시를 생성하지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Advice 적용 여부 판단 - 사용 단계&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;프록시가 호출되었을 때 부가 기능인 Advice를 적용할지 말지&lt;/u&gt; Pointcut을 보고 판단함&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex) 대상 빈 : 프록시 적용된 orderController, pointcut : request*로 시작하는 메서드&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;orderController의 request() : 현재 포인트컷 조건에 만족하므로 프록시는 Advice를 먼저 호출하고, 이후 target을 호출함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;orderController의 noLog() : 현재 포인트컷 조건에 만족하지 않으므로 Advice를 호출하지 않고 바로 target만 호출함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고)&lt;/span&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시를 모든 곳에 생성하는 것은 비용 낭비이므로, &lt;u&gt;꼭 필요한 곳에 최소한의 프록시를 적용해야&lt;/u&gt; 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자동 프록시 생성기는 모든 스프링 빈에 프록시 빈을 적용하지 않고 Pointcut으로 한번 필터링해서 Advisor가 사용될 가능성이 있는 곳에만 프록시를 생성함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;span style=&quot;color: #000000;&quot;&gt;하나의 프록시, 여러 Advisor 적용&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Q. 스프링 빈으로 등록된 advisor1, advisor2가 제공하는 포인트컷의 조건을 모두 만족하면 스프링의 프록시 자동 생성기는 프록시를 몇 개 생성할까?&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 자동 생성기는 &lt;b&gt;프록시를 '하나만' 생성&lt;/b&gt;함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 팩토리가 생성하는 &lt;b&gt;프록시는 내부에 여러 advisor들을 포함할 수 있기 때문에&lt;/b&gt; 프록시를 여러 개 생성해서 비용을 낭비할 이유가 없음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 자동 생성기 상황별 정리&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v2Shi/btsNtj6Kk87/7nMEVmmNKqbvi3LU1UHrS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v2Shi/btsNtj6Kk87/7nMEVmmNKqbvi3LU1UHrS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v2Shi/btsNtj6Kk87/7nMEVmmNKqbvi3LU1UHrS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv2Shi%2FbtsNtj6Kk87%2F7nMEVmmNKqbvi3LU1UHrS0%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;400&quot; height=&quot;183&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;advisor1의 포인트컷만 만족 : 프록시 1개 생성, 프록시에 advisor1만 포함&lt;/li&gt;
&lt;li&gt;advisor1, advisor2의 포인트컷을 모두 만족 : 프록시 1개 생성, 프록시에 advisor1과 advisor2 모두 포함&lt;/li&gt;
&lt;li&gt;advisor1, advisor2의 포인트컷을 모두 만족하지 않음 : 프록시가 생성되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7JeO/btsNtrDqh0y/MWbQZAEuDoVU94E2R1Uuw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7JeO/btsNtrDqh0y/MWbQZAEuDoVU94E2R1Uuw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7JeO/btsNtrDqh0y/MWbQZAEuDoVU94E2R1Uuw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7JeO%2FbtsNtrDqh0y%2FMWbQZAEuDoVU94E2R1Uuw1%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;300&quot; height=&quot;190&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;784&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;h2 id=&quot;참고&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; 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&quot;&gt;스프링 핵심 원리 - 고급편&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Spring</category>
      <author>sw_develop</author>
      <guid isPermaLink="true">https://fordevelop.tistory.com/300</guid>
      <comments>https://fordevelop.tistory.com/300#entry300comment</comments>
      <pubDate>Mon, 21 Apr 2025 23:54:09 +0900</pubDate>
    </item>
  </channel>
</rss>