[Spring] 프록시 패턴(Proxy Pattern)
✏️ 한 줄 정리
프록시 패턴은 실제 객체 대신 프록시 객체가 요청을 가로채어, 추가적인 기능(트랜잭션, 로깅, 보안 등)을 수행한 후 실제 객체를 호출하는 디자인 패턴
프록시 패턴(Proxy Pattern) 이란 ?
프록시 패턴은 객체에 대한 접근을 제어하기 위해 대리자(Proxy) 객체를 두는 디자인 패턴이다.
즉, 실제 객체 대신 프록시 객체가 먼저 호출을 가로채고, 필요하면 실제 객체를 호출하는 구조를 가진 패턴
Spring에서 프록시 패턴을 사용하는 경우
1. 트랜잭션 관리 (@Transaction)
- 트랜잭션을 시작하고, 메서드 실행 후 커밋 또는 롤백
2. AOP (Aspect-Oriented Programming, @Aspect)
- 로깅, 성능 모니터링, 보안 검증 등 부가기능 적용
- @Before, @AfterReturning, @Around
3. Spring Security (@PreAuthorize, @PostAuthorize)
- 특정메서드가 실행되기 전에 권한 체크
- @PreAuthorize("hasRole('ADMIN')")
4. Lazy Loading (지연 로딩) - JPA/Hibernate
- 연관된 엔티티를 처음에 로딩하지 않고, 실제로 접근할 때 프록시 객체가 데이터베이스에서 가져오는 방식
- @OneToMany(fetch = FetchType.LAZY)
Spring의 트랜잭션과 프록시 패턴
Spring의 @Transactional이 동작하는 방식도 프록시 패턴을 기반으로 하고 있다.
즉, 스프링이 실제 객체 대신 프록시 객체를 만들어서 트랜잭션을 관리함
트랜잭션 적용 전(일반적인 객체 호출)
@Service
public class OrderService {
public void processOrder() {
System.out.println("주문을 처리합니다.");
}
}
트랜잭션 적용 후 (Spring이 프록시 객체를 만들어 관리)
@Service
public class OrderService {
@Transactional
public void processOrder() {
System.out.println("주문을 처리합니다.");
}
}
1️⃣ Spring이 @Transactional이 있는 클래스를 감지
2️⃣ OrderService 의 원본 객체 대신, 트랜잭션을 관리하는 프록시 객체 생성
3️⃣ 서비스가 실행될 때, 프록시가 먼저 실행되고 트랜잭션을 시작
4️⃣ 실제 processOrder() 실행
5️⃣ 실행이 끝나면 프록시가 트랜잭션을 커밋 또는 롤백
프록시 패턴을 쓰는 이유
1. 트랜잭션을 자동으로 관리할 수 있음
- 개발자가 직접 트랜잭션을 시작/종료하지 않아도 Spring이 알아서 관리 해줌
2. 부가적인 기능을 추가할 수 있음
- 트랜잭션 뿐만 아니라, 로깅, 보안, 캐싱 같은 기능도 프록시를 통해 추가 가능
3. 객체에 직접 접근하지 않고, 제어 가능
- 예를 들어, 클라이언트가 직접 DB와 연결되지 않고, 프록시가 트랜잭션을 조정하는 것
프록시 패턴의 한계 (Self-Invocation 문제)
Spring에서 @Transactional 이 프록시 패턴을 이용하기 때문에
같은 클래스 내부에서 트랜잭션 메서드를 호출하면, 프록시를 거치지 않아 트랜잭션이 적용되지 않는 문제가 있음
잘못된 상황 (Self-Invocation 발생)
@Service
public class OrderService {
@Transactional
public void processOrder() {
validateOrder(); // 이 메서드는 트랜잭션 적용 안 됨!
System.out.println("주문 처리 완료");
}
@Transactional
public void validateOrder() {
System.out.println("주문 검증 중");
}
}
- processOrder() 내부에서 validateOrder() 를 호출했지만,
같은 클래스 내에서 호출하면 프록시를 거치지 않아서 validateOrder() 트랜잭션이 적용되지 않음
해결 방법 : 별도의 서비스로 분리
@Service
public class OrderService {
private final ValidationService validationService;
@Transactional
public void processOrder() {
validationService.validateOrder(); // 프록시를 통해 호출되므로 트랜잭션 정상 적용
System.out.println("주문 처리 완료");
}
}
@Service
public class ValidationService {
@Transactional
public void validateOrder() {
System.out.println("주문 검증 중");
}
}