Design Pattern

[디자인 패턴] JDK 동적 프록시와 CGLIB

꾸준함. 2021. 12. 11. 17:50

개요

기존에 정리한 프록시 패턴(https://jaimemin.tistory.com/2016)을 통해 기존 코드를 변경하지 않고 부가적인 기능을 추가할 수 있는 장점이 있었지만 비슷한 구조를 갖는 프록시 클래스를 계속 생성해야 하는 단점이 있었습니다.

클래스를 계속 생성하면 관리 포인트가 늘어난다는 것이 단점인데 이는 자바에서 제공하는 JDK 동적 프록시 기술과 CGLIB와 같은 프록시 생성 오픈소스 기술을 통해 해결 가능합니다.

따라서, 이번 게시글에서는 JDK 동적 프록시 기술과 CGLIB를 소개할 것이며 추가적으로 프록시 기술을 이해하기 위해 알아야 하는 리플렉션 기술 또한 정리해볼 것입니다.

 

1. 리플렉션

리플렉션 기술을 보다 쉽게 이해하기 위해 우선 예제 코드부터 작성해보겠습니다.

 

리플렉션 적용 전 예제 코드

 

 

 

위 코드를 보면 공통 로직 구조와 공통 로직 2 구조는 동일한데 호출하는 메서드만 다르다는 것을 알 수 있습니다.

즉, 현재는 두 구조의 흐름이 동일하지만 호출하는 메서드가 다르다는 이유로 똑같은 코드를 반복해서 작성해야 합니다.

호출하는 메서드만 동적으로 처리할 수 있다면 위 두 로직을 하나의 함수로 뽑아 리팩토링 할 수 있는데 이때 사용하는 기술이 리플렉션 기술입니다.

 

* 리플렉션: 클래스나 함수의 메타정보를 사용해서 동적으로 호출하는 메서드를 변경 가능하도록 하는 기술

 

리플렉션 적용한 예제 코드

 

 

 

 

위 코드를 보면 기존에 반복적으로 작성해야 하는 구조를 하나의 메서드로 뺀 것을 확인할 수 있습니다.

리플렉션을 적용하기 위해 사용된 메서드 설명은 주석으로 작성했고 여기서 핵심은 각각의 메서드를 직접 호출하지 않고 메서드 정보를 매개변수로 받고 클래스 또한 최상위 객체인 Object 객체를 매개변수로 받아 클래스나 메서드 정보를 동적으로 변경할 수 있다는 것입니다.

기존에 직접 호출한 functionA()와 functionB() 메서드들이 Method로 대체되었고 이를 통해 두 공통 로직을 하나의 함수로 뽑아서 합칠 수 있었습니다.

 

1.1 리플렉션 기술 정리

 

장점

  • 클래스와 메서드의 메타정보를 받아 클래스와 메서드 정보를 동적으로 변경 가능
    • 애플리케이션을 동적으로 유연하게 만들 수 있어 코드 리팩토링에 유용함 

단점

  • 메타정보를 받을 때 문자열로 받기 때문에 컴파일 시점에 오류를 잡을 수 없음
    • 오타가 발생하더라도 런타임 에러 로그를 통해서만 파악 가능
    • 에러 파악하는데 시간을 쏟아부을 확률이 큼

 

1.2 리플렉션 결론

앞서 단점에서도 언급했듯이 리플렉션을 사용할 경우 오류가 발생했을 때 컴파일 시점이 아닌 런타임 시점에서 오류가 났다는 것을 파악할 수 있습니다.

따라서, 리플렉션 기술을 실제로 적용하는 것은 추천드리지 않습니다.

다만, 해당 기술을 이해해야 앞으로 설명할 동적 프록시 기술을 이해하는데 도움이 될 것입니다.

 

2. JDK 동적 프록시

동적 프록시 기술을 적용하면 기존의 데코레이터 패턴처럼 개발자가 직접 프록시 클래스를 생성하지 않아도 된다는 장점이 있습니다.

이 또한 이해하기 쉽게 예제 코드를 먼저 소개하겠습니다.

 

 

 

* InvocationHandler 인터페이스는 JDK Proxy가 제공하는 인터페이스

* 따라서, JDK Proxy를 적용하기 위해서는 InvocationHandler 인터페이스를 구현해야 함

 

2.1 생성된 JDK 동적 프록시

  • proxyClass가 동적으로 생성된 프록시 클래스 정보
    • 개발자가 만든 클래스가 아니라 JDK 동적 프록시가 이름 그대로 동적으로 만들어준 프록시
    • 해당 프록시가 TimeCalculateInvocationHandler 로직 실행
    • class com.sun.proxy.$Proxy1과 같은 이름으로 생성됨
  • targetClass가 개발자가 만든 클래스

 

2.2 실행 순서

  • 클라이언트가 JDK 동적 프록시의 function() 메서드 호출
  • JDK 동적 프록시는 InvocationHandler.invoke() 메서드 호출
    • TimeCalculateInvocationHandler가 구현체로 있으므로 실질적으로는 TimeCalculateInvocationHandler.invoke() 메서드가 호출됨
  • TimeCalculateInovcationHandler가 내부 비즈니스 로직을 수행하고, method.invoke(target, args) 메서드를 호출하여 target인 실제 객체인 ExampleImpl를 호출
  • ExampleImpl 인스턴스의 function() 메서드가 호출
  • ExampleImpl 인스턴스의 function() 메서드 실행이 끝난 뒤 TimeCalculateInvocationHandler로 응답 돌아옴.
    • 시간 로그 출력하고 결과 반환

 

2.3 JDK 동적 프록시 정리

 

장점

  • 예제에서 볼 수 있다시피 ExampleImpl, TemporaryImpl 클래스 모두 각각의 프록시를 생성하지 않아도 됨
    • 적용 대상만큼 프록시 객체를 생성하지 않아도 되기 때문에 관리 포인트가 줄어듦
    • 같은 부가 기능 로직을 한 번만 개발해서 공통으로 적용 가능
    • 적용 대상이 N 개여도 동적 프록시를 통해 생성하고 각각 필요한 InvocationHandler만 생성해서 적용해주면 됨
    • 정리하자면 프록시 클래스를 계속 만들어야 하는 문제점도 해결하고, 부가 기능 로직도 하나의 클래스에 모아서 SOLID 원칙의 SRP 즉, 단일 책임 원칙도 지킬 수 있음

단점

  • JDK 동적 프록시의 경우 인터페이스가 필수
    • 클래스만 있는 경우 JDK 동적 프록시 적용 불가능

 

3. CGLIB

CGLIB는 JDK 동적 프록시의 단점을 해결해줄 수 있는 오픈소스 라이브러입니다.

즉, 클래스만 있는 경우에도 적용 가능합니다.

 

3.1 CGLIB 소개

  • Code Generator Library의 줄임말
  • 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술 제공
  • 구체 클래스만 가지고도 동적 프록시 생성 가능
  • 원래는 외부 라이브러리지만 스프링 프레임워크가 스프링 내부 소스 코드에 포함시킴
    • 스프링을 사용한다면 gradle이나 maven에 따로 추가하지 않아도 됨
  • JDK 동적 프록시가 InvocationHandler를 제공했다시피 CGLIB는 MethodInterceptor를 제공
  • 직접 사용할 일은 거의 없지만 개념을 파악할 필요성은 있음

 

3.2 CGLIB 예제 코드

 

 

 

테스트 코드 부연 설명

  • CGLIB는 Enhancer 객체를 사용해 프록시 생성
    • enhancer.setSuperclass(ExampleWithoutInterface.class) 메서드를 통해 구체 클래스를 상속받아 프록시 생성
    • enhancer.setCallback(new TimeCalculateMethodInterceptor(target)) 메서드를 통해 프록시에 적용할 실행 로직 할당
    • enhancer.create() 메서드를 통해 프록시 생성
      • setSuperclass 메서드를 통해 구체 클래스를 상속받아 프록시 생성

 

3.3 생성된 CGLIB 동적 프록시

  • JDK 동적 프록시와 마찬가지로 예제 코드 내 proxyClass가 CGLIB가 생성한 프록시
    • 대상클래스$$EnhancerByCGLIB$$임의코드와 같은 이름으로 생성됨

 

3.4 CGLIB 정리

 

장점

  • JDK 동적 프록시와 달리 인터페이스가 없는 구체 클래스에도 적용 가능

단점

  • 클래스 기반 프록시는 상속을 사용하기 때문에 상속에 따른 제약 존재
    • 부모 클래스의 생성자를 항상 확인해야 함
      • CGLIB는 자식 클래스를 동적으로 생성하기 때문에 기본 생성자 필요
    • 클래스에 final 키워드 붙으면 상속 불가능
    • 메서드에 final 키워드 붙으면 해당 메서드 오버 라이딩 불가능

 

참고

인프런 스프링 핵심 원리 - 고급편 (김영한 강사님)

반응형