[TIL] [Spring AOP] [실무 주의사항] [프록시 기술과 한계] - 의존관계 주입 (feat. 타입 캐스팅)

SlowAnd·2024년 1월 6일
0

Spring AOP

목록 보기
5/5
post-thumbnail

[문제 상황] 구체 클래스 주입상황


이 코드를 보고 MemberServiceImpl memberServiceImpl은 잘 작동 안할 가능성이 있겠다는...
"이거 문제일 수도 있겠는데?" 라는 생각이 든다.

이 생각이 든 후,
properties에서 어떤 프록시를 사용하는지 확인하러 간다.

해결


"아. JDK 사용하는구나. 문제네.."

MemberServiceImpl memberServiceImpl 이 문제를 해결하기 위해 properties 값에서 target-class 값을 true로 바꿨다.
(target은 실제 인스턴스를 가리킨다)
이제 JDK 대신 CGLIB 프록시를 사용하여 문제를 해결했다.


[필요한 배경지식] 프록시의 타입 캐스팅

[타입 캐스팅] JDK 프록시

JDK Proxy는 추상 타입만 알지.구체 클래스는 모른다.
그러니 구체 클래스로 타입 캐스팅을 시도하면 ClassCastException.class 예외가 발생한다.

[타입 캐스팅] CGLIB 프록시 캐스팅

CGLIB Proxy는 구체 클래스를 기반으로 프록시를 생성하기 때문에
구체 클래스의 인터페이스 타입으로도 캐스팅 할 수 있다.

[한계] - 의존관계 주입

테스트 또는 여러가지 이유로 AOP 프록시가 구체 클래스가 적용된 구체 클래스를 직접 의존관계 주입 받아야 하는 경우가 있을수 있다.
이때는 CGLIB을 통해 구체 클래스 기반으로 AOP 프록시를 적용하면된다

[한계] - CGLIB 단점

스프링에서 CGLIB는 구체 클래스를 상속 받아서 AOP 프록시를 생성할 때 사용한다.
CGLIB는 구체 클래스를 상속 받기 때문에 다음과 같은 문제가 있다.

CGLIB 구체 클래스 기반 프록시 문제점

  • 대상 클래스에 기본 생성자 필수
  • 생성자 2번 호출 문제
  • final 키워드 클래스, 메서드 사용 불가

[1] 대상 클래스에 기본 생성자 필수

CGLIB는 구체 클래스를 상속 받는다.
자바 언어에서 상속을 받으면 자식 클래스의 생성자를 호출할 때 자식 클래스의 생성자에서 부모 클래스의 생성자도 호출해야 한다.
(이 부분이 생략되어 있다면 자식 클래스의 생성자 첫줄에 부모 클 래스의 기본 생성자를 호출하는 super() 가 자동으로 들어간다.)
이 부분은 자바 문법 규약이다.

CGLIB를 사용할 때 CGLIB가 만드는 프록시의 생성자는 우리가 호출하는 것이 아니다. CGLIB 프록시는 대상 클래스를 상속 받고, 생성자에서 대상 클래스의 기본 생성자를 호출한다.

따라서 대상 클래스에 기본 생성자를 만들어야 한다. (기본 생성자는 파라미터가 하나도 없는 생성자를 뜻한다. 생성자가 하나도 없으면 자동으로 만들어진다.)

[2] 생성자 2번 호출 문제

CGLIB는 구체 클래스를 상속 받는다.
자바 언어에서 상속을 받으면 자식 클래스의 생성자를 호출할 때 부모 클래스의 생성자도 호출해야 한다.

그런데 왜 2번일까?

  1. 실제 target의 객체를 생성할 때
  2. 프록시객체를생성할때부모클래스의생성자호출

[3] final 키워드 클래스, 메서드 사용 불가

final 키워드가 클래스에 있으면 상속이 불가능하고, 메서드에 있으면 오버라이딩이 불가능하다.
CGLIB는 상속을 기반 으로 하기 때문에 두 경우 프록시가 생성되지 않거나 정상 동작하지 않는다.

프레임워크 같은 개발이 아니라 일반적인 웹 애플리케이션을 개발할 때는 final 키워드를 잘 사용하지 않는다. 따라서 이 부분이 특별히 문제가 되지는 않는다.


[정리]

JDK 동적 프록시는 대상 클래스 타입으로 주입할 때 문제가 있고,
CGLIB는 대상 클래스에 기본 생성자 필수, 생성자 2번 호출 문제가 있다.

[해결] 스프링의 해결책

스프링 3.2, CGLIB를 스프링 내부에 함께 패키징

(별도의 라이브러리 추가 없이 CGLIB를 사용할 수 있게 되었다.)

CGLIB 기본 생성자 필수 문제 해결

스프링 4.0부터 CGLIB의 기본 생성자가 필수인 문제가 해결되었다.
objenesis 라는 특별한 라이브러리를 사용해서 기본 생성자 없이 객체 생성이 가능하다. 참고로 이 라이브러리는 생성자 호출 없이 객체를 생성할 수 있게 해준다.

생성자 2번 호출 문제

스프링 4.0부터 CGLIB의 생성자 2번 호출 문제가 해결되었다. 이것도 역시 objenesis 라는 특별한 라이브러리 덕분에 가능해졌다. 이제 생성자가 1번만 호출된다.

스프링 부트 2.0 - CGLIB 기본 사용

스프링 부트 2.0 버전부터 CGLIB를 기본으로 사용하도록 했다.
이렇게 해서 구체 클래스 타입으로 의존관계를 주입하는 문제를 해결했다.
스프링 부트는 별도의 설정이 없다면 AOP를 적용할 때 기본적으로 proxyTargetClass=true 로 설정해서 사용한 다.
따라서 인터페이스가 있어도 JDK 동적 프록시를 사용하는 것이 아니라 항상 CGLIB를 사용해서 구체클래스를 기반으 로 프록시를 생성한다.
물론 스프링은 우리에게 선택권을 열어주기 때문에 다음과 깉이 설정하면 JDK 동적 프록시도 사용할 수 있다

CGLIB의 남은 문제라면 final 클래스나 final 메서드가 있는데, AOP를 적용할 대상에는 final 클래스나 final 메서드를 잘 사용하지는 않으므로 이 부분은 크게 문제가 되지는 않는다.


참조
스프링 핵심 원리 - 고급편

0개의 댓글