You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@Autowired MemberService memberService: 이 부분은 문제가 없다. JDK Proxy
MemberService인터페이스를 기반으로 만들어진다. 따라서 해당 타입으로 캐스팅 할 수 있다
MemberService = JDK Proxy가 성립한다.
@Autowired MemberServiceImpl memberServiceImpl: 문제는 여기다. JDK Proxy는MemberService인터페이스를 기반으로 만들어진다. 따라서MemberServiceImpl타입이 뭔지 전혀 모른 다. 그래서 해당 타입에 주입할 수 없다.
MemberServiceImpl = JDK Proxy 가 성립하지 않는다.
✅ CGLIB 프록시를 구체 클래스 타입에 주입
이번에는 JDK 동적 프록시 대신에 CGLIB를 사용해서 프록시를 적용해보자. 다음과 같이 주석을 반대로 걸어보자.
//@SpringBootTest(properties = {"spring.aop.proxy-target-class=false"}) //JDK 동적 프록시, DI 예외 발생@SpringBootTest(properties = {"spring.aop.proxy-target-class=true"}) //CGLIB 프록시, 성공
@Autowired MemberService memberService: CGLIB Proxy는MemberServiceImpl구체 클래스 를 기반으로 만들어진다.MemberServiceImpl은MemberService인터페이스를 구현했기 때문에 해당 타 입으로 캐스팅 할 수 있다.
MemberService = CGLIB Proxy 가 성립한다.
@Autowired MemberServiceImpl memberServiceImpl: CGLIB Proxy는MemberServiceImpl 구체 클래스를 기반으로 만들어진다. 따라서 해당 타입으로 캐스팅 할 수 있다.
MemberServiceImpl = CGLIB Proxy 가 성립한다.
7️⃣ 프록시 기술과 한계 - CGLIB
스프링에서 CGLIB는 구체 클래스를 상속 받아서 AOP 프록시를 생성할 때 사용한다.
CGLIB는 구체 클래스를 상속 받기 때문에 다음과 같은 문제가 있다.
✅ CGLIB 구체 클래스 기반 프록시 문제점
대상 클래스에 기본 생성자 필수
생성자 2번 호출 문제
final 키워드 클래스, 메서드 사용 불가
✅ 대상클래스에기본생성자필수
CGLIB는 구체 클래스를 상속 받는다. 자바 언어에서 상속을 받으면 자식 클래스의 생성자를 호출할 때 자식 클래스의 생성자에서 부모 클래스의 생성자도 호출해야 한다. (이 부분이 생략되어 있다면 자식 클래스의 생성자 첫줄에 부모 클래스의 기본 생성자를 호출하는 super() 가 자동으로 들어간다.) 이 부분은 자바 문법 규약이다.
CGLIB를 사용할 때 CGLIB가 만드는 프록시의 생성자는 우리가 호출하는 것이 아니다. CGLIB 프록시는 대상 클래스를 상속 받고, 생성자에서 대상 클래스의 기본 생성자를 호출한다. 따라서 대상 클래스에 기본 생성자를 만들어야 한다.
(기본 생성자는 파라미터가 하나도 없는 생성자를 뜻한다. 생성자가 하나도 없으면 자동으로 만들어진다.)
➡️ 생성자 2번호출문제
CGLIB는 구체 클래스를 상속 받는다. 자바 언어에서 상속을 받으면 자식 클래스의 생성자를 호출할 때 부모 클래스의생성자도 호출해야 한다.
실제 target의 객체를 생성할 때
프록시 객체를 생성할 때 부모 클래스의 생성자 호출
✅ final 키워드클래스, 메서드사용불가
final 키워드가 클래스에 있으면 상속이 불가능하고, 메서드에 있으면 오버라이딩이 불가능하다. CGLIB는 상속을 기반으로 하기 때문에 두 경우 프록시가 생성되지 않거나 정상 동작하지 않는다.
프레임워크 같은 개발이 아니라 일반적인 웹 애플리케이션을 개발할 때는 final 키워드를 잘 사용하지 않는다. 따라서 이 부분이 특별히 문제가 되지는 않는다.
8️⃣ 프록시 기술과 한계 - 스프링의 해결책
➡️ 스프링의 기술 선택 변화
스프링 3.2, CGLIB를스프링내부에함께패키징
CGLIB를 사용하려면 CGLIB 라이브러리가 별도로 필요했다.
스프링은 CGLIB 라이브러리를 스프링 내부에 함께 패키징해서 별도의 라이브러리 추가 없이 CGLIB를 사용할 수 있게 되었다. CGLIB spring-core org.springframework
➡️ CGLIB 기본생성자필수문제해결
스프링 4.0부터 CGLIB의 기본 생성자가 필수인 문제가 해결되었다.
objenesis 라는 특별한 라이브러리를 사용해서 기본 생성자 없이 객체 생성이 가능하다.
참고로 이 라이브러리는 생성자 호출 없이 객체를 생성할 수 있게 해준다.
➡️생성자 2번호출문제
스프링 4.0부터 CGLIB의 생성자 2번 호출 문제가 해결되었다.
이것도 역시 objenesis 라는 특별한 라이브러리 덕분에 가능해졌다.
이제 생성자가 1번만 호출된다
➡️스프링부트 2.0 - CGLIB 기본사용
스프링 부트 2.0 버전부터 CGLIB를 기본으로 사용하도록 했다.
이렇게 해서 구체 클래스 타입으로 의존관계를 주입하는 문제를 해결했다.
스프링 부트는 별도의 설정이 없다면 AOP를 적용할 때 기본적으로 proxyTargetClass=true로 설정해서 사용한다.
따라서 인터페이스가 있어도 JDK 동적 프록시를 사용하는 것이 아니라 항상 CGLIB를 사용해서 구체클래스를 기반으로 프록시를 생성한다.
물론 스프링은 우리에게 선택권을 열어주기 때문에 다음과 깉이 설정하면 JDK 동적 프록시도 사용할 수 있다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
5️⃣ 프록시 기술과 한계 - 타입 캐스팅
JDK 동적 프록시와 CGLIB를 사용해서 AOP 프록시를 만드는 방법에는 각각 장단점이 있다.
JDK 동적 프록시는 인터페이스가 필수이고, 인터페이스를 기반으로 프록시를 생성한다.
CGLIB는 구체 클래스를 기반으로 프록시를 생성한다.
물론 인터페이스가 없고 구체 클래스만 있는 경우에는 CGLIB를 사용해야 한다. 그런데 인터페이스가 있는 경우에는 JDK 동적 프록시나 CGLIB 둘중에 하나를 선택할 수 있다.
스프링이 프록시를 만들때 제공하는
ProxyFactory에proxyTargetClass옵션에 따라 둘중 하나를 선택해서 프록시를 만들 수 있다.proxyTargetClass=falseJDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성proxyTargetClass=trueCGLIB를 사용해서 구체 클래스 기반 프록시 생성참고로 옵션과 무관하게 인터페이스가 없으면 JDK 동적 프록시를 적용할 수 없으므로 CGLIB를 사용한다.
✅ JDK 동적 프록시 한계
✅ jdkProxy() 테스트
MemberServiceImpl타입을 기반으로 JDK 동적 프록시를 생성했다.MemberServiceImpl타입은MemberService인터페이스를 구현한다.MemberService인터페이스를 기반으로프록시를 생성한다.JDK Proxy라고 하자. 여기서memberServiceProxy가 바로JDK Proxy이다.✅ JDK 동적 프록시 캐스팅
JDK Proxy를 대상 클래스인
MemberServiceImpl타입으로 캐스팅 하려고 하니 예외가 발생한다.
왜냐하면 JDK 동적 프록시는 인터페이스를 기반으로 프록시를 생성하기 때문이다.
JDK Proxy는
MemberService인터페이스를 기반으로 생성된 프록시이다.따라서 JDK Proxy는
MemberService로 캐스팅은 가능하지만MemberServiceImpl이 어떤 것인지 전혀 알지 못한다.따라서
MemberServiceImpl타입으로는 캐스팅이 불가능하다. 캐스팅을 시도하면ClassCastException.class예외가 발생한다.✅ CGLIB 프록시
➡️ cglibProxy() 테스트
MemberServiceImpl타입으로 캐스팅하면 성공한다.MemberServiceImplMemberServiceImpl은 물론이고,MemberServiceImpl이 구현한 인터페이스인MemberService로도 캐스팅 할 수 있다.✅ 정리
6️⃣ 프록시 기술과 한계 - 의존관계 주입
JDK동적 프록시를 사용하면서 의존 관계 주입을 할 때 어떤 문제가 발생하는지 코드로 알아보자.
AOP 프록시 생성를 위해 Aspect 생성
🗒️ 코드 설명
@SpringBootTest: 내부에 컴포넌트 스캔을 포함하고 있다.MemberServiceImpl에@Component가 붙어있으므로 스프링 빈 등록 대상이 된다.properties = {"spring.aop.proxy-target-class=false"}:application.properties에설정하는 대신에 해당 테스트에서만 설정을 임시로 적용한다. 이렇게 하면 각 테스트마다 다른 설정을 손쉽게 적용할 수 있다.
spring.aop.proxy-target-class=false: 스프링이 AOP 프록시를 생성할 때 JDK 동적 프록시를 우선 생성한다. 물론 인터페이스가 없다면 CGLIB를 사용한다.
@Import(ProxyDIAspect.class): 앞서 만든 Aspect를 스프링 빈으로 등록한다.✅ JDK 동적 프록시를 구체 클래스 타입에 주입
JDK 동적 프록시를 구체 클래스 타입에 주입할 때 어떤 문제가 발생하는지 지금부터 확인해보자.
실행
spring.aop.proxy-target-class=false설정을 사용해서 스프링 AOP가 JDK 동적 프록시를 사용하도록 했다. 이렇게 실행하면 다음과 같이 오류가 발생한다.실행 결과
✅ CGLIB 프록시를 구체 클래스 타입에 주입
7️⃣ 프록시 기술과 한계 - CGLIB
스프링에서 CGLIB는 구체 클래스를 상속 받아서 AOP 프록시를 생성할 때 사용한다.
CGLIB는 구체 클래스를 상속 받기 때문에 다음과 같은 문제가 있다.
✅ CGLIB 구체 클래스 기반 프록시 문제점
✅ 대상 클래스에 기본 생성자 필수
➡️ 생성자 2번 호출 문제
✅ final 키워드 클래스, 메서드 사용 불가
8️⃣ 프록시 기술과 한계 - 스프링의 해결책
➡️ 스프링의 기술 선택 변화
CGLIB spring-core org.springframework➡️ CGLIB 기본 생성자 필수 문제 해결
objenesis라는 특별한 라이브러리를 사용해서 기본 생성자 없이 객체 생성이 가능하다.➡️생성자 2번 호출 문제
➡️스프링 부트 2.0 - CGLIB 기본 사용
물론 스프링은 우리에게 선택권을 열어주기 때문에 다음과 깉이 설정하면 JDK 동적 프록시도 사용할 수 있다.
Beta Was this translation helpful? Give feedback.
All reactions