모종닷컴

스프링 프록시 못된 녀석 - 2편 (프록시 생성 방식) 본문

Programming/Spring

스프링 프록시 못된 녀석 - 2편 (프록시 생성 방식)

모종 2022. 7. 17. 17:17
반응형

 

지난 1편에서는 리팩토링 중 스프링 프록시와 관련한 문제를 경험하였고 이에 대한 해결책 3가지를 알아보았습니다. 이번 포스트에서는 스프링이 프록시를 생성하는 방법 2가지에 대해 알아보는 시간을 가지려고 합니다. 그리고 스프링 부트에서 설정된 프록시 생성방법에 대해 설명드리겠습니다.

프록시란?

프록시를 검색해보면 아래와 같은 의미를 지니고 있음을 볼 수 있습니다.

어떤 일을 대신 맡아서 행하는 걸 뜻한다는 걸 알수 있습니다. 저희가 흔하게 듣는 프록시 서버가 바로 이러한 뜻입니다. 그리고 지금 포스트하려는 스프링에서 프록시는 어떠한 A객체의 일을 대신 위임받아 행하는 객체라고 이해해주시면 될 것 같습니다.

생성된 프록시의 다짐

스프링에서 프록시 생성 방법

스프링에서 프록시를 생성하는 방법은 크게 2가지가 있습니다. CGLIB를 이용한 프록시 생성과 JDK Dynamic Proxy를 이용한 프록시 생성 두 가지가 존재합니다. 스프링에서 프록시를 어떻게 생성할지 결정하는 방법은 프록시를 생성하려는 대상 객체의 인터페이스 유무에 따라 결정되었습니다. 예를 들어 인터페이스가 있는 객체의 경우에는 JDK Dynamic Proxy를 이용하여 프록시를 생성하고, 대상 객체가 인터페이스가 없는 경우라면 CGLIB 를 이용하여 생성하였습니다. 

JDK Dynamic Proxy

대상 객체가 인터페이스를 구현하고 있다면 스프링은 자바 리플렉션 API가 제공하는 java.lang.reflect.Proxy를 이용하여 프록시 객체를 생성합니다. 이 때 생성된 프록시 객체는 대상 객체와 동일한 인터페이스를 구현하게 되죠. 인터페이스에 정의 되어 있지 않은 메서드에 대해서는 aop가 적용되지 않습니다.

CGLIB

Code Generation Library 라고 불립니다. 이름에서 알 수 있듯이 코드 생성과 관련된 써드파티 라이브러리입니다. CGLIB는 런타임 시점에 대상 클래스의 하위 클래스를 생성시킵니다. 그리고 스프링은 이 생성된 하위 클래스에게 대상 객체의 메서드를 호출하는 부분들을 위임합니다. 그리고 이 하위클래스는 데코레이터 패턴으로 동작하기에 로깅을 남긴다던지, 트랜잭션을 연다던지 등의 행동을 취하고 실제 대상 객체를 호출하죠.

위에서 언급했듯이 CGLIB는 대상 객체를 상속받아 정의하면서 데코레이터 패턴으로 만들어지는데, 해당 객체의 메서드가 만약 final 이라면 이 메서드를 재정의할 수 없게 됩니다. 이러한 제약으로 인해 데코레이터가 적용이 되지 않습니다(로깅, 트랜잭션, 비동기...)

1편을 보고오셨던 분들에게 추가적으로 첨언하자면 all-open 을 적용받지 못한 코틀린의 코드는 자바로 변환되면서 final 키워드가 붙기에 우리가 evaluate()를 호출했을 때 Logging Aspect가 실행이 안되었던 것입니다. 또한 하위 클래스를 만들 때 필드는 재정의하지 않기 때문에 모두 null로 초기화합니다. 그래서 저희가 evaluate() 실행을 했을때 이 로직을 실행한 주체가 대상 객체가 아니라 프록시 객체였기 때문에 null인 필드에 접근하려해서 에러가 났던 것이었고요.

그럼 이제 제가 제안했던 첫 번째와 두 번째 해결방안이 이해되시겠죠?

스프링 부트의 프록시 생성

이 내용을 듣고나서 확인을 해보기 위해 저는 interface를 만들고 jdk dynamic proxy로 프록시가 생성이 되는지 확인을 해봤습니다. 근데 참 어이없게도 인터페이스를 만들어 테스트해봤음에도 여전히 CGLIB를 이용하여 프록시를 생성하고 있는 것을 알게되었습니다. 그러다 알게된 것이 proxy-target-class 라는 속성이었습니다. 스프링 부트에서는 이 속성이 기본적으로 true로 지정합니다. 이 속성이 무엇이냐면 스프링에서 프록시를 만들 때 CGLIB로만 생성하도록 하는 설정이었습니다. 따라서 스프링부트에서는 인터페이스의 유무를 떠나 무조건 CGLIB를 이용하여 프록시를 만들도록 되어있습니다. 

이제 세 번째 해결방안까지 모두 이해되셨을까요?

포스트 요점정리

  • 스프링의 프록시 생성 방법에는 JDK Dynamic Proxy를 이용하는 방법과 CGLIB 를 이용하는 방법이 있다.
  • CGLIB를 이용하여 만들어진 프록시 객체는 필드가 null로 채워지며, final 키워드가 붙은 메서드를 오버라이딩할 수 없다.
  • 스프링부트에서는 기본적으로 CGLIB 만을 이용하여 프록시를 생성하도록 설정되어있다.

여기까지 글을 읽어주셔서 감사합니다. 다음 포스트에서는 Intellij Inspection 을 이용하여 이러한 실수를 줄이고자 합니다.

 

반응형