Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Android
- 알고리즘
- springdatajpa
- Spring Boot
- Greedy
- spring
- 스프링 핵심 기능
- Exception
- jpa
- 그리디
- 인프런
- SpringBoot
- JPQL
- Proxy
- 스프링 핵심 원리
- JDBC
- kotlin
- pointcut
- Servlet
- java
- http
- 스프링
- 백준
- QueryDSL
- AOP
- 자바
- Thymeleaf
- 김영한
- transaction
- db
Archives
- Today
- Total
개발자되기 프로젝트
[동적 프록시] CGLIB - 구체클래스 기반 본문
1. CGLIB: Code Generator Library
- CGLIB는 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술을 제공하는 라이브러리이다.
- CGLIB를 사용하면 인터페이스가 없이 구체 클래스만 가지고 동적 프록시를 만들어낼 수 있다.
- CGLIB는 원래는 외부 라이브러리인데, 스프링 프레임워크가 스프링 내부 소스 코드에 포함했다.
- 따라서 스프링을 사용한다면 별도의 외부 라이브러리를 추가하지 않아도 사용할 수 있다.
- 참고로 우리가 CGLIB를 직접 사용하는 경우는 거의 없다.
- 이후에 설명할 스프링의 ProxyFactory 라는 것이 이 기술을 편리하게 사용하게 도와주기 때문에,
- 너무 깊이있게 파기 보다는 CGLIB가 무엇인지 대략 개념만 잡으면 됨.
2. 예제 - 준비
public interface ServiceInterface {
void save();
void find();
}
@Slf4j
public class ServiceImpl implements ServiceInterface{
@Override
public void save() {
log.info("save 호출");
}
@Override
public void find() {
log.info("find 호출");
}
}
@Slf4j
public class ConcreteService {
public void call(){
log.info("ConcreteService call 호출");
}
}
3. CGLIB 코드
JDK 동적 프록시에서 실행 로직을 위해 InvocationHandler 를 제공했듯이, CGLIB는 MethodInterceptor 를 제공한다.
package org.springframework.cglib.proxy;
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
- obj : CGLIB가 적용된 객체
- method : 호출된 메서드
- args : 메서드를 호출하면서 전달된 인수
- proxy : 메서드 호출에 사용
4. TimeMethodInterceptor
@Slf4j
public class TimeMethodInterceptor implements MethodInterceptor {
private final Object target;
public TimeMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = methodProxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime = {}", resultTime);
return result;
}
}
- TimeMethodInterceptor 는 MethodInterceptor 인터페이스를 구현해서 CGLIB 프록시의 실행 로직을 정의한다.
- JDK 동적 프록시를 설명할 때 예제와 거의 같은 코드이다.
- Object target : 프록시가 호출할 실제 대상
- proxy.invoke(target, args) : 실제 대상을 동적으로 호출한다.
- 참고로 method 를 사용해도 되지만, CGLIB는 성능상 MethodProxy proxy 를 사용하는 것을
권장한다.
5. TEST
@Slf4j
public class CglibTest {
@Test
void cglib(){
ConcreteService target = new ConcreteService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConcreteService.class);
enhancer.setCallback(new TimeMethodInterceptor(target));
ConcreteService proxy = (ConcreteService) enhancer.create(); //proxy 생성
log.info("targetClass = {}", target.getClass());
log.info("proxyClass = {}", proxy.getClass());
proxy.call();
}
}
- Enhancer : CGLIB는 Enhancer 를 사용해서 프록시를 생성한다.
- enhancer.setSuperclass(ConcreteService.class)
- CGLIB는 구체 클래스를 상속 받아서 프록시를 생성할 수 있다.
- 어떤 구체 클래스를 상속 받을지 지정한다.
- enhancer.setCallback(new TimeMethodInterceptor(target))
- 프록시에 적용할 실행 로직을 할당한다 -> Interceptor
- enhancer.create() : 프록시를 생성한다.
- enhancer.setSuperclass(ConcreteService.class) 에서 지정한 클래스를 상속 받아서 프록시가 만들어진다.
- JDK 동적 프록시는 인터페이스를 구현(implement)해서 프록시를 만든다. CGLIB는 구체 클래스를 상속
(extends)해서 프록시를 만든다. - 실행 결과
22:07:56.022 [main] INFO hello.proxy.cglib.CglibTest - targetClass = class hello.proxy.common.service.ConcreteService
22:07:56.029 [main] INFO hello.proxy.cglib.CglibTest - proxyClass = class hello.proxy.common.service.ConcreteService$$EnhancerByCGLIB$$25d6b0e3
22:07:56.029 [main] INFO hello.proxy.cglib.code.TimeMethodInterceptor - TimeProxy 실행
22:07:56.047 [main] INFO hello.proxy.common.service.ConcreteService - ConcreteService call 호출
22:07:56.047 [main] INFO hello.proxy.cglib.code.TimeMethodInterceptor - TimeProxy 종료 resultTime = 18
6. 의존관계
7. CGLIB 제약
- 클래스 기반 프록시는 상속을 사용하기 때문에 몇가지 제약이 있다.
- 부모 클래스의 생성자를 체크해야 한다.
--> CGLIB는 자식 클래스를 동적으로 생성하기 때문에 기본 생성자가 필요하다. - 클래스에 final 키워드가 붙으면 상속이 불가능하다.
--> CGLIB에서는 예외가 발생한다. - 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.
--> CGLIB에서는 프록시 로직이 동작하지 않는다.
8. 참고
- CGLIB를 사용하면 인터페이스가 없는 V2 애플리케이션에 동적 프록시를 적용할 수 있다.
- 그런데 지금 당장 적용하기에는 몇가지 제약이 있다.
- V2 애플리케이션에 기본 생성자를 추가하고, 의존관계를 setter 를 사용해서 주입하면 CGLIB를 적용할 수 있다.
- 하지만 다음에 학습하는 ProxyFactory 를 통해서 CGLIB를 적용하면
- 이런 단점을 해결하고 또 더 편리하기 때문에, 애플리케이션에 CGLIB로 프록시를 적용하는 것은 조금 뒤에 ..
9. GitHub : 211230 CGLIB
'인프런 > [인프런] 스프링 핵심 원리 - 고급' 카테고리의 다른 글
[프록시 팩토리] 프록시 팩토리 - 예제1 (0) | 2021.12.30 |
---|---|
[프록시 팩토리] 스프링이 지원하는 프록시 (0) | 2021.12.30 |
[동적프록시] JDK 동적프록시 - 적용2, 필터 (0) | 2021.12.30 |
[동적프록시] JDK 동적프록시 - 적용 (0) | 2021.12.30 |
[동적프록시] JDK 동적프록시 - 소개 (0) | 2021.12.30 |
Comments