1. 개요

이 기사에서는 cglib (코드 생성 라이브러리) 라이브러리 를 살펴보겠습니다 . Hibernate 또는 Spring 과 같은 많은 Java 프레임워크에서 사용되는 바이트 계측 라이브러리 입니다. 바이트코드 계측을 통해 프로그램의 컴파일 단계 후에 클래스를 조작하거나 생성할 수 있습니다.

2. 메이븐 의존성

프로젝트에서 cglib 를 사용하려면 Maven 의존성을 추가하기만 하면 됩니다(최신 버전은 여기 에서 찾을 수 있음 ).

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.4</version>
</dependency>

3. 씨글립

Java의 클래스는 런타임에 동적으로 로드됩니다. Cglib 는 Java 언어의 이 기능을 사용하여 이미 실행 중인 Java 프로그램에 새 클래스를 추가할 수 있도록 합니다.

Hibernate 는 동적 프록시 생성을 위해 cglib를 사용합니다. 예를 들어, 데이터베이스에 저장된 전체 개체를 반환하지 않지만 요청 시 데이터베이스에서 값을 느리게 로드하는 저장된 클래스의 계측 버전을 반환합니다.

Mockito 와 같은 인기 있는 모킹 프레임워크 는 모킹 메서드에 cglib 를 사용 합니다. 모의는 메서드가 빈 구현으로 대체되는 계측 클래스입니다.

우리는 cglib 에서 가장 유용한 구조를 살펴볼 것 입니다.

4. cglib 를 이용한 프록시 구현

두 가지 메서드가 있는 PersonService 클래스가 있다고 가정해 보겠습니다 .

public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

첫 번째 메서드는 String 을 반환 하고 두 번째 메서드는 Integer를 반환합니다.

4.1. 같은 값 반환

sayHello() 메서드 에 대한 호출을 가로채는 간단한 프록시 클래스를 만들고자 합니다 . Enhancer 클래스를 사용하면 Enhancer 클래스 의 setSuperclass() 메서드를 사용하여 PersonService 클래스를 동적으로 확장하여 프록시를 만들 수 있습니다 .

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
PersonService proxy = (PersonService) enhancer.create();

String res = proxy.sayHello(null);

assertEquals("Hello Tom!", res);

FixedValue 는 단순히 프록시된 메서드에서 값을 반환하는 콜백 인터페이스입니다. 프록시에서 sayHello() 메서드를 실행 하면 프록시 메서드에 지정된 값이 반환되었습니다.

4.2. 메서드 서명에 따라 반환 값

프록시의 첫 번째 버전에는 프록시가 가로채야 하는 메서드와 슈퍼클래스에서 호출해야 하는 메서드를 결정할 수 없기 때문에 몇 가지 단점이 있습니다. MethodInterceptor 인터페이스를 사용 하여 프록시에 대한 모든 호출을 가로채고 특정 호출을 수행할지 아니면 수퍼클래스에서 메서드를 실행할지 결정할 수 있습니다.

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});

PersonService proxy = (PersonService) enhancer.create();

assertEquals("Hello Tom!", proxy.sayHello(null));
int lengthOfName = proxy.lengthOfName("Mary");
 
assertEquals(4, lengthOfName);

이 예에서 우리는 메서드 시그니처가 Object 클래스에서 온 것이 아닐 때 모든 호출을 가로채고 있습니다. 즉, toString() 또는 hashCode() 메서드는 가로채지 않습니다. 그 외에도 String 을 반환하는 PersonService 의 메서드만 가로채고 있습니다. 반환 유형이 정수 이기 때문에 lengthOfName() 메서드 에 대한 호출은 가로채지 않습니다.

5. 빈 크리에이터

cglib 의 또 다른 유용한 구성은 BeanGenerator 클래스 입니다. 동적으로 bean을 생성하고 setter 및 getter 메소드와 함께 필드를 추가할 수 있습니다. 코드 생성 도구에서 간단한 POJO 객체를 생성하는 데 사용할 수 있습니다.

BeanGenerator beanGenerator = new BeanGenerator();

beanGenerator.addProperty("name", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setName", String.class);
setter.invoke(myBean, "some string value set by a cglib");

Method getter = myBean.getClass().getMethod("getName");
assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. 믹스인 만들기

믹스은 여러 개체를 하나로 결합할 수 있는 구성입니다. 몇 가지 클래스의 동작을 포함하고 해당 동작을 단일 클래스 또는 인터페이스로 노출할 수 있습니다. cglib 믹스 인 을 사용하면 여러 개체를 단일 개체로 결합할 수 있습니다. 그러나 이렇게 하려면 믹스에 포함된 모든 객체가 인터페이스에 의해 뒷받침되어야 합니다.

두 인터페이스의 혼합을 만들고 싶다고 가정해 보겠습니다. 인터페이스와 해당 구현을 모두 정의해야 합니다.

public interface Interface1 {
    String first();
}

public interface Interface2 {
    String second();
}

public class Class1 implements Interface1 {
    @Override
    public String first() {
        return "first behaviour";
    }
}

public class Class2 implements Interface2 {
    @Override
    public String second() {
        return "second behaviour";
    }
}

Interface1Interface2 의 구현을 구성하려면 둘 다 확장하는 인터페이스를 만들어야 합니다.

public interface MixinInterface extends Interface1, Interface2 { }

Mixin 클래스 의 create() 메서드를 사용하여 Class1Class2 의 동작 을 MixinInterface에 포함할 수 있습니다.

Mixin mixin = Mixin.create(
  new Class[]{ Interface1.class, Interface2.class, MixinInterface.class },
  new Object[]{ new Class1(), new Class2() }
);
MixinInterface mixinDelegate = (MixinInterface) mixin;

assertEquals("first behaviour", mixinDelegate.first());
assertEquals("second behaviour", mixinDelegate.second());

mixinDelegate 에서 메서드를 호출하면 Class1Class2 에서 구현이 호출됩니다 .

7. 결론

이 기사에서 우리는 cglib 와 가장 유용한 구조를 살펴보았습니다. Enhancer 클래스 를 사용하여 프록시를 만들었습니다 . 우리는 BeanCreator 를 사용했고 마지막 으로 다른 클래스의 동작을 포함 하는 Mixin 을 만들었습니다 .

Cglib는 Spring 프레임워크에서 광범위하게 사용됩니다. Spring에서 cglib 프록시를 사용하는 한 가지 예는 메서드 호출에 Security 제약을 추가하는 것입니다. 메서드를 직접 호출하는 대신 Spring Security은 먼저 지정된 Security 검사가 통과하는지(프록시를 통해) 확인하고 이 확인이 성공한 경우에만 실제 메서드에 Delegation합니다. 이 기사에서 우리는 우리 자신의 목적을 위해 그러한 프록시를 만드는 방법을 보았습니다.

이러한 모든 예제 및 코드 스니펫의 구현은 GitHub 프로젝트 에서 찾을 수 있습니다. 이것은 Maven 프로젝트이므로 그대로 가져오고 실행하기 쉬워야 합니다.

Generic footer banner