728x90
프록시 객체란?
원본 객체를 감싸는 객체로 실제 객체를 대신하여 요청을 처리하는 객체입니다.
메소드 호출을 가로채 추가적인 기능(로깅, 트랜잭션, 보안 등)을 수행할 수 있도록 합니다.
프록시 객체의 주요 특징
- AOP 적용 시 프록시 객체 사용
- AOP는 메소드 실행 전후에 부가 기능을 삽입하기 위해 프록시 객체를 활용합니다.
- @Transactional을 적용하면 대상 객체 대신 트랜잭션을 관리하는 프록시 객체를 생성
- 동적 프록시 방식
- 런타임에 동적으로 프록시 객체 생성
- 트랜잭션 관리와 관련
- @Transactional을 사용할 경우 해당 객체의 메소드를 실행할 때 프록시를 통해 트랜잭션을 시작하고 정상적으로 종료되면 커밋을 하고 예외가 발생할 때는 롤백을 합니다.
프록시 객체 종류
JDK 프록시 객체
인터페이스를 구현한 객체일 경우 JDK의 프록시 객체를 생성합니다.
UserServiceIfs
public interface UserServiceIfs {
void createUser();
}
UserService
@Service
@Transactional
public class UserService implements UserServiceIfs {
@Override
public void createUser() {
System.out.println("사용자 생성");
}
}
CGLIB 프록시 객체
인터페이스를 구현하지 않은 객체일 경우(일반적인 객체) CGLIB(Code Generation Library)을 이용하여 프록시 객체를 생성합니다. CGLIB은 바이트코드를 조작하여 원본 클래스를 상속하는 프록시 클래스를 동적으로 생성하고, 해당 프록시에서 메서드를 오버라이드하여 추가 기능을 삽입합니다.
@Service
@Transactional
public class UserService {
public void createUser() {
System.out.println("사용자 생성");
}
}
주의해야 할 점
- 오버헤드 발생 : 무분별하게 프록시 객체를 생성했을 때 프록시 객체로 변환하는 추가적인 작업이 있기에 필요한 곳에서 사용하는 것이 좋습니다. ex) 클래스 단위로 @Transactional
- 같은 클래스 내에서 메소드 호출 금지 : 프록시 객체로 감싸지만 주소로 참조하고 있는 this.메소드()를 했을 때 해당 객체로 바로 가기 때문에 프록시로 보호받을 수 없습니다.
@Service
public class UserService {
@Transactional
public void processUser() {
System.out.println("유저 생성 중");
}
public void createUser() {
this.processUser(); // 프록시를 거치지 않음 → 트랜잭션 미적용
}
}
- 메소드 접근자 private 사용 금지 : JDK 프록시는 인터페이스이기 때문에 private은 관계 없지만 CGLIB 프록시 객체(일반 구현 객체)의 메소드가 private일 경우 상속을 받아 프록시 객체를 생성하고 보호하기 때문에 private으로 인하여 트랜잭션이 적용되지 않습니다.
- 인터페이스에 선언되지 않은 메소드 선언 금지 : JDK 방식은 인터페이스를 프록시로 감싸서 동작하기 때문에 인터페이스에 선언되지 않은 메소드는 프록시 객체로부터 보호받을 수 없습니다.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 인터페이스 정의
public interface UserServiceIfs {
void createUser();
}
// 2. 실제 서비스 구현
public class UserService implements UserServiceIfs {
@Override
public void createUser() {
System.out.println("사용자 생성 완료!");
}
}
// 3. 프록시 생성 (JDK 동적 프록시 방식)
public class UserServiceProxyHandler implements InvocationHandler {
private final Object target;
public UserServiceProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("로그 시작...");
Object result = method.invoke(target, args); // 원래 메서드 호출
System.out.println("로그 종료...");
return result;
}
}
// 4. 프록시 객체 생성 및 사용
public class ProxyDemo {
public static void main(String[] args) {
UserServiceIfs target = new UserService(); // 실제 객체
UserServiceIfs proxy = (UserServiceIfs) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{UserServiceIfs.class},
new UserServiceProxyHandler(target)
);
proxy.createUser(); // 프록시 객체를 통해 메서드 호출
}
}
728x90
'Spring Boot' 카테고리의 다른 글
[Spring Boot] 상속관계 매핑 전략 (0) | 2025.03.15 |
---|---|
[Spring Boot] @PrePersist 어노테이션 (1) | 2025.02.18 |
[Spring Boot] HTTP Body 암호화 및 간단한 예제 (0) | 2024.12.17 |
[Spring Boot] 로그 파일 생성하기 (0) | 2024.11.30 |
[Spring Boot] Slf4j와 Logback (1) | 2024.11.29 |