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";
}
}
Interface1 과 Interface2 의 구현을 구성하려면 둘 다 확장하는 인터페이스를 만들어야 합니다.
public interface MixinInterface extends Interface1, Interface2 { }
Mixin 클래스 의 create() 메서드를 사용하여 Class1 및 Class2 의 동작 을 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 에서 메서드를 호출하면 Class1 및 Class2 에서 구현이 호출됩니다 .
7. 결론
이 기사에서 우리는 cglib 와 가장 유용한 구조를 살펴보았습니다. Enhancer 클래스 를 사용하여 프록시를 만들었습니다 . 우리는 BeanCreator 를 사용했고 마지막 으로 다른 클래스의 동작을 포함 하는 Mixin 을 만들었습니다 .
Cglib는 Spring 프레임워크에서 광범위하게 사용됩니다. Spring에서 cglib 프록시를 사용하는 한 가지 예는 메서드 호출에 Security 제약을 추가하는 것입니다. 메서드를 직접 호출하는 대신 Spring Security은 먼저 지정된 Security 검사가 통과하는지(프록시를 통해) 확인하고 이 확인이 성공한 경우에만 실제 메서드에 Delegation합니다. 이 기사에서 우리는 우리 자신의 목적을 위해 그러한 프록시를 만드는 방법을 보았습니다.
이러한 모든 예제 및 코드 스니펫의 구현은 GitHub 프로젝트 에서 찾을 수 있습니다. 이것은 Maven 프로젝트이므로 그대로 가져오고 실행하기 쉬워야 합니다.