프록시를 사용하는 프록시 패턴, 데코레이터 패턴을 배운 이후
프록시 클래스들의 소스 코드는 거의 동일한 모양을 하고 있는데 이 프록시를 적용하기 위해서
계속 프록시 클래스들을 만들어야 하는 문제점이 있었다.
동적 프록시 기술을 이용해서 쉽게 적용시켜보자!
JDK 동적 프록시를 이해하기 위해 먼저 자바의 리플렉션 기술을 이해해야 한다고 한다.
이 기술을 사용하면 클래스나 메서드의 메타정보를 동적으로 획득하고, 코드도 동적으로 호출 할 수 있다.
최소한의 리플렉션 기술을 알아보자
//(생략)
//공통 로직 1 시작
log.info("start");
String result1 = target.callA();
log.info("result ={}", result1);
//공통 로직 1 종료
//공통 로직 2 시작
log.info("start");
String result2 = target.callB();
log.info("result ={}", result2);
//공통 로직 2 종료
위 코드에서 공통되는 코드는 아래와 같다
log.info("start");
String result = xxx(); //호츌 대상이 다름, 동적 처리 필요
log.info("result ={}", result);
이때 동적 처리 필요 부분을 리플렉션을 사용하여 해결할 수 있다고 한다!
참고로 람다를 사용해서 공통화하는 것도 가능하다.하지만 학습을 위해서 람다를 사용하기 어려운 상황이라는 가정하에 리플렉션에 집중해보자
@Test
void reflection1() throws Exception {
//클래스 정보
Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello"); //클래스 메타정보 획득
Hello target = new Hello();
//callA 메서드 정보
Method methodCallA = classHello.getMethod("callA"); //해당 클래스의 call메서드 메타정보 획득
Object result1 = methodCallA.invoke(target); //획득한 메서드 메타정보로 실제 인스턴스의 메서드를 호출
log.info("result1={}", result1);
//callB 메서드 정보
Method methodCallB = classHello.getMethod("callB");
Object result2 = methodCallB.invoke(target);
log.info("result2={}", result2);
}
위 코드를 보면 methodCallA, methodCallB 모두 Method 이다. 이제 공통 로직을 만들 수 있게 되었다.
@Test
void reflection2() throws Exception {
Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");
Hello target = new Hello();
Method methodCallA = classHello.getMethod("callA");
dynamicCall(methodCallA, target);
Method methodCallB = classHello.getMethod("callB");
dynamicCall(methodCallB, target);
}
private void dynamicCall(Method method, Object target) throws Exception {
log.info("start");
Object result = method.invoke(target);
log.info("result={}", result);
}
최종적으로 이렇게 코드가 완성된다.
target.callA(), target.callB()
코드를 리플렉션을 사용해서 Method
라는 메타정보로 추상화했다. 덕분에 공통 로직을 만들어서 사용할 수 있게 되었다.
리플렉션을 사용하면 애플레케이션을 동적으로 유연하게 만들 수 있다.
하지만 리플렉션 기술은 런타임에 동작하기 때문에 컴파일 시점에 오류를 잡을 수 없다.