[ 이전 포스팅 요약 ]

- 요청 객체(Client)와 실제 객체(Server) 사이에 프록시 객체 도입으로 원본 코드를 수정하지 않고 의존성 주입 만으로 공통 부가 기능을 적용할 수 있게 되었다. (데코레이터 패턴 도입)
- 다형성(polymorphism)을 활용하여 프록시 객체는 실제 객체와 같은 인터페이스로 구현하거나, 실제 객체를 상속 받은 자식 클래스로 만들었다.
- 문제점으로는 실제 객체마다 각각의 프록시 객체를 만들어줘야 한다는 한계가 발생하였다.
- 이는 프록시 클래스를 동적으로 생성해주는 JDK 동적 프록시 기술과 CGLIB로 해결할 수 있다.
[ 리플렉션 ]
- JDK 동적 프록시를 이해하기 위해서는 먼저 자바의 리플렉션 기술을 이해해야 한다.
- 자바의 리플렉션 기술은 클래스나 메서드의 메타정보를 동적으로 획득하고, 코드도 동적으로 호출할 수 있다.
@Slf4j
public class ReflectionTest {
@Test
public void reflection2() throws Exception {
// 클래스의 메타정보 (내부 클래스는 구분을 위해 '$' 사용)
Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");
Hello target = new Hello();
// callA 메서드 메타정보
Method methodCallA = classHello.getMethod("callA");
dynamicCall(methodCallA, target); // 찾은 클래스와 메서드의 메타정보를 넘겨준다.
// callB 메서드 메타정보
Method methodCallB = classHello.getMethod("callB");
dynamicCall(methodCallB, target); // 찾은 클래스와 메서드의 메타정보를 넘겨준다.
}
/**
* 공통 로직1, 공통 로직2를 한 번에 처리할 수 있는 통합된 공통 처리 로직
* @param method 호출할 메서드 정보를 동적으로 제공
* @param target 실제 실행할 인스턴스 정보
* @throws Exception 호출할 클래스와 메서드 정보가 다르면 예외 발생
*/
private void dynamicCall(Method method, Object target) throws Exception{
log.info("start"); // 공통 로직
Object result = method.invoke(target); // 동적으로 실제 객체 호출
log.info("result={}", result); // 공통 로직
}
@Slf4j
static class Hello{
public String callA(){
log.info("callA");
return "A";
}
public String callB(){
log.info("callB");
return "B";
}
}
}
- 클래스와 메서드의 메타정보를 동적으로 추출해서 dynamicCall 메서드를 호출함으로서, 공통로직을 쉽게 추가하였다.
- 리플렉션은 치명적인 단점이 있는데, 런타임에 호출 메서드를 결정하므로 컴파일 시점에 에러를 잡을 수 없다.
- 찾을 클래스와 메서드 명을 문자로 입력하기 때문에 오타가 발생하여도 컴파일 시점에 오류가 발생하지 않는다. 그러나 해당 코드를 직접 실행하는 런타임 시점에 오류가 발생한다.
- 리플렉션은 프레임워크 개발이나 또는 매우 일반적인 공통 처리가 필요할 때 부분적으로 주의해서 사용해야 한다.
[ JDK 동적 프록시 - 예제]
- 실제 객체들에 적용할 부가 기능은 모두 동일하고, 적용 시킬 대상만 다를 때 런타임에 동적으로 프록시 객체를 생성해주는 방식
- 자바가 기본으로 제공하는 동적 프록시 기술
- JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. (인터페이스가 필수)