[Spring/AOP] JDK Dynamic Proxy vs CGLIB Proxy
목차
- JDK Dynamic Proxy vs CGLIB Proxy
- JDK Dynamic Proxy
- CGLIB Proxy
- JDK Dynamic Proxy vs CGLIB Proxy
- Spring AOP와 proxy
- Spring에서 사용하는 proxy
- Spring Boot에서 사용하는 proxy
- 왜 진작 CGLIB을 이용하지 않았을까?
1. JDK Dynamic Proxy vs CGLIB Proxy 💣
Spring에서는 프록시를 이용해 AOP를 지원한다. (AOP를 모른다면 이 링크를 참고하자. Spring의 대표적인 AOP인 @Transactional과 AOP에 대한 간략한 설명을 담았다.) JDK Dynamic Proxy, CGLIB 두 가지 방식을 사용하는데 왜 프록시 종류가 2가지나 있는지, 언제 어떤 것을 사용하는지 알아보자.
💥 JDK Dynamic Proxy
- proxy 생성을 위해 interface가 필요하다.
- Refelction을 이용해 proxy를 생성한다.
Proxy.newProxyInstance(
클래스로더,
new Class[] { 타깃의인터페이스.class },
new InvocationHandler의구현체()
);
🔻 예제 더 자세히 보기
public class ProxyTest {
// 인터페이스 생성
interface Target {
void print();
}
// 프록시를 적용할 구현체 생성
class TargetImpl implements Target {
@Override
public void print() {
System.out.println("Hello!");
}
}
// InvocationHandler 구현체 생성
class TestHandler implements InvocationHandler {
private Object target;
public TestHandler(final Object target) {
this.target = target;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
System.out.println("######## ---> method start ");
Object result = method.invoke(target, args);
System.out.println("######## ---> method end");
return result;
}
}
// Proxy 생성 및 호출
@Test
void test() {
Target proxy = (Target) Proxy.newProxyInstance(
ProxyTest.class.getClassLoader(),
new Class[]{Target.class},
new TestHandler(new TargetImpl())
);
proxy.print();
}
}
💥 CGLIB Proxy
- 바이트 코드를 조작해 프록시 생성
- Hibernate의 lazy loading, Mockito의 모킹 메서드 등에서 사용
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(타깃.class);
enhancer.setCallback(new MethodInterceptor의구현체());
Object proxy = enhancer.create();
🔻 예제 더 자세히 보기
public class ProxyTest {
// 프록시를 적용할 구현체 생성
class TargetImpl {
public void print() {
System.out.println("Hello!");
}
}
// Proxy 생성 및 호출
@Test
void test() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("######## ---> method start ");
Object result = proxy.invoke(obj, args);
System.out.println("######## ---> method end");
return result;
});
TargetImpl proxy = (TargetImpl) enhancer.create();
proxy.print();
}
}
💥 JDK Dynamic Proxy vs CGLIB Proxy
2. Spring AOP와 proxy 🔮
💜 Spring에서 사용하는 proxy
Spring AOP에서는 기본적으로 JDK dynamic proxy를 사용한다. 다만, JDK dynamic proxy는 인터페이스가 있어야만 사용할 수 있기 때문에 인터페이스가 없는 경우에는 CGLIB proxy를 사용한다.
Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies.
ㅡ Spring Docs / 5.3. AOP proxies
If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.
ㅡ Spring Docs / 5.8. Proxying Mechanisms
💜 Spring Boot에서 사용하는 proxy
Spring Boot에서는 설정을 통해 JDK dynamic proxy를 이용할지, CGLIB proxy를 이용할지 선택할 수 있다. Spring Boot 2.0부터는 디폴트 설정이 CGLIB proxy를 사용하도록 바뀌었다. (자세한 내용은 Spring Cloud 공식 사이트의 포스팅을 참고하자)
💜 왜 진작 CGLIB을 이용하지 않았을까?
왜 Spring은 Spring Boot 2.0을 거치고 나서야 CGLIB proxy를 디폴트로 설정해둔걸까? JDK dynamic proxy는 무조건 인터페이스가 있어야하고 Reflection을 사용하기 때문에 성능이 비교적 느리다고 알려져있다. 그렇다면 진작에 CGLIB을 디폴트로 설정해두었으면 좋았을걸 왜 나중에 적용했을까?
1. 오픈소스
일단 CGLIB가 오픈 소스였던 것이 문제다. 신뢰하고 사용해도 될 정도로 검증할 시간이 필요했고 Spring에 내장되어있지 않아 별도로 의존성을 추가해야한다는 문제도 있었다. Spring 3.2버전부터 spring-core로 리패키징된 상태라 의존성을 추가할 필요가 없어졌다.
2. 디폴트 생성자 필요 & 생성자 중복 호출
기존에는 CGLIB를 이용하면 디폴트 생성자가 필요했고 원본 객체의 생성자를 두 번 호출했다. 실제 빈을 생성할 때 한번, 프록시 생성을 위해 한번더. Spring 4.3부터는 Objenesis 라이브러리를 통해 생성되기 때문에 해당 현상이 개선되었다.
CGLIB-based proxy classes no longer require a default constructor. Support is provided via the objenesis library which is repackaged inline and distributed as part of the Spring Framework. With this strategy, no constructor at all is being invoked for proxy instances anymore.
ㅡ Spring 4.3.13.RELEASE / 3.6 Core Container Improvements
참고
- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction
- https://www.springcloud.io/post/2022-01/springboot-aop/#gsc.tab=0
- https://stackoverflow.com/questions/29650355/why-in-spring-aop-the-object-are-wrapped-into-a-jdk-proxy-that-implements-interf
- https://docs.spring.io/spring-framework/docs/4.3.13.RELEASE/spring-framework-reference/htmlsingle/
- https://www.baeldung.com/cglib