프록시

랏 뜨·2024년 12월 12일

🔎 Overview

  금일 듣는 강의 내용 중 아주 중요한 내용이 있었다. 프록시가 그 주인공이다.

가짜 객체를 하나 생성해서 실제 객체와의 작업을 진행한다.

내가 그동안 알고 있었던 프록시의 개념이다.
하지만 어디까지나 어렴풋이 알고 있었던 지식이고, 확실하게 알고 있는 개념인지도 헷갈리는 것이 사실이다.

  프록시 또한 중요한 개념이기 때문에, 이번 기회에 확실히 알아보고자 한다.


📕 프록시

  • 어떤 객체에 대한 대리자 역할을 수행하는 디자인 패턴
  • 실제 객체와의 접근을 제어하거나, 추가적인 작업을 수행하기 위해 사용
  • 스프링 프레임워크에서는 주로 AOP 와 관련된 기능을 제공하거나, Bean 을 동적으로 생성하기 위해서 사용

💪 프록시의 역할

  • 대리 객체 생성
    • 클라이언트가 실제 객체와 직접적으로 상호작용하지 않음
    • Proxy 를 통해 간접적으로 상호작용
  • 추가 로직 수행
    • 메서드 호출 전후로 로직을 추가할 수 있음
    • 로깅, 보안 검증, 캐싱, 트랙잭션 처리 등의 작업이 추가로 가능함
  • 지연 로딩
    • 필요 시점까지 객체 생성을 지연시킬 수 있음

✒️ 스프링에서의 프록시 사용

  • AOP
    • 스프링은 메서드 호출 전후에 부가적인 로직을 추가하기 위해 Proxy 객체를 사용
    • 로깅, 보안 검증 등 공통적으로 처리해야 하는 로직이 이에 해당
  • 트랜잭션 관리
    • @Transactional 어노테이션은 Proxy 를 통해 트랜잭션 경계를 설정하고 관리
  • 지연 로딩
    • 특정 의존성을 필요로 할 때까지 초기화하지 않도록 지원

🧑‍🤝‍🧑 트랜잭션과 프록시의 관계

  • 스프링에서 @Transactional 어노테이션이 붙은 메서드나 클래스는 Proxy 객체를 통해 트랜잭션 처리가 이루어짐
  • 트랜잭션 관리의 핵심은 메서드 호출을 가로채는 것이며, 이 역할을 Proxy 가 함

▶️ 작동 원리

1) 스프링은 @Transcation 이 붙은 클래스의 원본이 아닌, Proxy 객체를 생성

2) 클라이언트로부터 호출이 발생하면, Proxy 가 호출을 가로챔

3) 트랜잭션의 시작과 끝을 설정하고, 메서드를 실행

4) 메서드 실행이 완료된 후 커밋 또는 롤백을 진행


🚫 프록시 사용 시 주의사항

1) @Transactional 의 동작 제한

  • 같은 클래스 내부의 @Transactional 메서드를 호출할 경우 this 를 통해 객체 자신의 메서드 호출
  • 이 경우, Proxy 객체를 통하는 것이 아니므로 트랜잭션이 적용되지 않음

2) AOP 및 프록시의 영향

  • 디버깅 시 실제 객체가 아닌 Proxy 객체를 보게 될 수 있음
  • 원본 객체와 잘 구분해야 함

🤔 그렇다면 같은 클래스 내부의 @Transactional 메서드를 Proxy 객체를 통해 호출하려면 어떻게 해야 할까?


🔁 순환 참조와 프록시

  • 순환 참조
    • 2개 이상의 객체가 서로를 의존하여 초기화가 제대로 진행되지 않는 경우 발생
    • 스프링의 의존성 주입 과정에서 무한 루프가 발생할 수 있음

‼️ 스프링은 지연 로딩 프록시를 사용하여 순환 참조를 우회할 수 있음. 초기에는 실제 객체 대신 Proxy 객체를 주입하고, 이후 필요하다면 실제 객체로 초기화하여 사용


☑️ @Lazy

  • Bean 의 초기화를 지연시키는 역할을 하는 어노테이션
    • 스프링은 기본적으로 컨텍스트 로딩 시점에 모든 Bean 을 초기화
    • @Lazy 를 사용한 Bean의 경우, 실제로 필요할 때까지 초기화를 진행하지 않고 연기
    • @Lazy@Autowired 로 자기 자신을 주입할 경우, 이후 초기화 시점에 필요에 따라 Proxy 객체를 스프링 컨테이너에 등록

🕛 사용 시점

1) 순환 참조 해결

  • 순환 참조 발생이 가능한 Bean 의 경우, @Lazy 를 미리 붙여두어 초기화를 지연

2) 리소스 절약

  • 초기화 비용이 큰 Bean 은 필요할 때만 생성하여 애플리케이션의 성능 개선

❕@Transactional 메서드의 내부 호출

✔️ 이제 이전의 질문에 대한 답을 할 수 있다.

  • @Transactional 메서드를 내부 호출하기 위해서는 자기 참조(self-reference) 필드를 선언해야 함
  • @Autowired 로 자동 생성자 주입
  • @LazyBean 의 초기화를 미룸
@Configuration
@RequiredArgsConstructor
public class Board {
	// 생략
    
	@Autowired	// 필수
    @Lazy	// 필수
    private Board self;	// 변수명 설정은 자유, @RequiredArgsConstructor로 인한 초기화를 막기 위해 final은 붙이지 않음
    
    @Bean
    public ApplicationRunner init() {
    	return args -> self.work();
    }
    
    @Transactional
    public void work() {
    	// 생략
    }
}
  • 스프링 애플리케이션 시작 시 init() 메서드 실행
  • @Transactional 메서드인 work() 메서드를 내부 호출
  • @Lazy 로 설정된 self는 이 때 초기화
  • self의 초기화 시점에 @Transactional 메서드인 work() 메서드를 실행하므로, selfProxy 객체로 스프링 컨테이너에 주입됨
  • Proxy 객체가 work() 메서드를 수행

↔️ 프록시와 Bean

1) Bean

  • Bean 으로 등록된 객체는 기본적으로 원본 객체가 생성되어 스프링 컨테이너에 등록
  • 하지만 이 객체에 AOP 가 적용되거나, Proxy 기반의 기능이 활성화된 경우, 원본 객체가 아닌 Proxy 객체가 대신 생성되어 컨테이너에 등록

2) Proxy

  • 항상 원본 객체가 아닌 Proxy 객체를 생성하여 트랜잭션 관리를 수행
  • Proxy 객체가 스프링 컨테이너에 등록되며, 메서드 호출 시 Proxy 객체를 사용

참고) OpenAI. (2024).ChatGPT(4o)[Large language model].https://chatgpt.com/

profile
기록

0개의 댓글