ByteBuddy를 살펴보자

Circlee7
8 min readDec 26, 2018

--

https://www.baeldung.com/byte-buddy를 읽으며 내가 이해 하기 위해 번역한 문서입니다.

ByteBuddy란?

런타임 시에 java Class 들을 동적으로 만들어낼 수 있는 라이브러리 이다.

의존성추가

사용을 위해서 의존성을 추가하자

compile ('net.bytebuddy:byte-buddy:1.7.1')

런타임 Java Class 생성

이미 존재하는 클래스의 서브클래스를 동적으로 생성해보자.
예를들어, Object.class의 toString() 메소드를 오버라이딩 한 서브클래스를 만들어보자.

DynamicType.Unloaded unloadedType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.isToString())
.intercept(FixedValue.value("Hello World ByteBuddy!"))
.make();

ByteBuddy 인스턴스를 생성하였고. Object.class 상속을 위해 subclass() API를 사용했다.
또한 ElementMatchers를 이용하여 Object.class의 toString메소드를 선택했다.
마지막으로 intercept() API를 사용하여 toString() 메소드에서 고정값을 리턴하도록 하였다.
make() 메소드를 통해 새로운 클래스를 만든다.

이 시점에서, 우리의 클래스는 생성되었지만 JVM에 아직 로드되지 않은 상태이다. (이것은 DynamicType.Unloaded 의 인스턴스로 생성되며
생성한 타입의 binary form이다.)

그래서 아래와 같이 JVM에 생성된 클래스를 로드해야 한다.

Class<?> dynamicType = unloadedType.load(getClass()
.getClassLoader())
.getLoaded();

아래와 같이 동적타입의 toString() 메소드 실행을 확인할수 있다.

assertEquals(
dynamicType.newInstance().toString(), "Hello World ByteBuddy!");

메소드 위임과 커스텀 로직

앞선 예제에서 , 우리는 toString() 메소드에서 고정값을 리턴했다.

실제 업무 응용레벨에서는 이것보다 더 복잡한 로직을 요구한다.

커스텀 로직을 동적 타입으로 이용가능하게끔 하는 효과적인 방법은 메소드 호출 위임이다.

Foo.class 의 서브클래스를 동적 타입으로 만들어보자.
Foo.class는 sayHelloFoo() 메소드를 가지고있다.

public String sayHelloFoo() { 
return "Hello in Foo!";
}

또한 Bar라는 static sayHelloBar 메소드를 갖는 클래스를 만들어보자.

public static String sayHelloBar() { 
return "Holla in Bar!";
}

이제, ByteBuddy의 DSL을 사용하여 sayHelloFoo()의 모든 호출을 sayHelloBar()로 위임해보자.
이것은 우리에게 커스텀 로직 적용하는것을 허용해준다.

String r = new ByteBuddy()
.subclass(Foo.class)
.method(named("sayHelloFoo")
.and(isDeclaredBy(Foo.class)
.and(returns(String.class))))
.intercept(MethodDelegation.to(Bar.class))
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance()
.sayHelloFoo();

assertEquals(r, Bar.sayHelloBar());

sayHelloFoo() 를 호출하면, sayHelloBar() 가 실행된다.

어떻게 ByteBuddy는 Bar.class 안의 어떤 메소드가 실행하기 위한 메소드인지 알수있을까?
그것은 메소드 signature와 리턴타입 메소드명, 어노테이션으로 매칭메소드에 의해 선택되어진다.

sahHelloFoo() 와 sayHelloBar 는 같은 이름을 갖지 않는다.
그러나 그들은 같은 메소드 signature와 리턴타입을 갖는다.

만약 Bar.class 내에 signature 와 리턴타입이 맞는 다수의 메소드가 있다면
@BindingPriority 어노테이션을 이용하여 애매모호함을 해결할 수 있다.

@BindingPriority 는 integer인자를 갖으며
높은 수로 구현체 호출의 우선순위를 갖는다.
그렇게,아래 코드 스니펫의 sayHelloBar() 는 sayBar() 보다도 우선되어질 것이다.

@BindingPriority(3)
public static String sayHelloBar() {
return "Holla in Bar!";
}

@BindingPriority(2)
public static String sayBar() {
return "bar";
}

메소드와 필드 정의

동적타입의 슈퍼클래스 내 정의된 메소드를 재정의 할 수 있다.
동적타입 클래스에 새로운 메소드(또는 필드)를 추가하여 살펴보자.

동적 생성된 메소드 호출을 위해 java reflections을 사용할것이다.

Class<?> type = new ByteBuddy()
.subclass(Object.class)
.name("MyClassName")
.defineMethod("custom", String.class, Modifier.PUBLIC)
.intercept(MethodDelegation.to(Bar.class))
.defineField("x", String.class, Modifier.PUBLIC)
.make()
.load(
getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();

Method m = type.getDeclaredMethod("custom", null);
assertEquals(m.invoke(type.newInstance()), Bar.sayHelloBar());
assertNotNull(type.getDeclaredField("x"));

Object.class 의 서브클래스로 MyClassName이라는 클래스를 만들었다.
그 다음 custom 이라는 이름으로 String 타입 리턴,public 접근 제한의 메소드를 만들었다.
앞선예제와 같이 , 호출을 가로채서 앞선 튜토리얼에서 만든 Bar.class 로 위임하는 메소드를 구현했다.

존재하는 클래스의 재정의

동적으로 생성 된 클래스로 작업했지만 이미 로드 된 클래스로도 작업 할 수 있다.
기존 클래스를 재정의 (또는 리베이스) 하고 ByteBuddyAgent를 이용하여 JVM으로 다시 로드 할 수 있다.

ByteBuddyAgent 의존성을 추가하자.

compile ('net.bytebuddy:byte-buddy-agent:1.7.1')

최신버전은 여기서 찾을 수 있다.

이제, 앞서 만든 Foo.class의 sayHelloFoo 메소드를 재정의 해보자.

ByteBuddyAgent.install();
new ByteBuddy()
.redefine(Foo.class)
.method(named("sayHelloFoo"))
.intercept(FixedValue.value("Hello Foo Redefined"))
.make()
.load(
Foo.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());

Foo f = new Foo();

assertEquals(f.sayHelloFoo(), "Hello Foo Redefined");

ByteBuddy 라리브러리 기능과 이를 사용하여 동적 클래스를 효율적으로 만든는 방법을 살펴보았다.

이 문서는 라이브러리의 내부 동작 및 기타 측면에 대해 심층적인 설명을 제공합니다.

또한 튜토리얼 전체 코드 스니펫은 github에서 찾을 수 있습니다.

원본 문서

나의 생각

동적으로 바이트코드를 건드려서 클래스를 만들고 클래스로더에 올리고 하는 라이브러리 라고 생각.

다른 프레임워크 보다 성능은 더 좋은가?

오올

--

--

Circlee7
Circlee7

No responses yet