Framework/Spring

[Spring] AOP는 뭘까?

KOOCCI 2022. 8. 13. 03:40

목표 : AOP에 대해 설명 할 수 있다.


Spring에서는 IoC Container 기능과 더불어, AOP를 아주 중요하게 생각한다.

그럼, AOP가 무엇인지부터 알아가보도록 하자.

 

Aspect Oriented Programming (AOP)

다음 링크를 보면, AOP에 대해 다음과 같이 설명하고 있다.

 

컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다. 

 

어떠한 language가 만들어 지기 전에, OOP, Functional Programming 같은 패러다임이 먼저 존재한다.

이 패러다임을 기반으로 특정 Language가 만들어진다.

 

JavaOOP 패러다임으로 만들어졌고, 이후에 AOP의 요구사항에 의해 AOP 패러다임도 수용한 언어다.

 

그럼 위 설명의 용어를 알아보도록 하자.

 

횡단 관심사(Cross-cutting concern)

프로그램을 개발하다보면, 공통 기능이 생기기 마련이다.

모든 Controller 혹은 특정 목적에 따라 만들어진 Service 들 등, Log와 같이 목적에 따라, 공통적으로 적용되어야 할 소스들이 있다.

 

다음 예시 이미지를 보도록 하자.

AOP 설명1

@Slf4j
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.refresh();
        MyService service = context.getBean(MyService.class);
        service.check();
        context.close();
    }
}

Java에 Main 메서드가 있을 것이고, Spring boot를 만들어도 Main 메서드가 있다.

위 코드에서는 context, service같은 객체들을 선언/할당하고, 메서드들을 실행하고 있다.

즉, 위 이미지에서 A~D Class Method가 이에 해당한다.

 

그런데, 공통 기능을 넣어주려고 한다고 해보자.

예를 들어, 트랜젝션 기능을 넣어주려고 한다.

 

그럼 우리는 각 Class마다, Transaction Start와 Auto Commit = false를 상위에 넣어주고, Try Catch 문으로 감싸준 다음에, Rollback 혹은 Commit 처리를 마지막에 해야할 것이다.

즉, 다음 이미지 같이 될 것이다. (보기 좋게 만들다보니, Commit/Rollback 실행 위치는 모순이 있을 수 있다. 이후에 예시로 정확히 알아보자.)

AOP 설명 2

그럼 바로 발견되는 문제점이 있을 것이다.

동일한 작업의 반복이다.

우리가 함수를 사용하는 이유 중 하나는 반복을 최소화 하기 위함이다.

그러나 위 상황에서, 각 박스들을 함수로 만든다고 하더라도, 각 클래스에서 호출해서 써야 하다보니, 그 함수 실행 자체가 반복된다.

 

그럼 관점을 바꾸어서 바라보도록 노력 해보자.

위 박스들을 각 Class에서 실행하지 않으면 된다.

공통된 부분이 실행되는 함수에 각 클래스에서 독립적으로 실행시키고 싶은 부분을 Argment로 넘겨주어, 실행하도록 만들면 된다.

 

그럼 여기서 공통된 부분들과 독립적으로 실행되어야 할 부분을 나누어 보자.

 

AOP 설명 3

 

우리가 원하는 건, 각 클래스의 성격에 따라, 독립적으로 실행될 것은 실행되고, 공통된 부분은 따로 관리되어, 실행되는 것을 원한다.

 즉, 공통된 부분에 해당하는 횡단 관심사(Cross Cutting)를 찾아내어 따로 관리되었으면 한다.

AOP 설명 4

우리는 최종적으로, 위 그림처럼 횡단 관심사를 분리해내어 관리하고, 각 클래스에서 독립적으로 실행된 로직을 따로 관리하는 걸 원한다. 다시 말하면, 횡단 관심사를 비즈니스 로직에서 감추는 것을 원한다.

이에 대한 해결책이 AOP, 관점 지향 프로그래밍이다.

 

AOP Concepts

일단 AOP Concepts를 보도록 하자.

이는 Spring에서만 사용하는 용어는 아니고, 직관적이진 않기 때문에 개념과 용어의 정의가 먼저 선행될 필요가 있다.

 

Join point

A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

메서드 실행 또는 예외 처리와 같은 프로그램 실행 중 한 지점. 

Spring에서는 항상 메서드 실행을 말한다. (AOP라는 것에 초점을 맞추면, 메서드가 아니라 클래스나 여러 방면에서 활용될 수 있지만, Spring에서는 메서드만 가능하다)

즉, 앞서 보았던 이미지에서 각 클래스의 독립 부분에 대한 후보지로 볼 수 있다.

프로그램이 실행되면, 여러 join point가 있을 수 있고, 어떤 클래스에서는 위 횡단 관심사가 필요없어, Join point가 없을 수 있다.

 

Pointcut

A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.

Join point에 대해서 판단할 수 있는 것.

여러 Join point에 대해서, 모든 클래스들 중에 AOP를 적용해야 할 부분에 대해서 정의한다.

1개 이상 Joint point를 묶어서 Pointcut이 될 수 있고,  1개 이상의 횡단 관심사가 실행될 수도 있다.

즉, Join point (독립 부분의 후보지) 중에서 실제로 횡단 관심사(Advice)가 적용되는 독립 부분을 나타낸다.

 

Advice

Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

특정 Join point에서 aspect에 의해 취해진 행위.

다시 말해, Join point가 언제 동작할지에 대해 설정하며, JoinPoint에서 실행되어야 하는 코드를 말한다.

위 이미지의 횡단 관심사 부분에 실제적으로 적용되는 코드다.

즉, 독립 부분의 앞, 뒤 등 언제 적용될 지를 설정하고, 어떤 코드가 실행될지에 대해 정의한다.

 

advice의 타입은 아래를 참고하자.

  • Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
  • After returning advice: Advice to be run after a join point completes normally (for example, if a method returns without throwing an exception).
  • After throwing advice: Advice to be run if a method exits by throwing an exception.
  • After (finally) advice: Advice to be run regardless of the means by which a join point exits (normal or exceptional return).
  • Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
  •  

Aspect

A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).

여러 클래스를 가로지르는 관심사의 모듈화라고 말한다.

즉, 앞서 보았던 이미지의 횡단 관심사 부분이다.

좀 더 거시적인 관점의 용어라고 할 수 있다. Advice와 Pointcut을 합친 것.

 

Target object

An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.

하나 이상의 aspect에 의해 동작하게될 객체.

위 이미지의 독립 부분이 구현되는 객체다.

 

AOP Proxy

An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

AOP는 Proxy 방식으로 동작한다.

아래에 별도로 설명을 한다.

 

Weaving

linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

Aspect들과 advise가 적용된 객체 사이의 연결이다.

다시 말해, 포인트컷으로 지정한 메소드가 호출될 때, Advice에 해당하는 메소드가 삽입되는 과정이다.

weaving은 Runtime에 진행된다.

 

 

AOP Proxy

 

Proxy라는 것은 A라는 Class가 있을 떄, 이 Class를 감싸는 A Proxy Class가 있고, 외부에서는 A Proxy Class를 실행시키며, A Proxy Class의 특정 동작을 한 후, A Class를 동작시키며, A Proxy Class가 원하는 대로 호출하여 동작시키는 과정이 있다.

 

AOP Proxy에 대해 다음 링크의 예시를 참고해 한번 보도록 하자.

public interface Pojo {
    void foo();
}
public class SimplePojo implements Pojo {

    public void foo() {
        System.out.println("run foo");
    }
}
public class Main {
    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        pojo.foo();
    }
}

패키지를 만들어, 위와 같이 구성하였다.

Proxy

앞서, Proxy에 대한 설명 했듯이, Proxy는 Plain Object (SimplePojo)를 부르기 전에, Proxy로 감싼다.

따라서, 코드를 호출할 때, Proxy를 통해 Plain Object를 호출하고, Proxy를 통해, 코드가 동작한다.

 

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();
    }
}

Proxy를 만들 때는 Java SDK에서는 Dynamic Proxy 기능을 사용하고, ProxyFactory를 만들고, 특정 Advice를 넣는 형태가 된다.

 

RetryAdvice가 없기 때문에, 해당 클래스도 만들어준다.

public class RetryAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return null;
    }
}

invoke 메서드에 null을 리턴하다보니, pojo.foo()를 호출해도 동작하지 않는다.

public class RetryAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before");
        Object proceed = invocation.proceed();
        System.out.println("After");
        return proceed; 
    }
}

위와 같이, Proxy를 통해 proceed를 리턴하여, pojo.foo()가 동작하게끔 만들 수 있다.

위 방식도 Spring에서 제공하는 일종의 AOP이다.

 

Wrap up

AOP는 개념부터 잡아야 하다보니, 블로깅을 나누어서 할 예정이다.

우선 AOP의 개념을 알아보았고, 이후에 AOP를 어떻게 Spring에서 사용하는지 알아보도록 하자.