<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>오봉이와 함께하는 개발 블로그</title>
    <link>https://5bong2-develop.tistory.com/</link>
    <description>오봉이는 키우는 강아지 이름입니다.
좋은 개발자가 되고 싶습니다!</description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 06:09:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>오봉봉이</managingEditor>
    <image>
      <title>오봉이와 함께하는 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5024118/attach/3b0e88a35d754e56a6c29a857c795976</url>
      <link>https://5bong2-develop.tistory.com</link>
    </image>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; AOP 소개 애스팩트</title>
      <link>https://5bong2-develop.tistory.com/587</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; AOP 소개 애스팩트&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;핵심 기능과 부가 기능을 분리&lt;/strong&gt;&lt;br&gt;누군가는 이러한 부가 기능 도입의 문제점들을 해결하기 위해 오랜기간 고민해왔다.&lt;br&gt;그 결과 부가 기능을 핵심 기능에서 분리하고 한 곳에서 관리하도록 했다. 그리고 해당 부가 기능을 어디에 적용할지 선 택하는 기능도 만들었다.&lt;br&gt;이렇게 부가 기능과 부가 기능을 어디에 적용할지 선택하는 기능을 합해서 하나의 모듈로 만들었는데 이것이 바로 애스펙트(aspect)이다. 애스펙트는 쉽게 이야기해서 부가 기능과, 해당 부가 기능을 어디에 적용할 지 정의한 것이다. 예를 들어서 로그 출력 기능을 모든 컨트롤러에 적용해라 라는 것이 정의되어 있다.&lt;/p&gt;
&lt;p&gt;이전에 공부했던 &lt;code&gt;@Aspect&lt;/code&gt;가 바로 그것이다. 그리고 스프링이 제공하는 어드바이저도 어드바이스(부가 기능)과 포인트컷(적용 대상)을 가지고 있어서 개념상 하나의 애스팩트이다.&lt;/p&gt;
&lt;p&gt;애스팩트는 우리 말로 해석하면 관점이라는 뜻인데, 이름 그대로 어플리케이션을 바라보는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting concerns) 관점으로 달리 보는 것이다.&lt;/p&gt;
&lt;p&gt;이렇게 애스팩트를 사용한 프로그래밍 방식을 &lt;strong&gt;관점 지향 프로그래밍(AOP - Aspect Oriented Programming)&lt;/strong&gt;이라 한다.&lt;/p&gt;
&lt;p&gt;AOP는 OOP를 대체하기 위한 것이 아니라 횡단 관심사를 깔끔하게 처리하기 어려운 OOP의 부족한 부분을 보조하는 목적으로 개발되었다.&lt;/p&gt;
&lt;img width=&quot;761&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/681a2b98-99e2-41fd-8106-9a5cfbfabf24&quot;&gt;

&lt;p&gt;&lt;strong&gt;AspectJ 프레임워크&lt;/strong&gt;&lt;br&gt;AOP의 대표적인 구현으로 AspectJ 프레임워크(&lt;a href=&quot;https://www.eclipse.org/aspectj/)%EA%B0%80&quot;&gt;https://www.eclipse.org/aspectj/)가&lt;/a&gt; 있다.&lt;br&gt;스프링도 AOP를 지원하지만 대부분 AspectJ의 문법을 차용하고, AspectJ가 제공하는 기능의 일부만 제공한다.&lt;/p&gt;
&lt;p&gt;AspectJ 프레임워크는 스스로를 다음과 같이 설명한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바 프로그래밍 언어에 대한 완벽한 관점 지향 확장 횡단 관심사의 깔끔한 모듈화&lt;ul&gt;
&lt;li&gt;오류 검사 및 처리&lt;/li&gt;
&lt;li&gt;동기화&lt;/li&gt;
&lt;li&gt;성능 최적화(캐싱)&lt;/li&gt;
&lt;li&gt;모니터링 및 로깅&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/587</guid>
      <comments>https://5bong2-develop.tistory.com/587#entry587comment</comments>
      <pubDate>Sun, 3 Nov 2024 00:20:20 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; AOP 소개 핵심 기능과 부가 기능</title>
      <link>https://5bong2-develop.tistory.com/586</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; AOP 소개 핵심 기능과 부가 기능&lt;/h1&gt;
&lt;p&gt;애플리케이션 로직은 크게 &lt;strong&gt;핵심 기능&lt;/strong&gt;과 &lt;strong&gt;부가 기능&lt;/strong&gt;으로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 기능&lt;/strong&gt;은 해당 객체가 제공하는 고유의 기능이다. 예를 들어서 &lt;code&gt;OrderService&lt;/code&gt;의 핵심 기능은 주문 로직이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;부가 기능&lt;/strong&gt;은 핵심 기능을 보조하기 위해 제공되는 기능이다. 예를 들어서 로그 추적 로직, 트랜잭션 기능이 있다.&lt;br&gt;이러한 부가 기능은 단독으로 사용되지 않고, 핵심 기능과 함께 사용된다. 예를 들어 로그 추적 기능은 어떤 핵심 기능이 호출되었는지 로그를 남기기 위해 사용한다. 그러니까 부가 기능은 이름 그대로 핵심 기능을 보조하기 위해 존재한다.&lt;/p&gt;
&lt;img width=&quot;762&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/abafb0cb-bc3f-4c3c-8fd2-4b1ddff27263&quot;&gt;

&lt;img width=&quot;757&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/84b7b839-290a-4e23-9447-b1894935646b&quot;&gt;

&lt;p&gt;주문 로직을 실행하기 직전 로그 추적 기능을 사용해야 하면, 핵심 기능인 주문 로직 기능과 부가 기능인 로그 추적 기능이 하나의 객체 안에 섞이게 된다. 부가 기능이 필요한 경우 이렇게 둘을 합해서 하나의 로직을 완성한다. 이제 주문 서비스를 실행하면 핵심 기능인 주문 로직과 부가 기능인 로그 추적 기능이 함께 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;여기서 핵심 기능 비즈니스 로직에 부가 기능을 넣어버리면 OOP를 달성할 수 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;img width=&quot;762&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/e4ff3617-b0c6-4dcb-ad10-03d7ad69ab09&quot;&gt;

&lt;p&gt;보통 부가 기능은 여러 클래스에 걸쳐서 함께 사용된다. 예를 들어서 모든 애플리케이션 호출을 로깅 해야 하는 요구사항을 생각해보자.&lt;br&gt;이러한 부가 기능은 횡단 관심사(cross-cutting concerns)가 된다. 쉽게 이야기해서 하나의 부가 기능이 여러 곳에 동일하게 사용된다는 뜻이다.&lt;/p&gt;
&lt;h3&gt;부가 기능 적용 문제&lt;/h3&gt;
&lt;p&gt;그런데 이런 부가 기능을 여러 곳에 적응하려면 너무 번거롭다. 예를 들어 부가 기능을 적용할 클래스가 100개가 되는 경우 100개에 모두 동일한 코드를 추가해야 한다.&lt;br&gt;부가 기능을 별도의 유틸리티 클래스로 만든다고 해도, 해당 클래스를 호출하는 코드가 결국 필요하다. 만약 부가 기능이 단순 호출이 아니라 &lt;code&gt;try-catch-finally&lt;/code&gt; 같은 구조가 필요하다면 더욱 복잡해진다.&lt;/p&gt;
&lt;p&gt;더 큰 문제는 수정이다. 만약 부가 기능 수정이 발생하면, 100개 클래스 모두 하나씩 찾아가며 수정해야 한다.&lt;br&gt;여기에 추가로 부가 기능이 적용되는 위치를 변경한다면 어떻게 될까?&lt;br&gt;예를 들어서 부가 기능을 모든 컨트롤러, 서비스, 리포지토리에 적용했다가, 로그가 너무 많이 남아서 서비스 계층에만 적용한다고 수정해야하면 어떻게 될까? 또 수 많은 코드를 고쳐야 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;부가 기능 적용 문제를 정리하면 다음과 같다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;부가 기능을 적용할 때 아주 많은 반복이 필요하다.&lt;/li&gt;
&lt;li&gt;부가 기능이 여러 곳에 퍼져서 중복 코드를 만들어낸다.&lt;/li&gt;
&lt;li&gt;부가 기능을 변경할 때 중복 때문에 많은 수정이 필요하다.&lt;/li&gt;
&lt;li&gt;부가 기능의 적용 대상을 변경할 때 많은 수정이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;소프트웨어 개발에서 변경 지점은 하나가 될 수 있도록 잘 모듈화 되어야 한다. 그런데 부가 기능처럼 특정 로직을 어플 리케이션 전반에 적용하는 문제는 일반적인 OOP 방식으로는 해결이 어렵다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/586</guid>
      <comments>https://5bong2-develop.tistory.com/586#entry586comment</comments>
      <pubDate>Sun, 3 Nov 2024 00:12:29 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; @Aspect 프록시 설명 , 정리</title>
      <link>https://5bong2-develop.tistory.com/585</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; @Aspect 프록시 - 설명&lt;/h1&gt;
&lt;p&gt;앞서 자동 프록시 생성기를 학습할 때, 자동 프록시 생성기(&lt;code&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt;)는 &lt;code&gt;Advisor&lt;/code&gt;를 자동으로 찾아와서 필요한 곳에 프록시를 생성하고 적용해준다고 했다.&lt;br&gt;자동 프록시 생성기는 여기에 추가로 하나의 역할을 더 하는데, 바로 &lt;code&gt;@Aspect&lt;/code&gt;를 찾아서 이것을 &lt;code&gt;Advisor&lt;/code&gt;로 만들어준다.&lt;br&gt;쉽게 이야기해서 지금까지 학습한 기능에 더해 &lt;code&gt;@Aspect&lt;/code&gt;를 &lt;code&gt;Advisor&lt;/code&gt;로 변환해서 저장하는 기능도 한다.&lt;br&gt;그래서 이름 앞에 &lt;code&gt;AnnotationAware&lt;/code&gt;(어노테이션을 인식하는)가 붙어 있는 것이다.&lt;/p&gt;
&lt;img width=&quot;747&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/3ecdde1e-7348-4476-b012-cb2719e101e2&quot;&gt;

&lt;p&gt;&lt;strong&gt;자동 프록시 생성기는 2가지 일을 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@Aspect&lt;/code&gt;를 보고 어드바이저(&lt;code&gt;Advisor&lt;/code&gt;)로 변환해서 저장한다.&lt;/li&gt;
&lt;li&gt;어드바이저를 기반으로 프록시를 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;code&gt;@Aspect&lt;/code&gt;를 어드바이저로 변환해서 저장하는 과정&lt;/h3&gt;
&lt;img width=&quot;762&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/64e98e02-9dc4-4410-aa6f-d3439b438be5&quot;&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;실행&lt;/strong&gt;: 스프링 어플리케이션 로딩 시점에 자동 프록시 생성기를 호출 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모든 @Aspect 빈 조회&lt;/strong&gt;: 자동 프록시 생성기는 스프링 컨테이너에서 &lt;code&gt;@Aspect&lt;/code&gt; 어노테이션이 붙은 스프링 빈을 모두 조회한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어드바이저 생성&lt;/strong&gt;: &lt;code&gt;@Aspect&lt;/code&gt;어드바이저 필터를 통해 &lt;code&gt;@Aspect&lt;/code&gt; 어노테이션 정보를 기반으로 어드바이저를 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@Aspect 기반 어드바이저 저장&lt;/strong&gt;: 생성한 어드바이저를 @Aspect 어드바이저 빌더 내부에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;@Aspect 어드바이저 빌더&lt;/strong&gt;&lt;br&gt;&lt;code&gt;BeanFactoryAspectJAdvisorsBuilder&lt;/code&gt; 클래스이다.&lt;br&gt;&lt;code&gt;@Aspect&lt;/code&gt;의 정보를 기반으로 포인트컷, 어드바이스, 어드바이저를 생성하고 보관하는 것을 담당한다.&lt;br&gt;&lt;code&gt;@Aspect&lt;/code&gt;의 정보를 기반으로 어드바이저를 만들고, &lt;code&gt;@Aspect&lt;/code&gt; 어드바이저 빌더 내부 저장소에 캐시한다.&lt;br&gt;캐시에 어드바이저가 이미 만들어져 있는 경우 캐시에 저장된 어드바이저를 반환한다.&lt;/p&gt;
&lt;h3&gt;어드바이저를 기반으로 프록시 생성&lt;/h3&gt;
&lt;img width=&quot;761&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/25eabe0a-13d7-428b-88c5-bd8d332089a4&quot;&gt;

&lt;p&gt;&lt;strong&gt;1. 생성:&lt;/strong&gt; 스프링 빈 대상이 되는 객체를 생성한다. (&lt;code&gt;@Bean&lt;/code&gt;, 컴포넌트 스캔 모두 포함)&lt;br&gt;&lt;strong&gt;2. 전달:&lt;/strong&gt; 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.&lt;br&gt;&lt;strong&gt;3-1. Advisor 빈 조회:&lt;/strong&gt; 스프링 컨테이너에서 &lt;code&gt;Advisor&lt;/code&gt; 빈을 모두 조회한다.&lt;br&gt;&lt;strong&gt;3-2. @Aspect Advisor 조회:&lt;/strong&gt; &lt;code&gt;@Aspect&lt;/code&gt; 어드바이저 빌더 내부에 저장된 &lt;code&gt;Advisor&lt;/code&gt;를 모두 조회한다.&lt;br&gt;&lt;strong&gt;4. 프록시 적용 대상 체크:&lt;/strong&gt; 앞서 3-1, 3-2에서 조회한 &lt;code&gt;Advisor&lt;/code&gt;에 포함되어 있는 포인트컷을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 포인트컷에 하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용 대상이 된다. 예를 들어서 메서드 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.&lt;br&gt;&lt;strong&gt;5. 프록시 생성:&lt;/strong&gt; 프록시 적용 대상이면 프록시를 생성하고 프록시를 반환한다. 그래서 프록시를 스프링 빈으로 등 록한다. 만약 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록한다.&lt;br&gt;&lt;strong&gt;6. 빈 등록:&lt;/strong&gt; 반환된 객체는 스프링 빈으로 등록된다.&lt;/p&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;@Aspect&lt;/code&gt;를 사용해서 어노테이션 기반 프록시를 매우 편리하게 적용해보았다. 실무에서 프록시를 적용할 때는 대부분 이 방식을 사용한다.&lt;br&gt;지금까지 우리가 진행한 어플리케이션 전반에 로그를 남기는 기능은 특정 기능 하나에 관심이 있는 기능이 아니고, 어플리케이션의 여러 기능들 사이에 걸쳐서 들어가는 관심사이다.&lt;/p&gt;
&lt;p&gt;이것을 바로 &lt;strong&gt;횡단 관심사(cross-cutting concerns)&lt;/strong&gt;라고 한다. 우리가 지금까지 진행한 방법이 이렇게 여러곳에 걸쳐 있는 횡단 관심사의 문제를 해결하는 방법이었다.&lt;/p&gt;
&lt;img width=&quot;766&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/c25422c0-bfc7-4c70-9962-d60371a93dff&quot;&gt;


&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/585</guid>
      <comments>https://5bong2-develop.tistory.com/585#entry585comment</comments>
      <pubDate>Sat, 2 Nov 2024 23:12:52 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; @Aspect 프록시 적용</title>
      <link>https://5bong2-develop.tistory.com/584</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; @Aspect 프록시 - 적용&lt;/h1&gt;
&lt;p&gt;스프링 애플리케이션에 프록시를 적용하려면 포인트컷과 어드바이스로 구성된 어드바이저를 만들어서 스프링 빈으로 등록하면 된다.&lt;br&gt;그럼 나머지는 자동 프록시 생성기가 처리해준다. 자동 프록시 생성기는 스프링 빈으로 등록된 어드바이저들을 찾고, 스프링 빈들에 자동으로 프록시를 적용해준다. (당연히 포인트컷에 매치가 되어야 한다.)&lt;/p&gt;
&lt;p&gt;스프링은 &lt;code&gt;@Aspect&lt;/code&gt; 애노테이션으로 매우 편리하게 포인트컷과 어드바이스로 구성되어 있는 어드바이저 생성 기능을 지원한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; &lt;code&gt;@Aspect&lt;/code&gt;는 관점 지향 프로그래밍(AOP)을 가능하게 하는 AspectJ 프로젝트에서 제공하는 어노테이션이다. 스프링은 이것을 차용해서 프록시를 통한 AOP를 가능하게 한다. AOP와 AspectJ 관련된 자세한 내용은 다음에 설명한다. 지금은 프록시에 초점을 맞추자. 우선 이 어노테이션을 사용해서 스프링이 편리하게 프록시를 만들어준다고 생각하면 된다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Aspect
public class LogTraceAspect {
  private final LogTrace logTrace;

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

  @Around(&amp;quot;execution(* hello.proxy.app..*(..))&amp;quot;) //포인트 컷
  public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { // 어드바이스
    TraceStatus status = null;

    log.info(&amp;quot;target = {}&amp;quot;, joinPoint.getTarget()); // 실제 호출 대상
    log.info(&amp;quot;getArgs = {}&amp;quot;, joinPoint.getArgs()); // 전달 인자
    log.info(&amp;quot;getSignature = {}&amp;quot;, joinPoint.getSignature()); // join point 시그니처

    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&gt;
&lt;li&gt;&lt;code&gt;@Aspect&lt;/code&gt;: 애노테이션 기반 프록시를 적용할 때 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Around(&amp;quot;execution(* hello.proxy.app..*(..))&amp;quot;)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Around&lt;/code&gt;의 값에 포인트컷 표현식을 넣는다. 표현식은 AspectJ 표현식을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Around&lt;/code&gt;의 메서드는 어드바이스(&lt;code&gt;Advice&lt;/code&gt;)가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProceedingJoinPoint joinPoint&lt;/code&gt;: 어드바이스에서 살펴본 &lt;code&gt;MethodInvocation invocation&lt;/code&gt; 과 유사한 기능이다. 내부에 실제 호출 대상, 전달 인자, 그리고 어떤 객체와 어떤 메서드가 호출되었는지 정보가 포함되어 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;joinPoint.proceed()&lt;/code&gt;: 실제 호출 대상(&lt;code&gt;target&lt;/code&gt;)을 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
@Import({AppConfigV1.class, AppConfigV2.class})
public class AopConfig {

  @Bean
  public LogTraceAspect logTraceAspect(LogTrace logTrace) {
    return new LogTraceAspect(logTrace);
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Import({AppV1Config.class, AppV2Config.class})&lt;/code&gt;: V1, V2 애플리케이션은 수동으로 스프링 빈으로 등록해야 동작한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Bean logTraceAspect()&lt;/code&gt;: &lt;code&gt;@Aspect&lt;/code&gt; 가 있어도 스프링 빈으로 등록을 해줘야 한다. 물론 &lt;code&gt;LogTraceAspect&lt;/code&gt;에 &lt;code&gt;@Component&lt;/code&gt; 애노테이션을 붙여서 컴포넌트 스캔을 사용해서 스프링 빈으로 등록해도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(AopConfig.class)
@SpringBootApplication(scanBasePackages = &amp;quot;hello.proxy.app&amp;quot;) //주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;작동을 시키면 로그를 확인할 수 있다.&lt;br&gt;실제 로그 내용은 아래 형상과 다르나, 가독성을 위해 임의로 순서를 변경했다.&lt;br&gt;로그로 찍은 target, args, signature의 내용을 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;target = hello.proxy.app.v1.OrderControllerV1Impl@47c9f6cb
getArgs = hello
getSignature = String hello.proxy.app.v1.OrderControllerV1Impl.request(String)

target = hello.proxy.app.v1.OrderServiceV1Impl@1528d6a4
getArgs = hello
getSignature = void hello.proxy.app.v1.OrderServiceV1Impl.orderItem(String)

target = hello.proxy.app.v1.OrderRepositoryV1Impl@35da47df
getArgs = hello
getSignature = void hello.proxy.app.v1.OrderRepositoryV1Impl.save(String)

[94890137] OrderControllerV1Impl.request(..)
[94890137] |--&amp;gt;OrderServiceV1Impl.orderItem(..)
[94890137] |   |--&amp;gt;OrderRepositoryV1Impl.save(..)
[94890137] |   |&amp;lt;--OrderRepositoryV1Impl.save(..) time=1006ms
[94890137] |&amp;lt;--OrderServiceV1Impl.orderItem(..) time=1008ms
[94890137] OrderControllerV1Impl.request(..) time=1010ms&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/584</guid>
      <comments>https://5bong2-develop.tistory.com/584#entry584comment</comments>
      <pubDate>Sat, 2 Nov 2024 00:47:41 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; 하나의 프록시 여러 Advisor 적용, 정리</title>
      <link>https://5bong2-develop.tistory.com/583</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 하나의 프록시, 여러 Advisor 적용&lt;/h1&gt;
&lt;p&gt;스프링 빈이 &lt;code&gt;advisor1&lt;/code&gt;, &lt;code&gt;advisor2&lt;/code&gt;가 제공하는 포인트컷의 조건을 모두 만족하면 프록시 자동 생성기는 프록시를 몇 개 생성할까?&lt;br&gt;프록시 자동 생성기는 프록시를 하나만 생선한다. 왜냐면 프록시 팩토리가 생성하는 프록시는 내부에 여러 &lt;code&gt;advisor&lt;/code&gt;를 포함할 수 있기 때문이다.&lt;br&gt;따라서 프록시를 여러 개 생성하여 비용을 낭비할 이유가 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;프록시 자동 생성기 상황별 정리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;advisor1&lt;/code&gt; 의 포인트컷만 만족 프록시1개 생성, 프록시에 &lt;code&gt;advisor1&lt;/code&gt; 만 포함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;advisor1&lt;/code&gt; , &lt;code&gt;advisor2&lt;/code&gt; 의 포인트컷을 모두 만족 프록시1개 생성, 프록시에 &lt;code&gt;advisor1&lt;/code&gt; , &lt;code&gt;advisor2&lt;/code&gt; 모두 포함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;advisor1&lt;/code&gt; , &lt;code&gt;advisor2&lt;/code&gt; 의 포인트컷을 모두 만족하지 않음 프록시가 생성되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇다면 아래 경우는 어떻게 될까&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; 객체에 &lt;code&gt;Advisor1&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt; 객체에 &lt;code&gt;Advisor2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; 객체에 &lt;code&gt;Advisor1&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt; 객체에 &lt;code&gt;Advisor1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 상황은 서로 다른 객체이기 때문에 프록시가 각각 생성된다. 기본적으로 스프링 프록시 전략은 한 객체(스프링 Bean)에 대해서만 하나의 프록시 객체를 생성하기 때문이다. 주의하도록 하자.&lt;/p&gt;
&lt;img width=&quot;760&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/de76510c-37c5-45fb-a972-561bfb701d98&quot;&gt;

&lt;img width=&quot;764&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/2a177a7c-f6a4-4d3c-8d9d-2d1f7870cffe&quot;&gt;

&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;자동 프록시 생성기인 &lt;code&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt; 덕분에 개발자는 매우 편리하게 프록시를 적용할 수 있다.&lt;br&gt;이제 &lt;code&gt;Advisor&lt;/code&gt; 만 스프링 빈으로 등록하면 된다.&lt;br&gt;&lt;code&gt;Advisor&lt;/code&gt; = &lt;code&gt;Pointcut&lt;/code&gt; + &lt;code&gt;Advice&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/583</guid>
      <comments>https://5bong2-develop.tistory.com/583#entry583comment</comments>
      <pubDate>Sat, 2 Nov 2024 00:21:44 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 고급편 - 스프링이 제공하는 빈 후처리기 1, 2</title>
      <link>https://5bong2-develop.tistory.com/582</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 스프링이 제공하는 빈 후처리기 1&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;build.gradle - 추가&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;implementation &amp;#39;org.springframework.boot:spring-boot-starter-aop&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 라이브러리를 추가하면 &lt;code&gt;aspectjweaver&lt;/code&gt;라는 &lt;code&gt;aspectJ&lt;/code&gt;관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록한다.&lt;br&gt;스프링 부트가 없던 시절에는 &lt;code&gt;@EnableAspectJAutoProxy&lt;/code&gt;를 직접 사용해야 했는데, 이 부분을 스프링 부트가 자동으로 처리해준다.&lt;br&gt;&lt;code&gt;aspectJ&lt;/code&gt; 는 뒤에서 설명하고, 스프링 부트가 활성화하는 빈은 &lt;code&gt;AopAutoConfiguration&lt;/code&gt; 를 참고하자.&lt;/p&gt;
&lt;h3&gt;자동 프록시 생성기 - AutoProxyCreator&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;앞서 이야기한 스프링 부트 자동 설정으로 &lt;code&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt;라는 빈 후처리기가 스프링 빈에 자동으로 등록된다.&lt;/li&gt;
&lt;li&gt;이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기이다.&lt;/li&gt;
&lt;li&gt;이 빈 후처리기는 스프링 빈으로 등록된 &lt;code&gt;Advisor&lt;/code&gt; 들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시 를 적용해준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Advisor&lt;/code&gt; 안에는 &lt;code&gt;Pointcut&lt;/code&gt; 과 &lt;code&gt;Advice&lt;/code&gt; 가 이미 모두 포함되어 있다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;Advisor&lt;/code&gt; 만 알고 있으면 그 안에 있는 &lt;code&gt;Pointcut&lt;/code&gt; 으로 어떤 스프링 빈에 프록시를 적용해야 할지 알 수 있고 &lt;code&gt;Advice&lt;/code&gt; 로 부가 기능을 적용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고로 &lt;code&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt;는 &lt;code&gt;@AspectJ&lt;/code&gt;와 관련된 AOP 기능도 자동으로 찾아서&lt;br&gt;처리해준다.&lt;br&gt;&lt;code&gt;Advisor&lt;/code&gt; 는 물론이고, &lt;code&gt;@Aspect&lt;/code&gt; 도 자동으로 인식해서 프록시를 만들고 AOP를 적용해준다.&lt;/p&gt;
&lt;img width=&quot;763&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/c132c715-b175-434a-aa73-611c07686514&quot;&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1. 생성:&lt;/strong&gt; 스프링이 스프링 빈 대상이 되는 객체를 생성한다. (&lt;code&gt;@Bean&lt;/code&gt;, 컴포넌트 스캔 모두 포함)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2. 전달:&lt;/strong&gt; 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3. 모든 Advisor 빈 조회:&lt;/strong&gt; 자동 프록시 생성기 - 빈 후처리기는 스프링 컨테이너에서 모든 &lt;code&gt;Advisor&lt;/code&gt; 를 조회한다. 때문에 Advisor는 스프링 빈으로 등록되어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4. 프록시 적용 대상 체크:&lt;/strong&gt; 앞서 조회한 &lt;code&gt;Advisor&lt;/code&gt; 에 포함되어 있는 포인트컷을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 포인트컷에 하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용 대상이 된다. 예를 들어서 10개의 메서드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5. 프록시 생성:&lt;/strong&gt; 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록한다. 만약 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6. 빈 등록:&lt;/strong&gt; 반환된 객체는 스프링 빈으로 등록된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;774&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/f574f225-61b0-487a-a64b-0af4a922962b&quot;&gt;

&lt;p&gt;프록시는 내부에 어드바이저와 실제 호출해야할 대상 객체(target)을 알고 있다.&lt;br&gt;코드를 통해 바로 적용해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
@Import({AppConfigV1.class, AppConfigV2.class})
public class AutoProxyConfig {

  @Bean
  public Advisor advisor1(LogTrace logTrace) {
    //pointcut
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedNames(&amp;quot;request*&amp;quot;, &amp;quot;order*&amp;quot;, &amp;quot;save*&amp;quot;);
    //advice
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    //advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AutoProxyConfig&lt;/code&gt;코드를 보면 &lt;code&gt;advisor1&lt;/code&gt;이라는 어드바이저 하나만 등록했다.&lt;/li&gt;
&lt;li&gt;빈 후처리기는 이제 등록하지 않아도 된다. 스프링은 자동 프록시 생성기라는(&lt;code&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt;) 빈 후처리기를 자동으로 등록해준다.&lt;/li&gt;
&lt;li&gt;이전 코드와의 차이를 보면 &lt;code&gt;advisor&lt;/code&gt;가 스프링 빈으로 등록이 되었다 &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public Advisor advisor1(...) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(AutoProxyConfig.class)
@SpringBootApplication(scanBasePackages = &amp;quot;hello.proxy.app&amp;quot;) //주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v1/request?itemId=hello&quot;&gt;http://localhost:8080/v1/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v2/request?itemId=hello&quot;&gt;http://localhost:8080/v2/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v3/request?itemId=hello&quot;&gt;http://localhost:8080/v3/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;중요: 포인트컷은 두 가지에 사용된다.&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;프록시 적용 여부 판단&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;자동 프록시 생성기는 포인트컷을 사용해서 해당 빈이 프록시를 생성할 필요가 있는지 없는지 체크한다.&lt;/li&gt;
&lt;li&gt;클래스 + 메서드 조건을 모두 비교한다.&lt;/li&gt;
&lt;li&gt;이때 모든 메서드를 체크하는데, 포인트컷 조건 하나하나 매칭해본다.&lt;/li&gt;
&lt;li&gt;만약 조건에 맞는 것이 &lt;strong&gt;하나라도&lt;/strong&gt; 있다면 프록시를 생성한다.&lt;/li&gt;
&lt;li&gt;예를 들어 특정 &lt;code&gt;Controller&lt;/code&gt;에 &lt;code&gt;requestOrder()&lt;/code&gt;라는 메서드와 &lt;code&gt;responseOrder()&lt;/code&gt;라는 메서드가 있을 때 위 코드에서 포인트컷으로 &lt;code&gt;request*&lt;/code&gt;를 등록했기 때문에 &lt;code&gt;request&lt;/code&gt;가 있는 &lt;code&gt;Controller&lt;/code&gt;는 프록시를 생성, 등록한다.&lt;/li&gt;
&lt;li&gt;조건에 맞는 것이 하나도 없을 경우 프록시가 필요 없기 때문에 생성하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어드바이서 적용 여부 판단 - 사용 단계&lt;/strong&gt; &lt;ul&gt;
&lt;li&gt;프록시가 호출되었을 때 부가 기능인 어드바이스를 적용할지, 말지 포인트컷을 보고 판단한다.&lt;/li&gt;
&lt;li&gt;1번 예시에서 설명한 &lt;code&gt;Controller&lt;/code&gt;는 이미 프록시가 걸려있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Controller&lt;/code&gt;의 &lt;code&gt;requestOrder&lt;/code&gt;는 현재 포인트컷 조건에 만족하므로 프록시는 어드바이스를 먼저 호출하고 비즈니스 로직을 호출한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Controller&lt;/code&gt;의 &lt;code&gt;responseOrder&lt;/code&gt;는 현재 포인트컷 조건에 만족하지 않기 때문에 어드바이스를 호출하지 않고 비즈니스 로직을 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; 프록시를 모든 곳에 생성하는 것은 비용 낭비이다. 꼭 필요한 곳에 최소한의 프록시를 적용해야 한다. 그래서 자동 프록시 생성기는 모든 스프링 빈에 프록시를 적용하는 것이 아니라 포인트컷으로 한번 필터링해서 어드바이스가 사용될 가능성이 있는 곳에만 프록시를 생성한다.&lt;/p&gt;
&lt;h1&gt;스프링 핵심 원리 - 스프링이 제공하는 빈 후처리기 2&lt;/h1&gt;
&lt;p&gt;먼저 애플리케이션 로딩 로그를 보자&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[38b9c73e] EnableWebMvcConfiguration.requestMappingHandlerAdapter()
[38b9c73e] EnableWebMvcConfiguration.requestMappingHandlerAdapter() time=23ms
[f2ed13eb] EnableWebMvcConfiguration.requestMappingHandlerMapping()
[f2ed13eb] EnableWebMvcConfiguration.requestMappingHandlerMapping() time=3ms&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;애플리케이션 서버를 실행해보면, 스프링이 초기화 되면서 기대하지 않은 이러한 로그들이 올라온다.&lt;br&gt;그 이유는 지금 사용한 포인트컷이 단순히 메서드 이름에 &lt;code&gt;&amp;quot;request*&amp;quot;, &amp;quot;order*&amp;quot;, &amp;quot;save*&amp;quot;&lt;/code&gt;만 포함되어 있으면 매칭 된다고 판단하기 때문이다.&lt;br&gt;결국 스프링이 내부에서 사용하는 빈에도 메서드 이름에 &lt;code&gt;request&lt;/code&gt;라는 단어만 들어가 있으면 프록시가 만들어지고, 어드바이스도 적용되는 것이다.&lt;/p&gt;
&lt;p&gt;결론적으로 패키지에 메서드 이름까지 함께 지정할 수 있는 매우 정밀한 포인트컷이 필요하다.&lt;/p&gt;
&lt;h3&gt;AspectJExpressionPointcut&lt;/h3&gt;
&lt;p&gt;AspectJ라는 AOP에 특화된 포인트컷 표현식을 적용할 수 있다. AspectJ 포인트컷 표현식과 AOP는 나중에 자세하게 보고 지금은 그냥 이런 것이 있구나 정도만 생각하고 넘어가자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Bean
  public Advisor advisor2(LogTrace logTrace) {
    //pointcut
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression(&amp;quot;execution(* hello.proxy.app..*(..))&amp;quot;);
    //advice
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    //advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(advisor1의 @Bean은 주석 처리하자.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AspectJExpressionPointcut&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;AspectJ 포인트컷 표현식을 적용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execution(* hello.proxy.app..*(..))&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;AspectJ가 제공하는 포인트컷 표현식이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; : 모든 반환 타입&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hello.proxy.app..&lt;/code&gt; : 해당 패키지와 그 하위 패키지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*(..)&lt;/code&gt; : &lt;code&gt;*&lt;/code&gt; 모든 메서드 이름, &lt;code&gt;(..)&lt;/code&gt; 파라미터는 상관 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;쉽게 이야기해서 &lt;code&gt;hello.proxy.app&lt;/code&gt;패키지와 그 하위 패키지의 모든 메서드는 포인트컷의 매칭 대상이 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v1/request?itemId=hello&quot;&gt;http://localhost:8080/v1/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v2/request?itemId=hello&quot;&gt;http://localhost:8080/v2/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v3/request?itemId=hello&quot;&gt;http://localhost:8080/v3/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실행하면 동일한 결과가 나오는데, &lt;code&gt;http://localhost:8080/v1/no-log&lt;/code&gt;를 들어가도 로그가 찍힌다.&lt;br&gt;다시 &lt;code&gt;AspectJExpression&lt;/code&gt;을 작성해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  @Bean
  public Advisor advisor3(LogTrace logTrace) {
    //pointcut
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression(&amp;quot;execution(* hello.proxy.app..*(..)) &amp;amp;&amp;amp; !execution(* hello.proxy.app..noLog(..))&amp;quot;);
    //advice
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    //advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(advisor1, 2의 @Bean은 주석 처리하자.)&lt;br&gt;표현식을 다음과 같이 수정했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;execution(* hello.proxy.app..*(..)) &amp;amp;&amp;amp; !execution(* hello.proxy.app..noLog(..))&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;: 두 조건을 모두 만족해야 함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt;: 반대&lt;br&gt;&lt;code&gt;hello.proxy.app&lt;/code&gt;패키지와 하위 패키지의 모든 메서드는 포인트컷에 매칭하되, &lt;code&gt;noLog()&lt;/code&gt;메서드는 제외하라는 뜻이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다시 &lt;code&gt;http://localhost:8080/v1/no-log&lt;/code&gt;를 들어가보면 로그가 뜨지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/582</guid>
      <comments>https://5bong2-develop.tistory.com/582#entry582comment</comments>
      <pubDate>Tue, 8 Oct 2024 00:06:38 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 적용, 정리</title>
      <link>https://5bong2-develop.tistory.com/581</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 적용&lt;/h1&gt;
&lt;p&gt;빈 후처리기를 사용해서 실제 객체 대신 프록시를 스프링 빈으로 등록하자.&lt;br&gt;이렇게 하면 수동은 물론, 컴포넌트 스캔을 사용하는 빈까지 모두 프록시를 적용할 수 있다.&lt;br&gt;더 나아가서 설정 파일에 있는 수 많은 프록시 생성 코드도 한번에 제거할 수 있다.&lt;/p&gt;
&lt;img width=&quot;770&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/e8f71c20-6894-425e-8803-66adea4317f9&quot;&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
public class PackageLogTracePostProcessor implements BeanPostProcessor {
  private final String basePackage;
  private final Advisor advisor;

  public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
    this.basePackage = basePackage;
    this.advisor = advisor;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    log.info(&amp;quot;param Bean Name = {}, Bean = {}&amp;quot;, beanName, bean.getClass());

    // 프록시 적용 대상 여부 체크
    // 프록시 적용 대상이 아니면 원본을 그대로 반환
    String packageName = bean.getClass().getPackageName();
    if(!packageName.startsWith(basePackage)) {
      return bean;
    }

    // 프록시 대상이면 프록시를 만들어서 반환
    ProxyFactory proxyFactory = new ProxyFactory(bean);
    proxyFactory.addAdvisor(advisor);
    Object proxy = proxyFactory.getProxy();
    log.info(&amp;quot;create proxy: target = {}, proxy = {}&amp;quot;, bean.getClass(), proxy.getClass());
    return proxy;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PackageLogTraceProxyPostProcessor&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;원본 객체를 프록시 객체로 변환하는 역할&lt;/li&gt;
&lt;li&gt;프록시 객체로 변환 시 프록시 팩토리를 사용&lt;/li&gt;
&lt;li&gt;프록시 팩토리는 advisor가 필요하기 때문에 외부에서 생성자를 통해 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모든 스프링 빈들에 프록시를 적용할 필요는 없고, 특정 패키지와 그 하위에 위치한 스프링 빈들만 프록시를 적용&lt;ul&gt;
&lt;li&gt;여기서는 &lt;code&gt;hello.proxy.app&lt;/code&gt;과 관련된 부분에만 적용&lt;/li&gt;
&lt;li&gt;다른 패키지의 객체는 원본 그대로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시 적용 대상의 반환 값을 보면 원본 객체 대신 프록시 객체를 반환&lt;ul&gt;
&lt;li&gt;따라서 스프링 컨테이너에 원복 객체 대신 프록시 객체가 스프링 빈으로 등록&lt;/li&gt;
&lt;li&gt;원본 객체는 스프링 빈으로 등록되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
@Import({AppConfigV1.class, AppConfigV2.class})
public class BeanPostProcessorConfig {

  @Bean
  public PackageLogTracePostProcessor logTracePostProcessor(LogTrace logTrace) {
    return new PackageLogTracePostProcessor(&amp;quot;hello.proxy.app&amp;quot;, getAdvisor(logTrace));
  }

  private Advisor getAdvisor(LogTrace logTrace) {
    //pointcut
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedNames(&amp;quot;request*&amp;quot;, &amp;quot;order*&amp;quot;, &amp;quot;save*&amp;quot;);
    //advice
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    //advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Import({AppV1Config.class, AppV2Config.class})&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;V3는 컴포넌트 스캔으로 자동으로 스프링 빈으로 등록되지만, V1, V2 애플리케이션은 수동으로 스프링 빈으로 등록해야 동작&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProxyApplication&lt;/code&gt;에서 등록해도 되지만 편의상 여기에 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Bean logTraceProxyPostProcessor()&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;특정 패키지를 기준으로 프록시를 생성하는 빈 후처리기를 스프링 빈으로 등록&lt;/li&gt;
&lt;li&gt;빈 후처리기는 스프링 빈으로만 등록하면 자동으로 동작&lt;/li&gt;
&lt;li&gt;여기에 프록시를 적용할 패키지 정보(&lt;code&gt;hello.proxy.app&lt;/code&gt;)와 어드바이저(&lt;code&gt;getAdvisor(logTrace)&lt;/code&gt;)를 넘겨준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 &lt;strong&gt;프록시를 생성하는 코드가 설정 파일에는 필요 없다.&lt;/strong&gt; &lt;ul&gt;
&lt;li&gt;순수한 빈 등록만 고민하면 된다. &lt;/li&gt;
&lt;li&gt;프록시를 생성하고 프록시를 스프링 빈으로 등록하는 것은 빈 후처리기가 모두 처리해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(BeanPostProcessorConfig.class)
@SpringBootApplication(scanBasePackages = &amp;quot;hello.proxy.app&amp;quot;) //주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설정을 등록하고 실행하면 로그가 쭉~ 뜰텐데&lt;br&gt;중요한 부분만 남기고 하나씩 살펴보자.&lt;br&gt;실행 로그에 &lt;code&gt;create proxy&lt;/code&gt;를 검색으로 검색하면 V1 ~ V3까지 프록시가 생성된 로그를 확인할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V1:&lt;/strong&gt; 인터페이스가 있으므로 JDK 동적 프록시가 적용된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V2:&lt;/strong&gt; 구체 클래스만 있으므로 CGLIB 프록시가 적용된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V3:&lt;/strong&gt; 구체 클래스만 있으므로 CGLIB 프록시가 적용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;컴포넌트 스캔에도 적용&lt;/strong&gt;&lt;br&gt;여기서 중요한 포인트는 빈 후처리기 덕분에 v1, v2와 같이 수동으로 등록한 빈 뿐만 아니라 컴포넌트 스캔을 통해 등록한 v3 빈들도 프록시를 적용할 수 있다는 점이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v1/request?itemId=hello&quot;&gt;http://localhost:8080/v1/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v2/request?itemId=hello&quot;&gt;http://localhost:8080/v2/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/v3/request?itemId=hello&quot;&gt;http://localhost:8080/v3/request?itemId=hello&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;각 링크를 들어가서 로그를 보면 다 적용된 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;프록시 적용 대상 여부 체크&lt;/strong&gt;&lt;br&gt;애플리케이션을 실행해서 로그를 확인해보면 알겠지만, 우리가 직접 등록한 스프링 빈들 뿐만 아니라 스프링 부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기에 넘어온다.&lt;br&gt;그래서 어떤 빈을 프록시로 만들 것인지 기준이 필요하다.&lt;br&gt;여기서는 간단히 &lt;code&gt;basePackage&lt;/code&gt; 를 사용해서 특정 패키지를 기준으로 해당 패키지와 그 하위 패키지의 빈들을 프록시로 만든다.&lt;br&gt;스프링 부트가 기본으로 제공하는 빈 중에는 프록시 객체를 만들 수 없는 빈들도 있다. 따라서 모든 객체를 프록시 로만들 경우 오류가 발생한다.&lt;/p&gt;
&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 정리&lt;/h1&gt;
&lt;h3&gt;문제 1 - 너무 많은 설정&lt;/h3&gt;
&lt;p&gt;프록시를 직접 스프링 빈으로 등록하는 &lt;code&gt;ProxyFactoryConfigV1&lt;/code&gt;, &lt;code&gt;ProxyFactoryConfigV2&lt;/code&gt; 와 같은 설정 파일은 프록시 관련 설정이 지나치게 많다는 문제가 있다.&lt;br&gt;예를 들어 스프링 빈이 100개가 있다면 100개의 설정 코드가 들어가야 한다.... 난 자신이 없다.&lt;br&gt;스프링 빈을 편하게 등록하려고 컴포넌트 스캔을 사용하는데 설정 지옥에 빠지면 곤란하다.&lt;/p&gt;
&lt;h3&gt;문제 2 - 컴포넌트 스캔&lt;/h3&gt;
&lt;p&gt;애플리케이션 V3처럼 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능했다.&lt;br&gt;왜냐하면 컴포넌트 스캔으로 이미 스프링 컨테이너에 실제 객체를 스프링 빈으로 등록을 다 해버린 상태이기 때문이다.&lt;br&gt;좀 더 풀어서 설명하자면, 지금까지 학습한 방식으로 프록시를 적용하려면, 원본 객체를 스프링 컨테이너에 빈으로 등록 하는 것이 아니라 &lt;code&gt;ProxyFactoryConfigV1&lt;/code&gt;에서 한 것 처럼, 프록시를 원본 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다.&lt;br&gt;그런데 컴포넌트 스캔은 원본 객체를 스프링 빈으로 자동으로 등록하기 때문에 프록시 적용이 불가능하다.&lt;/p&gt;
&lt;h3&gt;문제 해결&lt;/h3&gt;
&lt;p&gt;빈 후처리기 덕분에 프록시를 생성하는 부분을 하나로 집중할 수 있다.&lt;br&gt;그리고 컴포넌트 스캔처럼 스프링이 직접 대상을 빈으로 등록하는 경우에도 중간에 빈 등록 과정을 가로채서 원본 대신에 프록시를 스프링 빈으로 등록할 수 있다.&lt;br&gt;덕분에 애플리케이션에 수 많은 스프링 빈이 추가되어도 프록시와 관련된 코드는 전혀 변경하지 않아도 된다.&lt;br&gt;그리고 컴포넌트 스캔을 사용해도 프록시가 모두 적용된다.&lt;/p&gt;
&lt;h3&gt;스프링 빈 후처리기&lt;/h3&gt;
&lt;p&gt;스프링은 프록시를 생성하기 위한 빈 후처리기를 이미 만들어서 제공한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;중요&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;프록시의 적용 대상 여부를 간단하게 패키지를 기준으로 설정했다.&lt;br&gt;그치만 잘 생각해보면 포인트컷을 사용하면 더 깔끔할 거 같다. 포인트컷은 이미 클래스, 메서드 단위 필터 기능을 가지고 있기 때문에, 프록시 적용 대상 여부를 정밀하게 설정할 수 있다.&lt;br&gt;참고로 어드바이저는 포인트컷을 가지고 있기 때문에, 어드바이저를 통해 포인트컷을 확인할 수 있다.&lt;br&gt;스프링 AOP는 포인트컷을 통해 프록시 적용 대상 여부를 체크한다.&lt;/p&gt;
&lt;p&gt;결과적으로 포인트컷은 다음 두 곳에 사용된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다. (빈 후처리기 - 자동 프록시 생성)&lt;/li&gt;
&lt;li&gt;프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할 지 판단한다. (프록시 내부)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/581</guid>
      <comments>https://5bong2-develop.tistory.com/581#entry581comment</comments>
      <pubDate>Mon, 7 Oct 2024 23:02:55 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 -  고급편 &amp;gt; 빈 후처리기 예제</title>
      <link>https://5bong2-develop.tistory.com/580</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 예제1&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 후처리기를 하기 전에 일반적인 스프링 빈 등록 과정을 코드로 작성해보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/325c711a-dd05-4c61-b5c5-29ab9e54a40b&quot; alt=&quot;image&quot; width=&quot;769&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Slf4j
public class BasicTest {

  @Test
  void basicConfig() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicConfig.class);

    A a = applicationContext.getBean(&quot;beanA&quot;, A.class);
    a.helloA();

    Assertions.assertThrows(
        NoSuchBeanDefinitionException.class, () -&amp;gt; applicationContext.getBean(&quot;beanB&quot;, A.class));
  }

  @Configuration
  static class BasicConfig {
    @Bean(name = &quot;beanA&quot;)
    public A a() {
      return new A();
    }
  }

  static class A {
    public void helloA() {
      log.info(&quot;hello A&quot;);
    }
  }

  static class B {
    public void helloB() {
      log.info(&quot;hello B&quot;);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 스프링 컨테이너를 생성하면서 BasicConfig.class를 넘겨주었고, BasicConfig.class은 스프링 빈으로 등록된다.&lt;br /&gt;BasicConfig를 통해 A 클래스를 Bean으로 등록했고, B는 등록한 적 없기 때문에 NoSuchBeanDefinitionException가 발생하는 테스트 코드가 성공하게 된다.&lt;/p&gt;
&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 예제2&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 빈 후처리기를 통해 A를 B 객체로 바꿔치기 해보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/288073e8-8fc0-4ac8-b3e3-964f2a745036&quot; alt=&quot;image&quot; width=&quot;770&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BeanPostProcessor 인터페이스 - 스프링 제공&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;&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;code&gt;BeanPostProcessor&lt;/code&gt;인터페이스를 구현하고, 스프링 빈으로 등록하면 된다.&lt;/li&gt;
&lt;li&gt;postProcessBeforeInitialization
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성 이후 &lt;code&gt;@PostConstruct&lt;/code&gt; 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;postProcessAfterInitialization
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성 이후에 &lt;code&gt;@PostConstruct&lt;/code&gt; 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@PostConstruct&lt;/code&gt; 같은 것은 스프링 빈 생명주기를 참고하자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
public class BeanPostProcessorTest {

  @Test
  void postProcessor() {
    ApplicationContext ap = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);
    B b = ap.getBean(&quot;beanA&quot;, B.class);
    b.helloB();

    Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -&amp;gt; ap.getBean(A.class));
  }

  @Configuration
  static class BeanPostProcessorConfig {
    @Bean(name = &quot;beanA&quot;)
    public A a() {
      return new A();
    }

    @Bean
    public AtoBPostProcessor helloPostProcessor() {
      return new AtoBPostProcessor();
    }
  }

  static class A {
    public void helloA() {
      log.info(&quot;hello A&quot;);
    }
  }

  static class B {
    public void helloB() {
      log.info(&quot;hello B&quot;);
    }
  }

  static class AtoBPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      log.info(&quot;beanName={} bean={}&quot;, beanName, bean);

      if (bean instanceof A) {
        return new B();
      }
      return bean;
    }
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 실행 결과는 아래 로그를 통해 확인할 수 있다.&lt;br /&gt;실행 결과를 보면 최종적으로 &lt;code&gt;beanA&lt;/code&gt;라는 스프링 빈 이름에 A 객체 대신 B 객체가 등록된 것을 확인할 수 있다.&lt;br /&gt;A 객체는 스프링 빈으로 등록조차 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 직접 찍은 로그만 확인해보면..&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;beanName=beanA bean=hello.proxy.postprocessor.BeanPostProcessorTest$A@3ed242a4
hello B&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 로그가 찍히는데, 만약 postProcessor를 적용해놓고 A로 받으면 어떻게 될까?&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;  @Test
  void postProcessor() {
    ApplicationContext ap = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);
    A a = ap.getBean(&quot;beanA&quot;, A.class);
    a.helloA();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;beanName=beanA bean=hello.proxy.postprocessor.BeanPostProcessorTest$A@3ed242a4

org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'beanA' is expected to be of type 'hello.proxy.postprocessor.BeanPostProcessorTest$A' but was actually of type 'hello.proxy.postprocessor.BeanPostProcessorTest$B'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 에러 로그가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 천천히 뜯어보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AtoBPostProcessor&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;/li&gt;
&lt;li&gt;인터페이스인 BeanPostProcessor를 구현하고, 스프링 빈으로 등록하면 스프링 컨테이너가 빈 후처리기로 인식하고 동작한다.&lt;/li&gt;
&lt;li&gt;이 빈 후처리기는 A 객체를 새로운 B 객체로 바꿔치기 한다.&lt;/li&gt;
&lt;li&gt;파라미터로 넘어오은 빈(bean) 객체가 A의 인스턴스면 새로운 B 객체를 생성하여 반환한다.&lt;/li&gt;
&lt;li&gt;여기서 A 대신 반환된 값인 B가 스프링 컨테이너에 등록된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;beanName=beanA bean=hello.proxy.postprocessor.BeanPostProcessorTest$A@3ed242a4&lt;/li&gt;
&lt;li&gt;위 로그를 보면 빈 후처리기에 A가 bean 이라는 파라미터로 넘어온 것을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 후처리기는 빈을 조작하고 변경할 수 있는 후킹 포인트다.&lt;br /&gt;빈 객체를 조작하거나 심지어 다른 객체로 바꿀 수 있을 정도로 강력한 기능이다.&lt;br /&gt;여기서 조작이라는 것은 해당 객체의 특정 메서드를 호출하는 것을 뜻한다.&lt;br /&gt;일반적으로 스프링 컨테이너가 등록하는, 특히 컴포넌트 스캔의 대상이 되는 빈들은 중간에 조작할 방법이 없는데, 빈 후처리기를 사용하면 개발자가 등록하는 모든 빈을 중간에 조작할 수 있다.&lt;br /&gt;이 말은 &lt;b&gt;빈 객체를 프록시로 교체&lt;/b&gt;하는 것도 가능하다는 뜻이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: @PostConstruct의 비밀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@PostConstruct&lt;/code&gt;는 스프링 빈 생성 이후에 빈을 초기화 하는 역할을 한다.&lt;br /&gt;그런데 생각해보면 빈의 초기화 라는 것이 단순히 &lt;code&gt;@PostConstruct&lt;/code&gt;애노테이션이 붙은 초기화 메서드를 한번 호출만 하면 된다. 쉽게 이야기해 서 생성된 빈을 한번 조작하는 것이다.&lt;br /&gt;따라서 빈을 조작하는 행위를 하는 적절한 빈 후처리기가 있으면 될 것 같다.&lt;br /&gt;스프링은 &lt;code&gt;CommonAnnotationBeanPostProcessor&lt;/code&gt; 라는 빈 후처리기를 자동으로 등록하는데, 여기에서 &lt;code&gt;@PostConstruct&lt;/code&gt;애노테이션이 붙은 메서드를 호출한다.&lt;br /&gt;따라서 스프링 스스로도 스프링 내부의 기능을 확장 하기 위해 빈 후처리기를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/b&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/580</guid>
      <comments>https://5bong2-develop.tistory.com/580#entry580comment</comments>
      <pubDate>Sun, 6 Oct 2024 01:49:02 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기 소개</title>
      <link>https://5bong2-develop.tistory.com/579</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 빈 후처리기&lt;/h1&gt;
&lt;img width=&quot;783&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/e5681c9b-f572-4922-b4f7-4ce8ee7c723f&quot;&gt;

&lt;p&gt;&lt;code&gt;@Bean&lt;/code&gt;이나 컴포넌트 스캔으로 스프링 빈을 등록하면 스프링은 대상 객체를 생성하고, 스프링 컨테이너 내부의 빈 저장소에 등록한다.&lt;br&gt;이후에 스프링 컨테이너를 통해 등록한 스프링 빈을 조회해서 사용하면 된다.&lt;/p&gt;
&lt;h3&gt;빈 후처리기 - BeanPostProcessor&lt;/h3&gt;
&lt;p&gt;스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장소에 등록하기 &lt;strong&gt;직전&lt;/strong&gt;에 조작하고 싶다면 빈 후처리기를 사용하면 된다.&lt;br&gt;빈 포스트 프로세서는 말 그대로 빈 후처리기 라는 뜻으로 빈을 생성한 후 무언가를 처리하는 용도로 사용된다.&lt;/p&gt;
&lt;h4&gt;빈 후처리기 기능&lt;/h4&gt;
&lt;p&gt;빈 후처리기의 기능은 막강한데, 객체를 조작할 수도 있고, 완전히 다른 객체로 바꿔치기 하는 것도 가능하다.&lt;/p&gt;
&lt;img width=&quot;767&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/ea92b41d-520e-4e4a-a5ce-d77fdd99d4c6&quot;&gt;

&lt;ol&gt;
&lt;li&gt;생성: 스프링 빈 대상이 되는 객체롤 생성한다 (@Bean, 컴포넌트 스캔 모두 포함)&lt;/li&gt;
&lt;li&gt;전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달&lt;/li&gt;
&lt;li&gt;후 처리 작업: 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기할 수 있다.&lt;/li&gt;
&lt;li&gt;등록: 빈 후처리기는 빈을 반환한다. 전달된 빈을 그대로 반환하면 해당 빈이 등록되고, 바꿔치기 하면 다른 객체가 등록된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;img width=&quot;770&quot; alt=&quot;image&quot; src=&quot;https://github.com/user-attachments/assets/641500ac-1227-46fc-afab-c6197702d016&quot;&gt;


&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/579</guid>
      <comments>https://5bong2-develop.tistory.com/579#entry579comment</comments>
      <pubDate>Sun, 6 Oct 2024 00:58:40 +0900</pubDate>
    </item>
    <item>
      <title>스프링 핵심 원리 - 고급편 &amp;gt; 프록시 팩토리 적용 1, 2, 정리</title>
      <link>https://5bong2-develop.tistory.com/578</link>
      <description>&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 프록시 팩토리 적용 1&lt;/h1&gt;
&lt;p&gt;지금까지 학습한 프록시 팩토리를 사용해서 애플리케이션에 프록시를 만들어보자.&lt;br&gt;먼저 인터페이스가 있는 v1 애플리케이션에 &lt;code&gt;LogTrace&lt;/code&gt;기능을 프록시 팩토리를 통해서 프록시를 만들어 적용해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class LogTraceAdvice implements MethodInterceptor {

  private final LogTrace logTrace;

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

  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    TraceStatus status = null;
    try {
      Method method = invocation.getMethod();
      String message = method.getDeclaringClass().getSimpleName() + &amp;quot;.&amp;quot; + method.getName() + &amp;quot;()&amp;quot;;
      status = logTrace.begin(message);
      Object result = invocation.proceed();
      logTrace.end(status);
      return result;
    } catch (Exception e) {
      logTrace.exception(status, e);
      throw e;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
public class ProxyFactoryConfigV1 {

  @Bean
  public OrderControllerV1 orderControllerV1(LogTrace logTrace) {
    OrderControllerV1Impl orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));

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

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

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


  @Bean
  public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace) {
    OrderRepositoryV1 orderRepository = new OrderRepositoryV1Impl();

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

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

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;포인트컷은 &lt;code&gt;NameMatchMethodPointcut&lt;/code&gt;을 사용한다.&lt;ul&gt;
&lt;li&gt;심플 매칭 기능이 있어 &lt;code&gt;*&lt;/code&gt;를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request*&lt;/code&gt;, &lt;code&gt;order*&lt;/code&gt;, &lt;code&gt;save*&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;request&lt;/code&gt;로 시작하는 메서드에 포인트컷은 &lt;code&gt;true&lt;/code&gt; 를 반환한다. 나머지도 같다.&lt;/li&gt;
&lt;li&gt;이렇게 설정한 이유는 &lt;code&gt;noLog()&lt;/code&gt; 메서드에는 어드바이스를 적용하지 않기 위해서다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어드바이저는 포인트컷(&lt;code&gt;NameMatchMethodPointcut&lt;/code&gt;), 어드바이스(&lt;code&gt;LogTraceAdvice&lt;/code&gt;)를 가지고 있다.&lt;/li&gt;
&lt;li&gt;프록시 팩토리에 각각의 &lt;code&gt;target&lt;/code&gt;과 &lt;code&gt;advisor&lt;/code&gt;를 등록해서 프록시를 생성한다. 그리고 생성된 프록시를 스프링 빈으로 등록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(ProxyFactoryConfigV1.class)
@SpringBootApplication(scanBasePackages = &amp;quot;hello.proxy.app&amp;quot;) //주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;애플리케이션 로딩 로그&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ProxyFactory proxy = class com.sun.proxy.$Proxy50, target = class hello.proxy.app.v1.OrderRepositoryV1Impl
ProxyFactory proxy = class com.sun.proxy.$Proxy52, target = class hello.proxy.app.v1.OrderServiceV1Impl
ProxyFactory proxy = class com.sun.proxy.$Proxy53, target = class hello.proxy.app.v1.OrderControllerV1Impl&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;V1 애플리케이션은 인터페이스가 있기 때문엔 프록시 팩토리가 JDK 동적 프록시를 적용한다.&lt;br&gt;애플리케이션 로딩 로그를 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실행 로그&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[b83177a1] OrderControllerV1.request()
[b83177a1] |--&amp;gt;OrderServiceV1.orderItem()
[b83177a1] |   |--&amp;gt;OrderRepositoryV1.save()
[b83177a1] |   |&amp;lt;--OrderRepositoryV1.save() time=1003ms
[b83177a1] |&amp;lt;--OrderServiceV1.orderItem() time=1004ms
[b83177a1] OrderControllerV1.request() time=1004ms&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;스프링 핵심 원리 - 고급편 &amp;gt; 프록시 팩토리 적용 2&lt;/h1&gt;
&lt;p&gt;이번에는 인터페이스가 없고 구체 클래스만 있는 V2에 적용해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
public class ProxyFactoryConfigV2 {
  @Bean
  public OrderControllerV2 orderControllerV2(LogTrace logTrace) {
    OrderControllerV2 orderController = new OrderControllerV2(orderServiceV2(logTrace));

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

  @Bean
  public OrderServiceV2 orderServiceV2(LogTrace logTrace) {
    OrderServiceV2 orderService = new OrderServiceV2(orderRepositoryV2(logTrace));

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


  @Bean
  public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace) {
    OrderRepositoryV2 orderRepository = new OrderRepositoryV2();

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

  private Advisor getAdvisor(LogTrace logTrace) {
    // pointcut
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedNames(&amp;quot;request*&amp;quot;, &amp;quot;order*&amp;quot;, &amp;quot;save*&amp;quot;);
    // advice
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    // advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(ProxyFactoryConfigV2.class)
@SpringBootApplication(scanBasePackages = &amp;quot;hello.proxy.app&amp;quot;) //주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;애플리케이션 로딩 로그&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ProxyFactory proxy = class hello.proxy.app.v2.OrderRepositoryV2$$EnhancerBySpringCGLIB$$ec5365c6, target = class hello.proxy.app.v2.OrderRepositoryV2
ProxyFactory proxy = class hello.proxy.app.v2.OrderServiceV2$$EnhancerBySpringCGLIB$$40a393e9, target = class hello.proxy.app.v2.OrderServiceV2
ProxyFactory proxy = class hello.proxy.app.v2.OrderControllerV2$$EnhancerBySpringCGLIB$$ee5a8c7c, target = class hello.proxy.app.v2.OrderControllerV2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;V2 애플리케이션은 인터페이스가 없고 구체 클래스만 있기 때문에 프록시 팩토리가 CGLIB을 적용한다.&lt;br&gt;애플리케이션 로딩 로그를 통해서 CGLIB 프록시가 적용된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실행 로그&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[9597071b] OrderControllerV2.request()
[9597071b] |--&amp;gt;OrderServiceV2.orderItem()
[9597071b] |   |--&amp;gt;OrderRepositoryV2.save()
[9597071b] |   |&amp;lt;--OrderRepositoryV2.save() time=1005ms
[9597071b] |&amp;lt;--OrderServiceV2.orderItem() time=1007ms
[9597071b] OrderControllerV2.request() time=1009ms&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;프록시 팩토리 덕분에 개발자는 매우 편리하게 프록시를 생성할 수 있게 되었다.&lt;br&gt;추가로 어드바이저, 어드바이스, 포인트컷 이라는 개념 덕분에 &lt;strong&gt;어떤 부가 기능&lt;/strong&gt;을 &lt;strong&gt;어디에 적용&lt;/strong&gt;할 지 명확하게 이해할 수 있었다.&lt;/p&gt;
&lt;h2&gt;남은 문제&lt;/h2&gt;
&lt;p&gt;프록시 팩토리와 어드바이저 같은 개념을 사용하여 지금까지 고민했던 문제들(원본 코드 변경 없이 적용, 어디에 어떻게 적용 등)은 해결되었다. 프록시도 깔끔하게 적용하고 포인트컷으로 어디에 부가 기능을 적용할지 명확하게 정의가 가능해졌다.&lt;br&gt;원본 코드를 전혀 손대지 않고 프록시를 통해 부가 기능도 적용할 수 있었지만 아직 해결되지 않는 문제들이 있다.&lt;/p&gt;
&lt;h3&gt;문제1 - 너무 많은 설정&lt;/h3&gt;
&lt;p&gt;바로 &lt;code&gt;ProxyFactoryConfigV1&lt;/code&gt;, &lt;code&gt;ProxyFactoryConfigV2&lt;/code&gt;와 같은 설정 파일이 지나치게 많다는 점이다.&lt;br&gt;예를 들어서 애플리케이션에 스프링 빈이 100개가 있다면 여기에 프록시를 통해 부가 기능을 적용하려면 100개의 동적 프록시 생성 코드를 만들어야 한다...&lt;br&gt;무수히 많은 설정 파일 때문에 설정 지옥을 경험하게 될 것이다.&lt;br&gt;최근에는 스프링 빈을 등록하기 귀찮아서 컴포넌트 스캔까지 사용하는데, 이렇게 직접 등록하는 것도 모자라서, 프록시를 적용하는 코드까지 빈 생성 코드에 넣어야 한다.&lt;/p&gt;
&lt;h3&gt;문제2 - 컴포넌트 스캔&lt;/h3&gt;
&lt;p&gt;애플리케이션 V3처럼 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능하다.&lt;br&gt;왜냐하면 실제 객체를 컴포넌트 스캔으로 스프링 컨테이너에 스프링 빈으로 등록을 다 해버린 상태이기 때문이다. 지금까지 학습한 프록시를 적용하려면, 실제 객체를 스프링 컨테이너에 빈으로 등록하는 것이 아니라 &lt;code&gt;ProxyFactoryConfigV1&lt;/code&gt;에서 한 것 처럼, 부가 기능이 있는 프록시를 실제 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;두 가지 문제를 한번에 해결하는 방법이 바로 다음에 설명할 &lt;strong&gt;빈 후처리기&lt;/strong&gt;이다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;출처: 김영한 지식공유자의 스프링 핵심 원리 고급편&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE/Spring</category>
      <author>오봉봉이</author>
      <guid isPermaLink="true">https://5bong2-develop.tistory.com/578</guid>
      <comments>https://5bong2-develop.tistory.com/578#entry578comment</comments>
      <pubDate>Thu, 3 Oct 2024 23:53:27 +0900</pubDate>
    </item>
  </channel>
</rss>