728x90
반응형
728x90
JDK 동적 프록시
- 런타임 시 인터페이스를 구현하여 프록시 객체를 생성하는 기술
- 특정 로직이 같고 호출하는 메서드만 다른 경우 프록시 클래스를 직접 각각 작성할 필요 없이 동적으로 생성
구성 요소
- InvocationHandler (Interface): 프록시 객체가 호출할 메서드에 대한 처리를 정의
- java.lang.reflect.Proxy: 동적 프록시 객체를 생성하는 클래스
사용 방법
- InvocationHandler 인터페이스를 상속받아 invoke 메서드를 구현
- invoke 메서드 내 프록시 객체가 처리할 로직을 구현
- Method의 invoke를 통해 프록시 객체에서 실제 객체로 요청을 전달
- Proxy.newProxyInstance() 동적 프록시 객체 생성
- 첫 번째 파라미터로 인터페이스를 불러올 클래스로더를 전달
- 두 번째 파라미터로 구현체가 상속할 인터페이스 배열 전달
- 세 번째 파라미터로 로직을 정의한 Handler 전달
- 생성된 프록시 객체를 통해 메서드 호출
//인터페이스 정의
interface Hello {
void sayHello();
}
//InvocationHandler 구현
class HelloInvocationHandler implements InvocationHandler {
private final Hello original;
public HelloInvocationHandler(Hello original) {
this.original = original;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("메서드 호출 전");
Object result = method.invoke(original, args); // 원래 메서드 호출
System.out.println("메서드 호출 후");
return result;
}
}
//실제 구현체
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("안녕하세요!");
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
Hello original = new HelloImpl();
Hello proxy = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), //클래스 로더
new Class[]{Hello.class}, //구현체가 상속 받을 인터페이스 배열
new HelloInvocationHandler(original) //로직이 정의된 핸들러
);
proxy.sayHello(); // 프록시 메서드 호출
}
}
장점
- 런타임에 객체를 변경하거나 새로운 기능을 추가할 수 있음
- 공통적인 처리 로직을 한 곳에서 관리할 수 있음
단점
- 인터페이스 기반으로 작동하여 클래스 기반 사용 불가
CGLIB
- 자바에서 동적 프록시를 생성하기 위한 라이브러리로 클래스 기반 동적 프록시 생성을 지원
- 자바의 리플렉션으로 런타임에 새로운 클래스를 생성하고 기존 클래스의 메서드를 오버라이드하여 프록시를 구현
- Java 표준 라이브러리가 아닌 외부 라이브러리
특징
- 인터페이스가 아닌 클래스를 기반으로 동적 프록시를 생성할 수 있음 -> 인터페이스가 없어도 적용 가능
- 바이트코드를 조작하여 새로운 클래스를 생성, 기존 클래스의 메서드를 동적으로 오버라이드
- 리플렉션을 사용하는 것보다 더 빠른 성능을 제공
사용 방법
- MethodInterceptor 인터페이스를 상속받아 intercept 메서드 구현
- InvocationHandler의 invoke를 구현하는 것과 동일하게 MethodInterceptor의 interceptor 메서드에 공통 로직을 구현
- MethodProxy의 invokeSuper를 사용해 원본 객체 메서드를 호출 해줌
- 프록시 객체에서 원본 객체 메서드를 오버라이드 한 경우 invoke를 통해 프록시 객체에 오버라이드 된 메서드 호출 가능
- Enhancer 동적 프록시 생성
- Enhancer를 통해 동적 프록시 객체를 생성
- 프록시 객체의 부모 클래스가 될 기반 클래스를 setSuperclass로 지정
- setCallback()을 통해 로직이 정의된 Interceptor 지정
- create()로 프록시 객체 생성
//실제 클래스
class HelloService {
public void sayHello() {
System.out.println("안녕하세요!");
}
}
//MethodInterceptor 구현
class HelloInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("메서드 호출 전");
//Object result = proxy.invokeSuper(obj, args); //원래 메서드 호출
Object result = proxy.invoke(obj, args); //원본 객체의 메서드를 오버라이드 한 경우
System.out.println("메서드 호출 후");
return result;
}
}
public class CGLIBExample {
public static void main(String[] args) {
//Enhancer를 통해 프록시 생성
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloService.class);
enhancer.setCallback(new HelloInterceptor());
//프록시 객체 생성
HelloService proxy = (HelloService) enhancer.create();
proxy.sayHello(); //프록시 메서드 호출
}
}
장점
- 인터페이스 없이 클래스 기반의 프록시를 만들 수 있음
- 실제 메서드를 오버라이드하여 추가적인 기능을 쉽게 적용할 수 있음
- JDK 동적 프록시에 비해 빠름
단점
- final로 선언된 메서드를 오버라이드할 수 없음 -> 해당 메서드 프록시 객체에서 사용 불가
스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런 강의 내용 참고
728x90
'백엔드 > Spring' 카테고리의 다른 글
[Spring] 빈 후처리기 - BeanPostProcessor (0) | 2024.11.07 |
---|---|
[Spring] ProxyFactory (1) | 2024.11.06 |
[Spring] Proxy & Decorator Pattern (0) | 2024.11.02 |
[Spring] Strategy & Template Callback (0) | 2024.11.01 |
[Spring] Template Method (0) | 2024.10.31 |