5. Aspect Oriented Programming with Spring

    5.1 Introduction

    • Aspect Oriented Programming (AOP)는 프로그램 구조에 대한 다른 관점을 제공함으로써 OOP를 보완함
      • OOP의 modularity 주요 단위: class
      • AOP의 modularity 주요 단위: aspect
        • aspect는 여러 타입과 객체에 걸친 트랜잭션 관리와 같은 문제를 모듈화하는 것을 가능하게 함

     

    • AOP는 Spring의 주요 요소 중 하나임.
      • Spring IoC 컨테이너가 AOP에 의존성을 가지지 않지만,
      • AOP는 괜찮은 middleware 솔루션을 제공하면서 Spring Ioc를 보완함

     

    • 읽고있는 문서가 Spring 5.0.0.RELEASE 버전인데, 이 문서에서는 Spring 2.0 AOP에 대해서 다룬다고 함

     

    • AOP 기능
      • 선언적인 enterprise service를 제공함 (e.g. declarative transaction managemet)
      • custom aspect 구현 가능

     

     

    5.1.1. AOP concepts

    • 용어 정리: Spring-specific한 용어가 아님
      • Aspect: 여러 class들을 걸쳐 있는 문제를 모듈화한 것
        • ex) 트랜잭션 관리
        • Spring AOP에서 aspect는 일반 클래스 (schema-based approach) 또는 @Aspect 어노테이션이 붙은 일반 클래스를 사용해서 구현됨
      • Join point: 프로그램을 실행 중인 지점
        • ex) 메서드 실행, 예외 처리
        • Spring AOP에서 join point는 항상 메서드 실행을 나타냄
      • Advice: 특정 Join point에서 Aspect에 의해 수행되는 action
        • 종류: around, before, after
        • Spring을 포함한 많은 AOP 프레임워크에서 advice를 interceptor로 모델화하고, join point 근처에서 interceptor들의 chain을 유지함
      • Pointcut: join point와 매치되는 predicate
        • Advice는 pointcut expression과 관련이 있음.
          • Advice는 pointcut에 매치되는 join point에 실행함 (ex. 특정 이름의 메서드를 실행)
        • join point의 개념은 AOP의 중심이고, Spring은 디폴트로 AspectJ pointcut expression 언어를 사용함
      • Introduction: 타입을 대신하여 추가적인 메서드나 필드를 선언하는 것
        • Spring AOP는 새로운 인터페이스와 관련된 구현체를 advised object에 도입하는 것을 도움
        • ex) bean이 IsModified라는 interface를 구현하게 하기 위해 introduction을 사용할 수 있음. 이것은 caching을 단순화하게 함.
        • AspectJ에서는 inter-type declaration으로도 알려져있음
      • Target Object (advised object): 하나 이상의 aspect에 의해 advised되는 객체
        • Spring AOP는 runtime proxy를 사용해서 구현되기 때문에, 이 객체는 항상 proxied 객체임
      • AOP proxy: aspect contract(타겟 메서드 실행 등...)를 구현하기 위해 AOP에 의해 만들어진 객체
        • Spring 프레임워크에서 JDK 동적 proxy 또는 CGLIB proxy를 사용함
      • Weaving: 타겟 객체를 만들기 위해 aspect와 다른 application 객체를 연결하는 것
        • complie time (AspectJ 컴파일러를 사용해서), load time 또는 runtime에 실행될 수 있음
        • 다른 pure한 Java AOP 프레임워크와 달리 Spring AOP는 런타임에 weaving을 수행함

     

    • Types of advice
      • Before advice: join point 전에 실행되는 advice
        • 예외를 발생하지 않는 이상, join point로 진행되는 실행 흐름을 막을 수 없음
      • After returning advice: join point가 평범하게 끝난 후 실행되는 advice
        • ex) 예외 발생 없이 메서드가 끝났을 때
      • After throwing advice: 예외가 발생해서 메서드가 종료되면 실행되는 advice
      • After (finally) advice: join point가 어떻게 종료되는지와 상관없이 실행되는 advice
      • Around advice: 메서드 발생처럼 join point를 감싸고 있는 advice
        • 가장 강력한 advice
        • method 발생 이전과 이후에 custom behavior를 수행할 수 있음
        • 원래의 return 값을 리턴하거나 예외를 발생시켜서, join point로 이동할지 아니면 advised method 실행으로 바로 이동할지 선택해야함

     

    • Around Advice: 가장 일반적인 advice
      • Spring AOP가 모든 범위의 advice type을 제공하기 때문에 가장 덜 강력한 advice type을 사용하는 것을 ㅜ천함
      • ex) 메서드의 리턴 값으로 캐시를 업데이트만 하려고 할때는, around advice보다 after returning advice를 사용해서 구현하는 것이 낫다.
      • 가장 구체적인 advice type을 사용하는 코드가 에러가 발생할 가능성이 적다.

     

    • Spring 2.0에서, 모든 advice parameter는 정적으로 타입화되었다.
      • Object arrays 대신 적절한 타입의 advice 파라미터를 사용하기 위해서이다.

     

    • join point의 개념은 interception만 제공하는 예전 기술과 AOP를 구분하는 AOP의 주요 요소이다.
      • pointcut은 advice가 객체지향 hierarchy를 독립적으로 타겟화하는 것을 가능하게 한다.
      • ex) declarative transaction 관리를 제공하는 around advice는 여러 객체들에 걸쳐있는 메서드의 집합(e.g. service layer의 모든 business operation)에 적용될 수 있다.

     

     

    5.1.2. Spring AOP capabilities and goals

    • Spring AOP는 pure한 Java로 구현된다. 특별한 컴파일 절차 이런거 필요없다. Spring AOP는 class loader hierarchy를 제어할 필요가 없기 때문에, 서블릿 컨테이너나 application 서버에 사용하는 것이 적절하다.

     

    • Spring AOP는 메서드 실행 join point만 지원한다. Field interception은 구현되지 않는다. field access를 advise하고 join point를 업데이트할  필요가 있다면, AspectJ와 같은 언어를 참고해라.

     

    • Spring AOP의 AOP 접근은 다른 대다수의 AOP 프레임워크와 다르다. 
      • Spring AOP의 목적은 가장 완벽한 AOP 구현체를 제공하는 것이 아니다.
      • enterprise aplication에서의 일반적인 문제를 해겨하는 것을 돕기 위해 AOP 구현과 Spring IoC 사이의 밀접한 통합을 제공한다.

     

    • 따라서 Spring AOP는 Spring IoC 컨테이너와 일반적으로 결합해서 사용된다.
      • Aspect는 일반적인 bean 정의 syntax를 사용해서 구성된다. (다른 AOP 구현체와 다른점!)
      • Spring AOP에서 쉽게 할 수 없는 것이 있다.
        • fine-grained object를 advise하는 일 (일반적으로, domain objects)
        • AspectJ는 이러한 경우 사용하면 좋다.
      • 그런데 웬만해서는 Spring AOP는 대부분 문제를 잘 해결한다.

     

    • Spring AOP, AspectJ는 경쟁관계가 아닌 보완관계

     

     

    5.1.3. AOP Proxies

    • Spring AOP는 AOP proxy로 기본적으로 표준 JDK dynamic proxy를 사용함.
      • 모든 interface가 proxy화되는 것을 가능하게 함

     

    • Spring AOP는 CGLIB proxy를 사용할 수 있음.
      • interface말고도 class를 proxy화하는 것은 필수적임
      • 객체가 interface를 구현하지 않는다면 CGLIB가 기본적으로 사용된다.
        • 일반적으로 class를  사용하는 것보다 interface를 사용해서 프로그래밍을 하는 것이 더 좋기 때문에, class들은 웬만하면 하나 이상의 interface를 구현하고 있을 것임
        • 다음과 같은 상황에 CGLIB의 사용을 강제하는 것이 가능함
          • 인터페이스에 정의되지 않은 메서드를 advise하는 경우
          • proxied 객체를 concrete type으로써의 메서드로 pass하는 경우

     

    • Spring AOP는 proxy 기반이다.

     

     

    5.6. Proxing mechanisms

    5.6.1. Understanding AOP proxies 

    • Spring AOP는 proxy 기반이다.

     

    첫번째 상황) plain-vanila, unproxied, straight object reference

    public class SimplePojo implements Pojo {
    
            public void foo() {
                    // 'this' reference에 대한 직접 호출
                    this.bar();
            }
    
            public void bar() {
                    // some logic...
            }
    }

    인스턴스에서 메서드를 실행시키면, 해당 인스턴스에서 메서드가 직접 호출됨

     

    두번째 상황) client code가 proxy일 때

    public class Main {
    
            public static void main(String[] args) {
    
                    Pojo pojo = new SimplePojo();
    
                    // this is a direct method call on the 'pojo' reference
                    pojo.foo();
            }
    }

    public class Main {
    
            public static void main(String[] args) {
    
                    ProxyFactory factory = new ProxyFactory(new SimplePojo());
                    factory.addInterface(Pojo.class);
                    factory.addAdvice(new RetryAdvice());
    
                    Pojo pojo = (Pojo) factory.getProxy();
    
                    // this is a method call on the proxy!
                    pojo.foo();
            }
    }
    • 여기서 주목해야할 점은 Main 클래스의 main 메서드는 proxy로의 reference를 가진다는 것이다.
      • 객체 reference의 메서드 호출은 proxy에 대한 호출을 뜻한다.
      • 이와 같은 proxy는 특정한 메서드 호출에 관련된 모든 interceptors(advices)를 위임할 수 있다.
        • 하지만 호출이 마지막으로 타겟 객체에 도달한다면 (이 경우에 SimplePojo reference)
        • this.bar(), this.foo()와 같이 자체적으로 발생할 수 있는 모든 메서드 호출은 proxy가 아니라 객체 reference(this)에 대해 호출됨
      • self-invocation은 method invocation과 관련된 advice가 실행 기회를 얻게 하지 않음

     

    • 해결책) self-invocation이 일어나지 않도록 코드를 refactor하기

     

    세번째 상황) client code와 Spring AOP를 묶기

    public class SimplePojo implements Pojo {
    
            public void foo() {
                    // this works, but... gah!
                    ((Pojo) AopContext.currentProxy()).bar();
            }
    
            public void bar() {
                    // some logic...
            }
    }
    • 좋지 않은 방법이다.
    • class 자체가 AOP context에서 사용되고 있음을 인지하게 함. proxy가 생성될 때 추가적인 configuration을 요구함
    public class Main {
    
            public static void main(String[] args) {
    
                    ProxyFactory factory = new ProxyFactory(new SimplePojo());
                    factory.adddInterface(Pojo.class);
                    factory.addAdvice(new RetryAdvice());
                    factory.setExposeProxy(true);
    
                    Pojo pojo = (Pojo) factory.getProxy();
    
                    // this is a method call on the proxy!
                    pojo.foo();
            }
    }

     

    • AspectJ는 proxy 기반 AOP 프레임워크가 아니기 때문에 self-invocation 문제가 없음

     

     

    Core Technologies

    In the scenario above, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do you

    docs.spring.io

     

    '백엔드 > Spring' 카테고리의 다른 글

    build.gradle와 build 과정 살펴보기  (0) 2023.04.07
    Docker(도커)  (0) 2023.03.31
    1. The IoC container  (0) 2023.03.15
    JPA와 연관관계 (with MySQL) (1)  (0) 2022.10.20
    JWT 토큰을 이용한 로그인 구현 (1)  (0) 2022.10.13

    댓글