Byte Buddy # 1

임현규·2022년 12월 8일
0

ByteBuddy #1

Byte Buddy

Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application and without the help of a compiler. Other than the code generation utilities that ship with the Java Class Library
, Byte Buddy allows the creation of arbitrary classes and is not limited to implementing interfaces for the creation of runtime proxies. Furthermore, Byte Buddy offers a convenient API for changing classes either manually, using a Java agent or during a build.

byte buddy란 컴파일러의 도움 없이 런타임에 자바 클래스를 생성 또는 수정하는 라이브러리이다. 바이트버디는 proxy에 활용할 수 있고, Java agent와 사용하거나 빌드 도중에 활용할 수 있다.

Byte Buddy는 Java code parser 라이브러리인 ASM에 의존한 경량 라이브러리이다. 그리고 바이트버디는 성능이 좋고 Mockito, Hibernate, google bazel build system과 같은 유명한 프레임워크 또는 툴과 사용해도 안정적이다.

Byte Buddy는 상업에서도 성공적인 라이브러리이다. 이미 매 년 75,000,000 번 사람들이 라이브러리를 다운로드한다.

의존성

gradle

implementation 'net.bytebuddy:byte-buddy:1.12.19'

간단한 예제 (Hello World~~~)

바이트 버디를 활용해 새로운 타입의 클래스를 생성해보자.

class MainTest {

    @Test
    void newClassTest()
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> dynamicType = new ByteBuddy()
                .subclass(Object.class)
                .method(ElementMatchers.named("toString"))
                .intercept(FixedValue.value("Hello world!"))
                .make()
                .load(getClass().getClassLoader())
                .getLoaded();

        assertThat(dynamicType.getConstructor().newInstance().toString())
                .isEqualTo("Hello world!");
    }
}

바이트 버디는 Builder 패턴을 활용해서 사용할 수 있다. 요즘 많은 라이브러리, 프레임워크가 이런 방식을 채용하는 것 같다. 그 이유는 사용자 입장에서 이해하기 편하기 때문이다.

예제의 코드를 하나씩 뜯어보자

.subclass(Object.class)

입력한 슈퍼타입의 서브 클래스를 만들 빌더를 생성한다. 인터페이스 또는 슈퍼 클래스를 상속 받아 클래스 타입을 생성할 수 있다.

class를 상속받는다면, 바이트버디는 보이는 생성자 모두 모방합니다. 생성자들은 super 생성자를 호출하도록 모방한다.

또 다른 생성 방법은 입력 인자에 ConstructorStrategy를 활용하는 것이다.

주의점1: 오너 타입의 변수 타입을 이미 선언해서 생성하낟.

주의점2: Byte Buddy는 이전 subclass를 캐시하지 않는다. 만약 캐시를 하고 싶은 경우 CacheType을 활용해야 한다.

💡 입력: super class 또는 extend 할 인터페이스 💡 결과: interface 또는 제공된 class를 입력받아 클래스 생성을 위한 builder 반환

.method(ElementMatchers.named(”메서드이름”))

이미 선언된 메서드 또는 이미 상속된 타입의 메서드를 매치한다. 그 후 override를 통한 메서드 구현, default value, annotaion 또는 커스텀 속성을 바꿀 기회를 제공한다.

💡 입력: ElementMatcher 💡 출력: 메서드를 어떻게 처리할지에 대한 builder

ElementMatcher

A utility class that contains a human-readable language for creating ElementMatchers.

ElementMatcher는 사용자 친화적인 메서드를 제공하는 유틸 클래스이다.

ElementMatcher.named(””)

named 는 정확한 이름을 입력하는 해당 element의 정확한 이름을 매칭한다.

.intercept()

method의 매칭 설정 후 설정할 수 있다. 여러 implementation을 .intercept에 적용할 수 있다. 이 때 가지는 3가지 특징이 있다.

  1. subclass로 이미 선언된 타입의 메서드를 재정의할 때 사용한다. 이 때 이전에 구현된 부분은 사라진다.
  2. rebase 버전으로 메서드를 선언할 때 원래 메서드는 보존되고 private, synthetic 메서드의 경우 계측된 타입 그대로 보존한다. 그리고 새로운 메서드를 정의할 때 super 메서드를 호출하는 형식으로 사용한다. (프록시)
  3. 가상 메서드 abstract와 같은 경우 override를 통해 구현된다.

FixedValue(”hello world!”)

출력값을 상수 형태로 출력한다.

.make()

잘못된 생성정보를 입력한 경우 IllegalStateException 예외를 던진다. 리턴값은 DynamicType.Unloaded로 실제 class를 JVM에서 로드한 것은 아니다. 단지 동적으로 생성할 타입을 생성해준 것이라 이해하면 된다.

.load & .getLoaded()

.load는 ClassLoader에 DynamicType을 주입하는 역할을 한다. 이를 통해 생성된 .class 바이트 코드를 JVM이 런타임 중에 읽을수 읽게 된다.

실제 클래스 정보를 얻기 위해서는 .getLoaded()를 해주면 된다.

그 이후 리플렉션을 활용해 인스턴스를 생성해주면 된다.

정리

간단한 개요하 Hello World ByteBuddy를 분석해보면서 어떤 방식으로 ByteBuddy의 동작과 활용방식을 알아봤다. 다음엔 실제 어떻게 활용할 수 있을지 Learn 문서를 정리해볼 계획이다.

참고

Byte Buddy - runtime code generation for the Java virtual machine

profile
엘 프사이 콩그루

0개의 댓글