[java] Java Byte Buddy를 사용한 데코레이터 패턴

데코레이터 패턴은 기존 객체에 추가적인 기능을 동적으로 더하는 디자인 패턴입니다. 이 패턴은 객체의 구조를 변경하지 않고 기능을 확장할 수 있는 장점이 있습니다. Java Byte Buddy는 Java 언어를 통해 런타임에 클래스의 동작을 변경하는 도구로 사용될 수 있습니다. 이번 블로그에서는 Java Byte Buddy를 사용하여 데코레이터 패턴을 구현하는 방법을 알아보겠습니다.

1. Byte Buddy 라이브러리 추가하기

먼저 프로젝트에 Byte Buddy 라이브러리를 추가해야 합니다. Maven을 사용한다면 pom.xml 파일에 다음과 같이 의존성을 추가할 수 있습니다.

<dependencies>
  <dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.11.12</version>
  </dependency>
</dependencies>

Gradle을 사용한다면 build.gradle 파일에 다음과 같이 의존성을 추가합니다.

dependencies {
    implementation 'net.bytebuddy:byte-buddy:1.11.12'
}

2. 데코레이터 클래스 구현하기

다음으로, 데코레이터 클래스를 구현해야 합니다. 이 클래스는 원본 객체를 감싸고 있으며, 추가 기능을 제공합니다. 아래는 예시를 위해 CustomerDAO 인터페이스를 구현한 원본 클래스와 데코레이터 클래스입니다.

public interface CustomerDAO {
    void save(Customer customer);
}

public class CustomerDAOImpl implements CustomerDAO {
    @Override
    public void save(Customer customer) {
        // 데이터베이스에 고객 정보를 저장하는 코드
        System.out.println("Customer saved: " + customer.getName());
    }
}

public class CustomerLoggerDecorator implements ByteBuddyInterceptor {
    private final CustomerDAO delegate;

    public CustomerLoggerDecorator(CustomerDAO delegate) {
        this.delegate = delegate;
    }

    @Override
    public void intercept(MethodInvocation methodInvocation) throws Exception {
        System.out.println("Before saving customer");
        methodInvocation.proceed();
        System.out.println("After saving customer");
    }
}

3. Byte Buddy를 사용하여 데코레이터 생성하기

이제 Byte Buddy를 사용하여 데코레이터 객체를 생성하는 코드를 작성해보겠습니다. 아래의 코드는 데코레이터 클래스에 추가적인 로깅 기능을 제공하는 코드를 생성합니다.

ByteBuddy byteBuddy = new ByteBuddy();

CustomerDAO decoratedCustomerDAO = byteBuddy
    .subclass(CustomerDAO.class)
    .method(named("save"))
    .intercept(MethodDelegation.to(new CustomerLoggerDecorator(new CustomerDAOImpl())))
    .make()
    .load(getClass().getClassLoader())
    .getLoaded()
    .newInstance();

위의 코드에서 CustomerLoggerDecorator 클래스의 intercept 메소드를 호출하여 로깅 기능을 구현하도록 지시합니다. CustomerLoggerDecoratorCustomerDAO 객체를 감싸고 있으므로, CustomerDAOsave 메소드가 호출될 때마다 로깅이 출력됩니다.

4. 데코레이터 사용하기

이제 생성된 데코레이터 객체를 사용하여 기존의 CustomerDAO 객체를 대체할 수 있습니다.

decoratedCustomerDAO.save(new Customer("John Doe"));

위의 코드를 실행하면 아래와 같은 결과가 출력됩니다.

Before saving customer
Customer saved: John Doe
After saving customer

데코레이터를 사용하여 기존 객체에 동적으로 기능을 추가하는 것이 성공적으로 이루어진 것을 확인할 수 있습니다.

결론

Java Byte Buddy를 사용하면 런타임에 클래스의 동작을 변경하여 데코레이터 패턴을 구현할 수 있습니다. 이를 통해 기존 객체에 추가 기능을 동적으로 제공하고, 객체의 구조를 변경하지 않는 장점을 제공할 수 있습니다. Java Byte Buddy를 효과적으로 사용하면 유연하고 확장 가능한 코드를 작성할 수 있습니다.

참고 자료