본문 바로가기
프로그래밍/kotlin

[KOTILIN] JDK 동적 프록시

by 뜨끔쓰 2022. 9. 25.
728x90
728x90
인프런의 김영한님의 강의 스프링 핵심 원리 - 고급편을 학습하며 정리한 글입니다.

 

스프링 핵심 원리 - 고급편 (인프런)

 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

JDK 동적 프록시란?

지금까지 학습한 결과물로 프록시를 적용하기 위해서는 적용 숫자 만큼 많은 프록시 클래스를 만들어야한다.

EX) 적용대상이 100개이면 프록시 클래스도 100개나 만들어야 함.

 

프록시 적용하는 코드를 보다보면 코드의 흐름은 거의 같고, 프록시를 어디에 적용할지에 대한 정도의 차이만 있다. 

정리하면 프록시의 로직은 같은데, 적용하는 대상의 차이만 있다.

 

그렇기 때문에 이것을 해결할 수 있는 기능이 바로 동적 프록시이다.

 

주의
JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어 주기때문에 인터페이스가 필수.

 

이제 코드로 한번 알아봅시다.

 

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

 

제공되는 파라미터는 다음과 같다.

  • proxy: Any => 프록시 자신
  • method: Method => 호출한 메서드
  • args: Array<out Any>? => 메서드를 호출할 때 전달한 인수

 

TimeInvodationHandler
class TimeInvocationHandler(
    private val target: Any,    //동적 프록시가 호출할 대상
): InvocationHandler {
    private val log = LoggerFactory.getLogger(TimeInvocationHandler::class.java)

    override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
        log.info("TimeProxy 실행")
        val startTime = System.currentTimeMillis()

        /* 리플렉션을 사용하여 target 인스턴스의 메서드를 실행, args는 메서드 호출시 넘겨줄 인수 */
        val result = method.invoke(target, *(args ?: arrayOfNulls<Any>(0)))

        val endTime = System.currentTimeMillis()
        val resultTime = endTime - startTime
        log.info("TimePrxoy 종료 resultTime={}", resultTime)
        return result
    }
}

 

이렇게 handler를 작성한 뒤 테스트 코드로 JDK 동적 프록시를 사용해보도록합시다.

 

dynamicA
    @Test
    fun dynamicA(){
        val target: AInterface = AImpl()

        val handler: InvocationHandler = TimeInvocationHandler(target)

        val proxy =
            Proxy.newProxyInstance(AInterface::class.java.classLoader, arrayOf(AInterface::class.java), handler) as AInterface
        proxy.call()
        log.info("targetClass={}", target.javaClass)
        log.info("proxyClass={}", proxy.javaClass)
    }

이렇게 테스트코드를 작성하여 테스트를 실행해봅시다.

dynamicA 메서드 호출 결과

이렇게하면 따로 프록시 클래스를 만들지 않고도 프록시가 정상 호출된 것을 확인할 수 있습니다.

 

생성된 JDK 동적 프록시

proxyClass=class.jdk.proxy2.$proxy13이 부분이 동적으로 생성된 프록시 클래스 정보이다.

이것은 우리가 만든 클래스가 아니라 JDK 동적 프록시가 이름 그대로 동적으로 만들어준 프록시이다. 이
프록시는 TimeInvocationHandler 로직을 실행한다.

 


 

실행순서

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

 


정리

이렇게 JDK 동적 프록시를 사용하여 동적으로 만들어 사용하면 TimeInvocationHanlder기능을 사용하는 다른 테스트 클래스를 작성한다하여도 적용대상 만큼 프록시 객체를 만들지 않아도 된다. 결과적으로 프록시 클래스를 수 없이 만들어야 하는 문제도 해결하고, 부가 기능 로직도 하나의 클래스에 모아서 단일 책임 원칙(SRP)도 지킬 수 있게 되었다.

728x90
반응형

댓글