[Spring AOP] JDK Dynaic Proxy에서 내부호출이 안되는 이유

2025. 3. 11. 08:11·Spring
728x90
반응형
SMALL

 JDK Dynamic Proxy에서 내부 메서드 호출이 안 되는 이유

 

Spring의 @Transactional이 같은 클래스 내에서 메서드를 호출할 때 동작하지 않는 이유는 JDK Dynamic Proxy가 메서드 호출을 가로채지 못하기 때문이다.

 


 JDK Dynamic Proxy의 특징

 

JDK Dynamic Proxy는 인터페이스 기반의 프록시를 생성한다.

즉, 프록시는 인터페이스를 구현하는 객체로 생성되며, 원본 객체의 메서드 실행을 가로채 트랜잭션을 관리한다.

 

하지만!

JDK Dynamic Proxy는 인터페이스를 통해 접근할 때만 프록시를 거치며, 같은 클래스 내부에서 메서드를 호출하면 프록시를 거치지 않음.

 


 왜 내부 호출이 트랜잭션을 적용하지 못하는가?

 

 외부에서 호출할 때는 프록시가 적용됨

public interface ProductService {
    void createProduct();
}

@Service
public class ProductServiceImpl implements ProductService {
    
    @Override
    @Transactional
    public void createProduct() {
        System.out.println("상품 생성");
    }
}

📌 실제 객체 생성 과정 (JDK Dynamic Proxy 사용):

ProductService target = new ProductServiceImpl();
ProductService proxy = (ProductService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    new Class[]{ProductService.class},
    new TransactionHandler(target) // 트랜잭션을 관리하는 핸들러
);

 트랜잭션이 적용되는 과정

1. 클라이언트가 proxy.createProduct() 호출.

2. TransactionHandler가 createProduct() 실행을 가로채서 트랜잭션을 시작.

3. target.createProduct()를 실행.

4. 실행이 끝나면 트랜잭션을 commit() 또는 rollback().

 

 즉, 외부에서 호출하면 JDK Dynamic Proxy가 동작하여 트랜잭션이 적용됨.

 


 내부 호출 시 프록시가 적용되지 않음

@Service
public class ProductServiceImpl implements ProductService {

    @Override
    @Transactional
    public void createProduct() {
        System.out.println("상품 생성");
        logTransaction(); // 내부 메서드 호출 (프록시 적용 안 됨)
    }

    @Transactional
    public void logTransaction() {
        System.out.println("트랜잭션 로그 저장");
    }
}

 내부 호출 흐름

productService.createProduct(); //  프록시 적용 (트랜잭션 관리)
    ├── createProduct() 호출
        ├── logTransaction() 호출 (내부 호출, 프록시를 거치지 않음)

 결과적으로 logTransaction()은 프록시를 거치지 않고 직접 실행되므로 @Transactional이 적용되지 않음.

 


 내부 호출에서 프록시가 적용되지 않는 이유

 

 JDK Dynamic Proxy는 인터페이스를 기반으로 프록시 객체를 생성한다

• 프록시는 인터페이스를 통해 호출될 때만 실행을 가로챌 수 있음.

• 내부에서 this.logTransaction()을 호출하면, 인터페이스를 거치지 않고 직접 원본 객체를 실행하게 됨.

 

같은 클래스 내에서 this를 통해 호출하면 프록시를 거치지 않음

• Spring이 프록시 객체를 생성하는 방식:

ProductService proxy = new JdkDynamicProxy(new ProductServiceImpl());

 

• 이때, this.logTransaction();을 호출하면 프록시가 아니라 원본 객체(ProductServiceImpl)의 메서드를 직접 실행.

 

 


 해결 방법

 

 ApplicationContext를 사용하여 프록시를 통해 호출

@Autowired
private ApplicationContext applicationContext;

public void createProduct() {
    System.out.println("상품 생성");
    applicationContext.getBean(ProductService.class).logTransaction(); //  프록시를 통해 호출
}

• applicationContext.getBean(ProductService.class)를 사용하면 Spring이 관리하는 프록시 객체를 통해 실행되므로 트랜잭션이 정상적으로 적용됨.

 


 CGLIB Proxy 사용 (proxyTargetClass = true)

 

JDK Dynamic Proxy는 인터페이스 기반이지만,

CGLIB은 클래스 기반의 프록시를 생성하여 내부 호출도 감지할 수 있음.

 

 설정 방법

@EnableTransactionManagement(proxyTargetClass = true) // CGLIB 프록시 사용
public class AppConfig {
}

 CGLIB을 사용하면 내부 호출도 트랜잭션이 적용됨

@Service
@Transactional
public class ProductService {
    public void createProduct() {
        System.out.println("상품 생성");
        logTransaction(); //  CGLIB에서는 트랜잭션이 적용됨!
    }

    @Transactional
    public void logTransaction() {
        System.out.println("트랜잭션 로그 저장");
    }
}

• CGLIB은 클래스를 상속받아 프록시를 생성하므로, 내부 호출(this.logTransaction())도 프록시가 감지할 수 있음.

 


 

구분JDK Dynamic ProxyCGLIB Proxy

기반 인터페이스 기반 프록시 클래스 기반 프록시
내부 메서드 호출  프록시를 거치지 않음 프록시를 거쳐 트랜잭션 적용됨
프록시 생성 방식 Proxy.newProxyInstance() Enhancer.setSuperclass()
설정 방법 기본적으로 사용됨 @EnableTransactionManagement(proxyTargetClass = true) 필요

 JDK Dynamic Proxy는 인터페이스 기반이므로, 내부 메서드 호출 시 트랜잭션이 적용되지 않는다.

다르게 말하면 내부 호출된 메서드에서 엔티티를 변경하거나 저장하려고 해도 영속성 컨텍스트가 활성화되지 않을 수 있으며, 트랜잭션이 관리되지 않아 예상치 못한 동작이 발생할 수 있다.

 해결 방법

1. ApplicationContext.getBean()을 사용하여 프록시 객체를 통해 호출.

2. proxyTargetClass = true로 CGLIB을 사용하여 클래스 기반 프록시를 활성화.

728x90
반응형
SMALL

'Spring' 카테고리의 다른 글

[Lombok] @Builder를 클래스가 아닌 메서드에 적용하는 이유  (0) 2025.03.11
[Spring AOP] @Transactional의 Proxy 작동 방식  (0) 2025.03.11
[Lombok] @Builder, DTO 접근제한자 적절한 설정 방법  (0) 2025.03.11
[Lombok] @Builder를 클래스가 아닌 메서드에 적용하는 이유  (0) 2025.03.11
[Spring/Serialization-Deserialization] Date, LocalDate, LocalDateTime 형변환 방법, 직렬화/역직렬화 문제 해결하기  (0) 2025.03.07
'Spring' 카테고리의 다른 글
  • [Lombok] @Builder를 클래스가 아닌 메서드에 적용하는 이유
  • [Spring AOP] @Transactional의 Proxy 작동 방식
  • [Lombok] @Builder, DTO 접근제한자 적절한 설정 방법
  • [Lombok] @Builder를 클래스가 아닌 메서드에 적용하는 이유
공부하고 기억하는 공간
공부하고 기억하는 공간
IT 비전공자로 시작하여 훌륭한 개발자가 되기 위해 공부하고 있는 공간입니다. 틀린 내용이나 부족한 부분이 있으면 댓글로 알려주세요 바로 수정하겠습니다.
    250x250
  • 공부하고 기억하는 공간
    IT - railroad
    공부하고 기억하는 공간
  • 전체
    오늘
    어제
    • 분류 전체보기 (315)
      • 면접 준비 (36)
        • OS (6)
        • Spring Security (0)
        • Java (2)
        • DB (9)
        • Network (3)
      • ElasticSearch (2)
      • Kafka (4)
      • Spring (22)
        • Spring Cloud (7)
        • Security6 (5)
        • JPA (12)
        • 프로젝트 리팩토링 회고록 (4)
        • Logging (8)
        • Batch (2)
      • Redis (17)
        • Redis 개념 (8)
        • Redis 채팅 (5)
        • Redis 읽기쓰기 전략 (1)
      • AWS (11)
      • 리눅스 (29)
        • 리눅스 마스터 2급 (5)
        • 네트워크(기초) (7)
        • 리눅스의 이해 (6)
        • 리눅스의 설치 (2)
        • 리눅스 운영 및 관리 (6)
      • JAVA-기초 (16)
        • JAVA기본 (11)
        • Design Pattern (5)
      • JSP (27)
        • JSP 기본 개념 (10)
        • JSP (1)
      • SQL (1)
      • TIL (36)
      • 문제 풀이 (2)
        • Programmers (9)
        • 백준 문제풀이 (28)
      • JavaScript (10)
      • HTML (17)
      • Ngrinder (1)
        • Ngrinder 문서 정리 (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      java
      리눅스마스터2급
      자바기초
      JavaScript
      자바스크립트
      자바
      spring redis
      리눅스마스터2급정리
      프로그래머스
      CSS
      HTML
      자바 면접
      자바 반복문
      레디스
      JSP
      redis
      redis 채팅
      자바 알고리즘
      자바 면접질문
      Til
      springsecurity
      Spring Data Redis
      스프링프레임워크
      Spring
      리눅스
      백준
      Springframework
      jsp request
      JS
      jsp기초
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    공부하고 기억하는 공간
    [Spring AOP] JDK Dynaic Proxy에서 내부호출이 안되는 이유
    상단으로

    티스토리툴바