본문 바로가기
Spring Study

AOP(Aspect-Oriented Programming)의 중요한 세 가지 기술

by xogns93 2024. 8. 19.

AOP(Aspect-Oriented Programming)의 세 가지 주요 기술인 다이나믹 프록시, CGLib, 그리고 AspectJ

 

1. 다이나믹 프록시 (Dynamic Proxy)

개념:

다이나믹 프록시는 자바 표준 라이브러리에서 제공하는 java.lang.reflect.Proxy 클래스를 이용하여 인터페이스 기반의 프록시 객체를 런타임에 동적으로 생성하는 기술입니다. 이 프록시 객체는 실제 객체의 대리자 역할을 하며, 메서드 호출을 가로채서 특정 로직(예: 로깅, 트랜잭션 관리 등)을 추가할 수 있습니다.

동작 방식:

  • 인터페이스 필요: 다이나믹 프록시는 반드시 인터페이스를 기반으로 동작합니다. 즉, 프록시 객체를 생성하려면 원본 클래스가 구현한 인터페이스가 있어야 합니다.
  • InvocationHandler: 다이나믹 프록시는 InvocationHandler 인터페이스를 통해 메서드 호출을 처리합니다. 사용자는 이 인터페이스를 구현하여, 원래 메서드 호출을 가로채어 추가적인 로직을 수행할 수 있습니다.

예시:

public interface Service {
    void perform();
}

public class ServiceImpl implements Service {
    @Override
    public void perform() {
        System.out.println("Service is being performed");
    }
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ServiceInvocationHandler implements InvocationHandler {
    private final Service target;

    public ServiceInvocationHandler(Service target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before service");
        Object result = method.invoke(target, args);
        System.out.println("After service");
        return result;
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        Service proxyInstance = (Service) Proxy.newProxyInstance(
            service.getClass().getClassLoader(),
            service.getClass().getInterfaces(),
            new ServiceInvocationHandler(service)
        );

        proxyInstance.perform();
    }
}

출력:

Before service
Service is being performed
After service

이 예제에서, 프록시는 실제 ServiceImpl 객체를 대리하여 메서드 호출 전후에 추가적인 로직(출력)을 실행합니다.

2. CGLib (Code Generation Library)

개념:

CGLib은 바이트코드 조작을 통해 런타임에 클래스를 동적으로 생성하여 프록시 객체를 만드는 라이브러리입니다. CGLib은 자바의 기본 라이브러리가 아니며, 외부 라이브러리로 제공됩니다. CGLib을 사용하면 인터페이스가 없는 클래스도 프록시를 생성할 수 있습니다.

동작 방식:

  • 상속 기반 프록시: CGLib은 원본 클래스의 서브클래스를 생성하여 프록시 객체를 만듭니다. 이 서브클래스는 원본 클래스의 모든 메서드를 오버라이드하여 호출을 가로챕니다.
  • Enhancer 클래스: CGLib의 핵심 클래스는 Enhancer로, 이를 사용하여 프록시 객체를 생성합니다. MethodInterceptor를 구현하여 메서드 호출 시 추가적인 동작을 정의할 수 있습니다.

예시:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class Service {
    public void perform() {
        System.out.println("Service is being performed");
    }
}

public class ServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before service");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After service");
        return result;
    }
}

public class CGLibExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service.class);
        enhancer.setCallback(new ServiceInterceptor());

        Service serviceProxy = (Service) enhancer.create();
        serviceProxy.perform();
    }
}

출력:

Before service
Service is being performed
After service

이 예제에서는 Service 클래스의 서브클래스를 생성하고, 메서드 호출 전후에 추가적인 로직을 실행합니다.

3. AspectJ

개념:

AspectJ는 가장 강력한 AOP 프레임워크로, 컴파일 시점, 로드 시점, 또는 런타임 시점에 바이트코드를 조작하여 AOP를 구현할 수 있습니다. AspectJ는 주로 어노테이션을 사용하여 코드 내에서 직접 AOP를 정의할 수 있으며, XML 설정을 통해서도 가능합니다.

동작 방식:

  • Aspect: AOP에서 정의한 모듈로, 메서드 호출, 예외 처리, 필드 접근 등 다양한 지점에서 추가적인 로직을 실행할 수 있습니다.
  • Pointcut: 메서드나 필드 접근과 같은 특정 지점을 지정하는 표현식입니다.
  • Advice: Pointcut에서 정의된 지점에서 실행되는 구체적인 로직입니다. Before, After, Around 등 여러 종류가 있습니다.

예시:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;

@Aspect
public class LoggingAspect {
    @Before("execution(* Service.perform(..))")
    public void beforeService() {
        System.out.println("Before service");
    }

    @After("execution(* Service.perform(..))")
    public void afterService() {
        System.out.println("After service");
    }
}

public class Service {
    public void perform() {
        System.out.println("Service is being performed");
    }
}

public class AspectJExample {
    public static void main(String[] args) {
        Service service = new Service();
        service.perform();
    }
}

출력:

Before service
Service is being performed
After service

이 예제에서 LoggingAspectService.perform() 메서드 호출 전후에 특정 로직(출력)을 추가합니다.

AspectJ는 다른 AOP 구현 방식보다 훨씬 강력하고, 다양한 기능을 제공합니다. 예를 들어, 메서드의 실행 전후, 특정 예외 발생 시, 혹은 객체 생성 시점에 로직을 삽입할 수 있습니다. 이러한 유연성과 강력함 덕분에 대규모 프로젝트나 복잡한 비즈니스 로직이 필요한 경우에 많이 사용됩니다.

요약

  • 다이나믹 프록시는 인터페이스 기반의 프록시 생성에 적합하며, 간단한 구조로 로깅이나 보안, 트랜잭션과 같은 부가 기능을 추가할 수 있습니다.
  • CGLib은 상속을 통한 프록시 생성으로 인터페이스가 없는 클래스에도 적용 가능하며, 성능이 우수합니다.
  • AspectJ는 AOP의 가장 강력한 구현체로, 다양한 시점에 부가 기능을 적용할 수 있으며, 복잡한 로직을 관리하는 데 유용합니다.

이 세 가지 방법은 각기 다른 장단점이 있어, 사용자의 요구사항에 따라 적절한 방식을 선택하는 것이 중요합니다.