JDK 동적 프록시

slee2·2022년 3월 14일
0

소개

이전에서 말했듯이 프록시는 클래스 개수만큼 만들어야 한다는 불편함이 있었다.
쉽게 이야기해서 프록시의 로직은 같은데, 적용 대상만 차이가 있는 것이다.

이 문제를 해결하는 것이 동적 프록시이다.
동적 프록시 기술을 사용하면 개발자가 직접 프록시 클래스를 만들지 않아도 된다. 이름 그대로 프록시 객체를 동작으로 런타임에 개발자 대신 만들어준다.
그리고 동적 프록시에 원하는 실행 로직을 저장할 수 있다.

주의
JDK 동적 프록시는 인터페이스가 필수이다.

먼저 자바 언어가 기본으로 제공하는 JDK 동적 프록시를 알아보자.

AInterface

AImpl

BInterface

BImpl

예제 코드

JDK동적 프록시에 적용할 로직은 InvocationHandler 인터페이스를 구현해서 작성하면 된다.

Object proxy: 프록시 자신
Method method: 호출할 메서드
Object[] args: 메서드를 호출할 때 전달한 인수

TimeInvocationHandlerInvocationHandler 인터페이스를 구현한다.
Object target: 동적 프록시가 호출할 대상
method.invoke(target, args): 리플렉션을 사용해서 target 인스턴스의 메서드를 실행한다. args는 메서드 호출시 넘겨줄 인수이다.

new TimeInvocationHandler(target): 동적 프록시에 적용할 핸들러 로직이다.
Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler)

  • 동적 프록시는 java.lang.reflect.Proxy를 통해서 생성할 수 있다.
  • 클래스 로더 정보, 인터페이스, 핸들러 로직을 넣어주면 된다. 그러면 해당 인터페이스를 기반으로 동적 프록시를 생성하고 그 결과를 반환한다.

  1. 클라이언트는 JDK 동적 프록시의 call() 을 실행한다.
  2. JDK 동적 프록시는 InvocationHandler.invoke() 를 호출한다. TimeInvocationHandler 가 구현체로 있으로 TimeInvocationHandler.invoke() 가 호출된다.
  3. TimeInvocationHandler 가 내부 로직을 수행하고, method.invoke(target, args) 를 호출해서 target 인 실제 객체( AImpl )를 호출한다.
  4. AImpl 인스턴스의 call() 이 실행된다.
  5. AImpl 인스턴스의 call() 의 실행이 끝나면 TimeInvocationHandler 로 응답이 돌아온다. 시간 로그를 출력하고 결과를 반환한다.

dynamicA()dynamicB() 둘을 동시에 함께 실행하면 JDK 동적 프록시가 각각 다른 동적 프록시 클래스를 만들어주는 것을 확인할 수 있다.

proxyClass=class com.sun.proxy.$Proxy1 //dynamicA
proxyClass=class com.sun.proxy.$Proxy2 //dynamicB

이제 적용 대상이 100개여도 동적 프록시를 통해서 생성하고, 각각 필요한 InvocationHandler 만 만들어서 넣어주면 된다.
결과적으로 프록시 클래스를 수 없이 만들어야 하는 문제도 해결하고, 부가 기능 로직도 하나의 클래스에 모아서 단일 책임 원칙(SRP)도 지킬 수 있게 되었다.

적용1

이제 어플리케이션에 적용해보자.
동적 프록시는 인터페이스가 필수이므로 V1에만 적용가능하다.

LogTraceBasicHandlerInvocationHandler 인터페이스를 구현해서 JDK 동적 프록시에서 사용된다.
private final Object target: 프록시가 호출할 대상이다.
String message = method.getDeclaringClass().getSimpleName() + "." ...
LogTrace에 사용할 메세지. OrderController.request() 와 같은 내용을 만든다.

이제 수동 빈 등록을 설정하자.

그림으로 정리

문제는 no-log 도 로그가 남아버린다. 모든 메서드에 적용되버리기 때문이다.
이를 해결하기위한 적용2 출시임박

적용2


PatternMatchUtils.simpleMatch(...)

  • xxx: xxx가 정확히 매칭되면 참
  • xxx*: xxx로 시작하면 참
  • *xxx: xxx로 끝나면 참
  • *xxx*: xxx가 있으면 참
    String[] patterns: 적용할 패턴은 생성자를 통해서 외부에서 받는다.

이렇게 추가해주면 특정 조건일때만 로그가 남도록 설정된다.

한계

동적 프록시는 인터페이스가 필수이다.
그럼 클래스일때는 어떻게 해야할까?
이때는 CGLIB를 이용해야 한다.

0개의 댓글