CGLIB란 무엇이며 프록시 생성 방식 #18
Replies: 3 comments 1 reply
-
|
프록시 생성 방법 중 하나인, Code Generation Library는 자바 바이트코드를 조작하여, 동적으로 프록시를 만들어낸다.
프록시 실험실험@Service
@Transactional
public class SimpleService {
public void doSomething() {
System.out.println("SimpleService 기본 비즈니스 로직 실행");
}
}@Component
@RequiredArgsConstructor
public class MyProxyRunner implements CommandLineRunner {
private final SimpleService simpleService;
@Override
public void run(String... args) throws Exception {
System.out.println("=========================================");
System.out.println("주입된 클래스 이름: " + simpleService.getClass().getName());
System.out.println("=========================================");
}
}실행 결과 여기서 만약 class에 위와 같은 에러 메세지를 얻을 수 있다. GCLIB는 대상이 되는 클래스를 상속받아 프록시를 구현하는데, final이 붙으면 상속받을 수 없기 때문이다. (상속받는 코드는 바이트코드로 조작한다.)
실제 바이트코드를 조작하여 만들어진 CGLIB방식의 SimpleService 프록시 객체 코드를 보고싶다면 @SpringBootApplication
public class RoomescapeApplication {
public static void main(String[] args) {
System.setProperty("cglib.debugLocation", "./cglib-dump");
SpringApplication.run(RoomescapeApplication.class, args);
}
}다음과 같이 |
Beta Was this translation helpful? Give feedback.
-
|
CGLIB는 클래스를 상속해서 프록시 객체를 만들어주는 바이트코드 생성 라이브러리라고 이해했습니다. Spring에서 프록시는 주로 예를 들어 프록시를 만드는 방식에는 크게 JDK 동적 프록시와 CGLIB 방식이 있는데, 다만 상속을 기반으로 하기 때문에 결국 CGLIB는 Spring이 “기존 객체의 코드를 직접 수정하지 않고도” CGLIB란 무엇인가?CGLIB는 Code Generation Library의 줄임말로, 런타임에 바이트코드를 조작해서 새로운 클래스를 만들어주는 라이브러리입니다. Spring에서는 주로 프록시 객체를 만들 때 사용됩니다. 특히 인터페이스가 없는 클래스에 AOP나 예를 들어 아래와 같은 클래스가 있다고 가정할 수 있습니다. public class ReservationService {
public void create() {
// 예약 생성 로직
}
}CGLIB는 이 클래스를 상속한 프록시 클래스를 런타임에 만들어냅니다. 개념적으로 보면 아래와 비슷합니다. public class ReservationServiceProxy extends ReservationService {
@Override
public void create() {
// 부가 기능
super.create();
// 부가 기능
}
}실제로 개발자가 이런 클래스를 직접 작성하는 것은 아니고, Spring이 런타임에 만들어주는 방식입니다. 프록시는 왜 필요한가?프록시는 기존 객체의 코드를 직접 수정하지 않고, 메서드 호출 전후에 부가 기능을 끼워 넣기 위해 필요하다고 생각합니다. 예를 들어 트랜잭션을 직접 코드로 작성하면 서비스 로직마다 반복이 생길 수 있습니다. public void create() {
transactionManager.begin();
try {
reservationRepository.save();
transactionManager.commit();
} catch (Exception e) {
transactionManager.rollback();
throw e;
}
}하지만 @Transactional
public void create() {
reservationRepository.save();
}이게 가능한 이유는 Spring이 실제 서비스 객체 앞에 프록시를 세우기 때문입니다. 즉, 프록시는 트랜잭션, 로깅, 권한 확인, 성능 측정 같은 공통 기능을 비즈니스 로직과 분리하기 위한 장치라고 볼 수 있습니다. 프록시 생성 방식 1: JDK 동적 프록시JDK 동적 프록시는 인터페이스를 기반으로 프록시를 만드는 방식입니다. 예를 들어 서비스가 인터페이스를 구현하고 있다면, public interface ReservationService {
void create();
}
public class ReservationServiceImpl implements ReservationService {
public void create() {
// 실제 로직
}
}JDK 동적 프록시는 장점은 자바 표준 기능이라는 점입니다. 하지만 인터페이스가 있어야 사용할 수 있습니다. 프록시 생성 방식 2: CGLIBCGLIB는 클래스를 상속해서 프록시를 만드는 방식입니다. 인터페이스가 없어도 실제 클래스를 상속한 하위 클래스를 만들어서 프록시를 생성할 수 있습니다. 예를 들어 아래 클래스가 있다면, public class ReservationService {
public void create() {
// 실제 로직
}
}CGLIB는 개념적으로 아래와 같은 클래스를 만듭니다. public class ReservationServiceProxy extends ReservationService {
@Override
public void create() {
// 트랜잭션 시작
super.create();
// 트랜잭션 커밋
}
}즉, 호출자는 JDK 동적 프록시와 CGLIB의 차이둘의 가장 큰 차이는 무엇을 기준으로 프록시를 만드느냐입니다.
JDK 동적 프록시는 인터페이스를 기반으로 하기 때문에 인터페이스가 필요합니다. 반면 CGLIB는 클래스를 상속하기 때문에 인터페이스가 없어도 됩니다. 하지만 CGLIB는 상속 기반이므로 public final class ReservationService {
}이 클래스는 상속할 수 없기 때문에 CGLIB 프록시를 만들 수 없습니다. 또 아래처럼 메서드가 public final void create() {
}CGLIB는 메서드를 오버라이딩해서 부가 기능을 끼워 넣는 방식이므로, 이런 경우 프록시 적용에 제약이 생깁니다. Spring에서는 언제 CGLIB를 사용할까?Spring AOP는 프록시 기반으로 동작합니다. 예를 들어 Spring은 프록시를 만들 때 상황에 따라 JDK 동적 프록시 또는 CGLIB를 사용할 수 있습니다. 일반적으로 인터페이스 기반이면 JDK 동적 프록시를 사용할 수 있고, 클래스 기반 프록시가 필요하면 CGLIB를 사용할 수 있습니다. Spring Boot에서는 기본적으로 클래스 기반 프록시, 즉 CGLIB 방식이 사용되는 경우가 많습니다. 그래서 인터페이스 없이 서비스 클래스를 만들어도 @Service
public class ReservationService {
@Transactional
public void create() {
// ...
}
}이 경우 Spring은 CGLIB와 self-invocation 문제CGLIB 프록시를 이해할 때 중요한 점은, Spring AOP가 프록시 객체를 거쳐 호출될 때만 부가 기능을 적용한다는 점입니다. 예를 들어 외부에서 아래 메서드를 호출하면, reservationService.outer();실제로는 프록시를 거치기 때문에 AOP가 적용될 수 있습니다. 하지만 같은 클래스 내부에서 자기 자신의 메서드를 호출하면 문제가 생길 수 있습니다. public class ReservationService {
public void outer() {
inner();
}
@Transactional
public void inner() {
// 트랜잭션 기대
}
}
그래서 이 문제를 self-invocation 문제라고 이해했습니다. 즉, CGLIB가 클래스를 상속해서 프록시를 만들더라도, 내부 메서드 호출까지 자동으로 모두 가로채는 것은 아닙니다. CGLIB의 장점과 한계CGLIB의 장점은 인터페이스가 없어도 프록시를 만들 수 있다는 점입니다. 그래서 클래스 기반으로 작성된 서비스에도 AOP를 적용할 수 있습니다. 또 개발자는 비즈니스 코드에 트랜잭션, 로깅, 권한 확인 같은 공통 기능을 직접 섞지 않아도 됩니다. 하지만 한계도 있습니다.
즉, CGLIB는 강력하지만 “상속해서 감싼다”는 방식의 제약을 가지고 있습니다. 정리정리하면 저는 CGLIB를 이렇게 이해했습니다.
결론적으로 CGLIB는 Spring이 기존 객체의 코드를 직접 수정하지 않고도 메서드 호출 전후에 부가 기능을 끼워 넣기 위해 사용하는 클래스 기반 프록시 생성 방식이라고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
Proxy와 CGLIB란?Proxy 프록시란 프록시란 원본 객체를 대신해서 앞에 서는 대리 객체이다. CGLIB (Code Generation Library) 란 Spring AOP, Hibernate, Mockito 등이 사용한다. JDK의 동적 프록시의 한계를 해결하고자 등장하였습니다.Java의 기본 프록시 (java.lang.relect.Proxy)가 존재하는데,
어떻게 동작할까?기존 JDK에서 동적 프록시 방식은 인터페이스를 구현한 프록시 클래스를 런타임에 생성하고, CGLIB 방식은 원본 클래스를 상속한 서브 클래스를 바이트코드로 생성한다. CGLIB 프록시 코드// 원본
public class OrderService {
public void order() { ... }
}
// CGLIB가 런타임에 이런 클래스를 바이트코드로 만듦
public class OrderService$$EnhancerByCGLIB extends OrderService {
@Override
public void order() {
// 부가 로직 (트랜잭션, 로깅 등)
super.order();
}
}정리프록시란 원본 클래스 앞에 대리인을 세우고 싶은데, Java에서 기본 방식은 인터페이스가 있어야만 가능했다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
.
Beta Was this translation helpful? Give feedback.
All reactions