Java

CGLIB

Hoonco 2023. 7. 2. 19:09

CGLIB(Class Generation Library)은 자바 언어의 라이브러리 중 하나로, 코드 생성을 통해 동적으로 클래스를 생성하는 라이브러리입니다. 

 

주로 스프링 프레임워크와 같은 프레임워크나 라이브러리에서 AOP(Aspect-Oriented Programming)을 구현하는데 사용됩니다.

 

 CGLIB은 JDK의 동적 프록시를 보완하는 방법으로 동작합니다. 

JDK 동적 프록시는 인터페이스를 기반으로 프록시를 생성하며, 인터페이스를 구현하는 클래스만 프록시로 감쌀 수 있습니다. 

JDK 동적 프록시는 Reflection API를 활용하게 되는데 이는 구체적인 클래스 타입을 알지 못해도 런타임에 클래스 정보에 접근할 수 있게 해주는 자바의 API 입니다.

허나 리플렉션은 동적일 때, 해결되는 타입을 포함하게 되므로 JVM의 optimization이 작동하지 않아 성능상 느립니다.

 

JDK의 동적 프록시에 대해서 좀더 알아보겠습니다.

client가 특정 메소드를 요청하게 되면 JDK Dynamic Proxy가 Invocation Handler를 호출하여 부가기능을 수행하고 원본이 되는 Target Class를 호출하여 실제 메소들 실행하게 됩니다.

 

반면에 CGLIB은 클래스의 상속을 통해 프록시를 생성하며, 인터페이스를 구현하지 않은 클래스에도 프록시를 적용할 수 있습니다. 

CGLIB의 동작 방식은 기본적으로 바이트 코드를 조작하여 동적으로 클래스를 생성합니다. 

프록시가 필요한 클래스의 하위 클래스를 만들어서 프록시를 구현하고, 이렇게 생성된 프록시 클래스를 통해 원본 클래스에 접근하여 원하는 기능을 구현하는 방식입니다. 

 

스프링 프레임워크에서 AOP를 사용할 때, 메소드 실행 전/후에 공통 기능을 추가하거나, 트랜잭션 관리 등을 구현하기 위해 CGLIB를 사용하여 프록시 객체를 생성하는 경우가 많습니다.

 따라서 CGLIB는 스프링의 핵심 라이브러리 중 하나로 사용되며, 자바의 다이나믹 프록시보다 좀 더 강력한 기능을 제공합니다.

 

아래와 같으 클래스가 있다고 가정합시다.

public class OriginalClass {
    public void doSomething() {
        System.out.println("OriginalClass - doSomething 메소드 실행");
    }
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyClass implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("메소드 실행 전 - " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("메소드 실행 후 - " + method.getName());
        return result;
    }
}

이후 프록시 클래스를 생성합니다.

 

import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        // 원본 클래스 생성
        OriginalClass original = new OriginalClass();

        // 프록시 생성
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OriginalClass.class);
        enhancer.setCallback(new ProxyClass());
        OriginalClass proxy = (OriginalClass) enhancer.create();

        // 프록시를 통해 메소드 실행
        proxy.doSomething();
    }
}

CGLIB은 Enhancer 클래스를 의존하여 활용할 수 있는데  원본 (Target) 클래스를 setSuperClass에

프록시 클래스를 setCallback 함수에 넣어준 후 create 메소드를 통하여 가짜 원본 객체 OriginalClass를 생성할 수 있습니다.

이후 메소드를 실행시키면 부가기능이 추가된 결과를 확인할 수 있습니다.

 

메소드 실행 전 - doSomething
OriginalClass - doSomething 메소드 실행
메소드 실행 후 - doSomething

 

CGLIB의 특징

- 인터페이스에도 강제로 적용할 수 있습니다. 이땐 클래스에도 프록시를 적용시켜야 합니다.

- 상속을 이용하여 프록시를 만듭니다.

- 메소드에 final을 붙이면 오버라이딩이 불가능하게 됩니다.

- net.sf.cglib.proxy.Enhancer 의존성을 추가해야합니다.

- Default 생성자가 필요합니다.

- 원본 클래스의 생성자를 두번 호출하게 됩니다.

- 메소드가 처음 호출되었을 대 동적으로 원본 클래스의 바이트 코드를 조작하게 됩니다.

- 이후 호출시엔 조작된 바이트 코드를 재사용하도록 되어 있습니다.

 

Spring Boot에서는 default로 JDK 동적 프록시가 아닌 CGLIB을 사용하게 됩니다. ( 설정으로 변경 가능합니다. )

이는 인터페이스 기반 프록시(JDK 동적 프록시)가 때때로 ClassCast Exceptions를 추적하기 어렵게 하기 때문입니다.

 

Spring에서는 프록시를 Bean으로 만들어주는 ProxyFactoryBean을 제공합니다.

ProxyFactoryBean을 통해 Proxy를 쉽게 생성할 수 있습니다.

 

ProxyFactoryBean의 주요 특징

- 타깃의 인터페이스 정보가 필요없다.

- 프록시 빈을 생성해주는 클래스입니다.

- 부가기능을 MethodInterceptor로 구현합니다. (CGLIB에 있는 MethodInterceptor와 다릅니다.)

- MethodInterceptor를 재정의한 invoke를 구현해주어야 합니다.