본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!
🎈 리플렉션 사용 전
void reflection(){
Hello target = new Hello();
//공통 로직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 종료
}
@Slf4j
static class Hello{
public String callA(){
log.info("callA");
return "A";
}
public String callB(){
log.info("callB");
return "B";
}
}
reflection
에서 두 공통 로직내에서 서로 다른 점은 호출하는 메서드가 다르다는 점이다.target.callA()
, target.callB()
그럼 메서드 호출 부분만 동적으로 처리할 수 있다면 반복되는 코드의 양을 줄일 수 있겠구나!
🎈 리플렉션 사용 후
void reflection() 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);
}
Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello")
: 클래스 메타 정보를 획득한다. 이때 클래스 위치를 적어주고, 내부 클래스는 구분을 위해 $
을 사용한다.
classHello.getMethod( {메소드명} )
: 클래스 메타 정보를 이용해 메소드 메타 정보를 획득한다. 파라미터로 호출하려는 메서드 이름을 적어준다.
호출할 메서드 정보와 인스턴스를 dynamicCall
에게 전달하여 공통 로직을 실행한다.
method.invoke( {인스턴스} )
: 인스턴스를 넘겨주며, 해당 인스턴스의 메서드를 실행한다.위 예제에서 배운 리플렉션을 이용해 JDK 동적 프록시를 사용해보자
public interface AInterface {
String call();
}
@Slf4j
public class AImpl implements AInterface {
@Override
public String call() {
log.info("A 호출");
return "a";
}
}
InvocationHandler
인터페이스를 구현해 핸들러 객체를 리턴받는다.invoke(Object proxy, Method method, Object[] args)
Object proxy
: 프록시 자신Method method
: 호출할 메서드Object[] args
: 메서드를 호출할 때 전달한 인수@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime-startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
method.invoke(target, args)
: 리플렉션을 사용해서 target
인스턴스의 메서드를 실행한다. args
는 메서드 호출시 넘겨줄 인수이다.🎈 실행 테스트
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
)를 통해 만들 수 있다.ClassLoader loader
: 클래스 로더 정보Class<?>[] interfaces
: 인터페이스InvocationHandler h
: 핸들러 로직void dynamicA(){
AInterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(),new Class[]{AInterface.class}, handler);
proxy.call();
}
//실행결과
TimeInvocationHandler - TimeProxy 실행
AImpl - A 호출
TimeInvocationHandler - TimeProxy 종료 resultTime=0
CGLIB
라는 바이트코드를 조작하는 라이브러리를 사용해야 한다.인프런의 '스프링 핵심 원리 고급편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.