본문 바로가기

연재작/프로그래밍 언어

Aspect Oriented Programming

 

요즘 DB와 Transaction에 대한 공부를 하면서 AOP라는 흥미로운 개념에 대해 배우게 됐다.

방식 자체도 특이하지만 기존에 배웠던 Lombok과 유사한 것처럼 보여서
해당 개념을 설명하고 어떤 부분이 다른지를 설명하고자 한다.

 

 

1. Aspect Oriented Programming

 

"In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding behavior to existing code (an advice) without modifying the code, instead separately specifying which code is modified via a "pointcut" specification, such as "log all function calls when the function's name begins with 'set' ". This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code of core functions." - wikipedia

 

횡단 관심사를 분리모듈성을 증가 시키는 것을 목적으로 하는 프로그래밍의 패러다임이다.

코드를 변형시키지 않고 코드에 추가 동작을 통해 AOP를 지원한다.

logging과 같은 주요 로직과 동떨어진 코드의 부분들을
기존의 비즈니스 로직의 핵심을 유지하면서 코드를 쓰지 않고도 사용할 수 있게 해준다는 것이다.

 

이게 무슨 말인가??? 싶을 것이다.

일단 횡단 관심사가 뭔지를 알아보자.

 

일례로 Spring MVC에서 어떤 메서드를 통해 비즈니스 로직을 처리할 때, 
이 비즈니스 로직을 수행함과 동시에 모든 메서드에서는 audit과 같은 로깅 작업을 진행한다고 하자.
그러면 이 application의 핵심 로직(관심사)은 service에서 다루는 비즈니스 로직이지,
audit은 실제로 비즈니스 로직 실행에 아무런 영향을 주지 않는 2차적인 로직이 될 것이다.

 

그리고 모든 서비스마다 이러한 2차적인 로직이 있을 것이기 때문에 이러한 흐름을 아래의 그림처럼 표현할 수 있을 것이다.

 

여기서 logging과 security가 바로 "횡단 관심사" 가 되는 것이다.

 

그러면 loggin, security와 같은 횡단 관심사는 각 메서드에서 코드상으로 공통적으로 표현이 될 것이다.

이러한 공통적인 횡단 관심사를 나타낸 코드를 쓰지 않고도 가능하게 해주는 것이 바로 AOP라고 할 수 있겠다.

 

 

2. 사용 방법

 

Spring에서의 AOP는 @Annotation으로 구현된다.

코드를 안보이게 해줄 뿐이지, 실제로 코드를 따로 작성한 후 적용하는 모듈화가 베이스이다.

사용은 다음과 같이 진행된다.

  1. @{AopAnnotation}을 작성한다.
  2. @Component 를 사용한 bean의 형태로 아래의 코드처럼 aop기능을 할 class를 작성한다.
  3. 사용할 class에서 @ComponentScan을 달아주고, 해당 메서드에 지정한 어노테이션을 지정해준다.

Transaction과 같은 경우, @Transactional 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceCheck {
}

 

@Aspect
@Component
@Slf4j
public class TimeCheckAspect {

    @Around("@annotation(PerformanceCheck)")
    // Point Cut : @PerformanceCheck 달린 메서드에 Aspect 로직 적용
    // 같은 package에 속하지 않을 경우 경로를 설정해주어야 한다.
    public Object timeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
   
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            return joinPoint.proceed();
        } finally {
            stopWatch.stop();
            log.info("request spent {} ms", stopWatch.getLastTaskTimeMillis());
        }
    }
}

 

 

 

3. AOP의 일반적인 구현 원리

 

 

크게 compile time 상에 정적으로 메서드를 조작하는 방식과

동적으로 runtime에 해당 메소드를 wrapping 하는 프록시 객체를 생성하는 방식이 존재한다.

물론 각각의 결과는 해당 메서드를 감싸고 있는 핵심 로직 메서드에서
부가적인 관점을 가지고 있는 로직들을 따로 더해주는 방식으로 동일할 것이겠지만 말이다.

Spring에서 AOP를 사용하기 위한 몇 가지 방식과 그에 대한 구현 방식을 설명하고자 한다.

참고로 weaving(짜깁기, 직조)라는 단어를 알아두면 좋을 것이다.

 

  • Compile Time Weaving : Complie time에 바이트 코드를 직접적으로 조작해서 AOP를 구현한다.
    • Spring의 경우, AspectJ를 통해 이루어진다.
    • 컴파일 타임에 결정되기 때문에, 런타임에서 속도와 기능적인 측면에서 좋은 성능을 보인다.
    • 그렇지만 바이트코드 조작을 위해 AJC라는 별도 컴파일러의 사용이 필요하다.
    • 따라서 일반적으로는 아래에서 기술할 Spring AOP를 사용하고,
      이것 이상의 기능이 필요할 경우 AspectJ를 통한 방식을 확장해서 사용한다.
  • Runtime Weaving : Runtime에 프록시 객체를 생성하여 타겟 객체는 변경하지 않고 사용한다.
    • Spring AOP를 통해서 runtime에 프록시 객체를 생성한다.
    • 이 또한 Spring 내부에서 두 가지 방식을 통해 구현한다
    • 타겟 객체의 interface가 존재하지 않는다면 CGLIB의 방식을 통해 구현하고, interface가 존재한다면 JDK Dynamic Proxy를 통해서 구현한다.
      • CGLIB 
        • Spring 에서 AOP의 표준이다.
        • runtime에 클래스의 바이트 코드를 통해 프록시 객체를 구현해 사용한다.
        • target 객체를 상속하는 프록시 객체를 구현한다. (interface 없이도 구현이 가능하다)
        • 바이트 코드 조작을 통해서 생성되기에 후술할 JDK Proxy의 방식보다는 성능상으로 조금 더 낫다.
      • JDK Dynamic Proxy
        • Java 표준 (과거 Spring 표준도 JDK proxy였지만, 현재는 CGLIB이다.)
        • runtime에 Reflection API를 통해 target 객체에 대한 프록시 객체를 구현한다.
        • 이 때, 반드시 해당 target 객체의 interface가 구현되어 있어야 한다.
        • 구체적인 동작 원리는 아래와 같다. 
public class ServiceInvocactionHandler implements InvocationHandler {
    private final Object target;
    
    public Object invoke(Object proxy, Method method, Object[] args) {
        
        // 부가적인 Aspect 로직
        Object result = method.invoke(target, args);
        // 부가적인 Aspect 로직
        
        return result;
    }
}

 

✅ 위와 같이 Java Reflection API InvocationHandler 인터페이스를 구현하는 코드의 작성이 필요하다.
여기에 AOP를 위한 부가로직을 추가해야 한다.

IUserService proxy = (IUserService) Proxy.newProxyInstance(
    IUserService.class.getClassLoader(),
    new Class[] {IUserService.class},
    new ServiceInvocationHandler(new UserService())
)

 

✅ target 객체를 변수로 가지는 proxy 객체를 interface로 생성한다. 위와 같이 생성해주어야 한다.
이 때 interface를 기반으로 Reflection API를 통해 proxy객체가 만들어지는 것이기 때문에
private 접근 제어자를 사용한 메서드는 @Transactional을 통한 선언형 트랜잭션이 불가능하다.

(private 메서드는 Interface를 통한 사전 정의가 불가능 하기에 override도 불가능)
혹은 final 키워드로 형성된 메서드(아예 메서드 자체가 재정의가 불가능 하다)에 대해서
@Transactional 과 같은 AOP의 적용은 불가능하다.

 

 

 

4. Reflection API란?

 

구체적인 클래스 타입을 알지 못해도 변수, 메서드, 타입에 접근할 수 있도록 해주는 자바 API이다.

자바 컴파일러가 사용자가 작성한 자바 코드를 바이트 코드로 변환하여 heap영역에 저장한다.

Class Loader가 Runtime Data Area의 heap영역에 바이트 코드를 적재한다.

이를 사용해서 해당 클래스의 메서드에 접근하는 것이다.

또한, 이렇게 접근하는 방식은 private접근제어자로 외부에서의 접근이 불가능한 필드나 메서드 또한 접근이 가능하다.

다만, 이렇게 될 것을 방지해 setAccessible과 같은 Reflection API 사용의 접근 여부를 설정할 수도 있다.

 

 

여기서 의문점은 reflection API를 사용하면 private 접근자의 접근도 가능할텐데

@Transactional 과 같은 선언형 트랜잭션이 붙은 메서드의 경우,
왜 AOP의 적용이 불가능한 것일까? 이다.

 

그 이유는 Interface 기반으로 proxy객체를 생성하고,

다형성을 통해 Interface로 정의된 proxy객체의 메서드만을 사용하기 때문이다.

그렇기에 private 메서드를 사용한 tranactional 메서드는 선언형 트랜잭션을 사용할 수 없다.

private 메서드를 사용했을 경우, TransactionTemplate을 통해 프로그래밍형 트랜잭션을 구현해야한다.

 

 

 

5. Lombok과 같은 점

 

만약 관점 지향 프로그래밍을 위해 AspectJ를 통해 AOP를 구현할 경우,

컴파일 시점에 바이트코드를 조작한다는 점에서 Lombok과의 공통점이 존재한다.

(그러나 엄밀히 말하면 이 부분에서도 다르다

Lombok은 컴파일 시점에 AST에 메서드를 주입하는 방식이지만,

AspectJ는 컴파일 시점에 컴파일된 바이트 코드를 직접 조작하는 방식이기에

시점 자체도 다르다.)

또한 코드의 가시성과 편의를 위해서 사용된다는 점도 AOP와 비슷할 수 있다.

 

그러나.

 

 

6. Lombok과 다른 점. 왜 Lombok은 AOP라고 볼 수 없는가?

 

왜 Lombok은 AOP를 반영한 것이라고 볼 수 없을까?

이는 Lombok에는 관심사의 분리, 횡단 관심사의 분리에 대한 개념이 전혀 들어가있지 않기 때문이다.

 

Lombok과 같은 경우, 개발자의 편의를 위해서 사용되는 것이지 관심사의 분리를 위해서 사용된다고 볼 수는 없다.

가령 @AllArgsConstructor와 같은 경우, 이러한 생성자라는 메서드 자체를 생성해주는 것이지,

객체를 생성해줘서 핵심로직의 관심사를 없애거나 코드의 context의 공통 관심사를 덜어주는 행위의 annotation은 아니다.

 

마찬가지로 @Setter와 같은 경우, setter는 오히려 핵심 로직의 메서드 호출로 사용이 되는 경우도 많기에

관심사를 덜어주는 AOP의 관점에 속하지 않는다.

결론적으로 Lombok은 관심사의 분리를 행하는 프로그래밍이라기 보다는

순수하게 개발자의 편의에 관심을 두고 있기에 AOP가 될 수 없다.

 

한편, Spring AOP의 경우 runtime에 동적으로 작동되는 것이기에
compile time에 정적으로 메서드를 주입하는 lombok과는 다르다고 볼 수도 있다.

 

 

https://en.wikipedia.org/wiki/Aspect-oriented_programming

 

Aspect-oriented programming - Wikipedia

From Wikipedia, the free encyclopedia Programming paradigm In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding behavior to exi

en.wikipedia.org

https://yejun-the-developer.tistory.com/6#:~:text=%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EC%9D%80%20%27%EA%B5%AC%EC%B2%B4%EC%A0%81%EC%9D%B8%20%ED%81%B4%EB%9E%98%EC%8A%A4%20%ED%83%80%EC%9E%85%EC%9D%84%20%EC%95%8C%EC%A7%80%20%EB%AA%BB%ED%95%B4%EB%8F%84%20%EA%B7%B8%20%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98%20%EB%B3%80%EC%88%98%2C%20%EB%A9%94%EC%84%9C%EB%93%9C%2C%20%ED%83%80%EC%9E%85%EC%97%90%20%EC%A0%91%EA%B7%BC%ED%95%A0%20%EC%88%98%20%EC%9E%88%EB%8F%84%EB%A1%9D%20%ED%95%B4%EC%A3%BC%EB%8A%94%20%EC%9E%90%EB%B0%94%20API%27%EC%9E%85%EB%8B%88%EB%8B%A4.

 

[Spring] 다이내믹 프록시(DynamicProxy)

(프록시에 대한 이해가 부족하신 분들은 이전 포스팅을 참고하세요!!) [Spring] 프록시와 디자인패턴 프록시와 디자인 패턴 스프링의 3대 기반기술 중 AOP를 공부하던 중 관심사 분리를 위한 다이내

yejun-the-developer.tistory.com

 

'연재작 > 프로그래밍 언어' 카테고리의 다른 글

IntegerCache, Atomic, CAS  (2) 2024.11.16
Java Casting, Generic 응용  (0) 2024.11.12
Java - Optional 뽀개기 (2)  (0) 2024.10.11
Java - Optional 뽀개기 (1)  (0) 2024.10.11
Java - Stream 뽀개기 (2)  (2) 2024.10.06