간단한 Spring AOP 개념과 적용
Java

간단한 Spring AOP 개념과 적용

안녕하세요. 이번에는 AOP를 적용해볼까 합니다. 

 

AOP 공부를 하면서 용어들이 헷갈리기에 정리 할 시간을 가져보려고 하는데요. 한번 천천히 알아보도록 하겠습니다.

 

모든 코드는 https://github.com/lkimilhol/tistoryblog에서 확인 가능합니다.

 

1. AOP란?


AOP의 개념은 많은 분들이 아실거라 생각해요. AOP란 Aspect Oriented Programing의 약자로써 관점지향 프로그래밍이라고 불립니다. 그렇다면 관점지향 프로그래밍이란 무엇일까요?

제가 생각하는 관점지향 프로그래밍이란 공통의 관심사를 묶어놓는 프로그래밍이라고 생각하는데요.즉, 주요 기능과 부가 기능이 있을 때 공통 되는 부가기능을 하나의 처리로 묶고 주요 기능의 소스코드와 분리를 시키는 것이라고 생각해요. 이렇게 된다면 주요 기능의 소스코드는 부가기능의 소스코드와 섞이지 않고 코드를 맥락적으로 작성 할 수 있는 장점이 생기게 됩니다.

관점지향 프로그래밍이라고 해서 객체 지향 프로그래밍과 대립 되는 것 처럼 느껴질 수 있지만, 결국엔 관점지향 프로그래밍은 공통의 기능을 묶어 처리해주는 기능이기 때문에 객체지향을 좀 더 객체지향으로 만들어 줄 수 있는 것입니다.

관점지향 프로그래밍을 좀 더 이해하기 쉽도록 그림으로 표현해보려고 하는데요.
일단 코드를 먼저 보도록 합시다. 예시로 작성한 계산기 프로그램입니다.

 

Calculator.java

public class Calculator {
    private int value;

    public Calculator(int value) {
        this.value = value;
    }

    public void add(int value) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        this.value += value;

        stopWatch.stop();
        System.out.println(stopWatch);
    }

    public void sub(int value) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        this.value -= value;

        stopWatch.stop();
        System.out.println(stopWatch);
    }

    public void mul(int value) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        this.value *= value;

        stopWatch.stop();
        System.out.println(stopWatch);
    }

    public int get() {
        return value;
    }
}

계산기는 덧셈, 뺄셈, 곱셈의 기능을 가지고 있어요. 이 때 각 연산이 얼마나 걸렸는지 확인하기 위해 StopWatch 클래스를 사용했습니다.

 

하지만 코드를 보니 너무 많은 중복 코드가 발생하고 있는데요. 이 부분은 각 연산의 핵심 기능 코드를 복잡하게 할 뿐만 아니라 같은 역할임에도 불구하고 각기 다른 메서드에 계속해서 자리잡고 있네요. 그럼 이제 이 내용을 그림으로 파악해보겠습니다.

 

StopWatch를 sw로 표현하였습니다!

add(), sub(), mul() 메서드의 행동(실제로는 this.value += value 같은 역할을 표현한 것입니다) 위 아래에 공통으로 sw.start, sw.stop이라는 역할들이 들어가네요. 우리는 이 역할을 공통 기능으로 생각하려 합니다. 

 

이 역할을 가로로 보니 공통의 역할들이 눈에 띄게 되는데요. 이런 공통의 기능을 공통 관심 사항(cross-cutting concern)이라고 합니다. 공통 관심 사항을 횡단(가로)으로 잘라 공통 관심 사항을 추출하기에 AOP를 cross-cutting 이라고 부르기도 합니다. 

 

2. AOP의 용어


AOP의 개념들에 대해서 살펴보았는데요. 그렇다면 AOP는 어떤 기능들을 가지고 있어 cross-cutting을 구현 할 수 있을까요?

다음은 AOP의 Aspect라고 불리우는 클래스입니다. Aspect란 공통 기능을 어떻게 언제 구현할지 정의한 곳이라고 생각할 수 있어요. 

 

먼저 용어에 대해서 정의를 하고 넘어가겠습니다.

 

타겟(Target)

타겟(Target): 프록시로 감쌀 클래스를 뜻합니다. Calculator 클래스가 타겟이 될 수 있겠네요.

 

어드바이스(Advice)

공통 기능을 구현한 구현체입니다. 어드바이스는 공통 기능을 언제 할지를 정의 하는데요.
어드바이스는 어떤 공통 기능을 언제 할지를 구현한 것입니다.

 

포인트컷(PointCut)

어드바이스가 적용 될 메서드를 선정하는 방법입니다. 

 

조인포인트(JoinPoint)

실제 어드바이스가 적용되는 메서드 입니다. 포인트컷 설명시에도 메서드에 볼드체 처리를 하였는데요. 스프링에서는 어드바이스의 적용 대상을 메서드로 한정하고 있습니다. 다른 AOP프레임워크에서는 필드나 클래스에도 적용 가능한데요. 스프링 AOP 한해서는 메서드에만 적용이 되니 잊지 말아야 하겠습니다!

 

perfCheck.java

@Aspect
@Component
public class PerformanceCheck {
    @Around("execution(* com.example.blogjava..*(..))")
    public Object printlnPerf(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object proceed = joinPoint.proceed();

        stopWatch.stop();
        System.out.println(stopWatch);
        return proceed;
    }
}

PerformanceCheck라는 클래스입니다. 이 클래스는 앞서 설명드린 공통의 기능을 구현한 것이에요. 위의 코드의 애노테이션들과 ProceedingJoinPoint 등으로 우리는 공통 기능 모듈을 구현 할 수 있는 것입니다. 차근 차근 살펴 보도록 하겠습니다.

 

@Around가 바로 Advice인데요. 공통 기능을 언제 적용할지를 표현하는 것입니다. 그리고 "execution(* com.example.blogjava..*(..))" 구문이 바로 PointCut 인데요. 바로 어디에 적용할지를 명시해주고 있어요.

 

때문에 Aspect는 Advice와 PointCut의 기능을 가지고 있는데요. 보통 Aspect는 Advice와 PointCut을 함께 가지고 있다고 표현합니다.

 

@Arond가 Advice라고 하였는데요. 그렇다면 Advice는 어떤 애노테이션들을 가지고 있을까요?

 

  • Before : 메서드 호출 전
  • After Returning : 메서드가 실행된 이 후
  • After Throwing : 메서드 예외가 발생했을 때 이 후
  • After : 예외 발생에 상관없이 메서드 호출 후
  • Around : 메서드의 실행 전과 후

이런식으로 우리가 정의한 애노테이션으로 '언제' 동작할지를 명시 해줄 수 있습니다. 다음은 PointCut인데요. PointCut의 정규식은 다음과 같은 규칙을 가지고 있습니다.

 

규칙이 조금 많을 수 있는데요. 몇 가지 주요 기능들을 살펴보는 방식으로 진행해보겠습니다.

먼저 표현식에는 3가지가 있습니다. execution, within, bean인데요.

 

표현식을 실제로 어떻게 적용할지 파악하면서 어떤 기능들을 제공하는지 보도록 하겠습니다.

기본적으로 *는 모든 값을, ..은 0개 이상을 뜻해요.

 

execution

  • execution(* com.example.aop..()): com.example.aop 패키지의 파라메터가 0개인 모든 메서들에게 적용즉 여기서 com.example.aop..(..) 으로 표현한다면 com.example.aop 패키지의 파라메터가 0개 이상인 모든 메서들에게 적용이 된다는 뜻입니다.
  • execution(* com.example.aop...()): com.example.aop 하위 패키지의 파라메터가 0개인 모든 메서들에게 적용
  • execution(Integer com.example.aop...()): com.example.aop 하위 패키지의 리턴 타입이 Integer이고 파라메터가 0개인 메서드들 에게 적용
  • execution(* add*(*)): 이름이 add로 시작하고 1개의 파라미터를 갖는 메서드들 에게 적용

execution은 패키지의 특정 패턴을 가지는 메서드들을 적용 대상으로 명시합니다.

 

within

  • within(com.example.aop.ExampleService): ExampleService 인터페이스의 모든 메서드
  • within(com.example.aop.*): com.example.aop 패키지의 모든 메서드
  • within(com.example.aop..*): com.example.aop 패키지와 하위 패키지에 있는 모든 메서드

within은 패키지나 인터페이스 들을 명시하여 그에 포함된 모든 메서드들을 적용 대상으로 명시합니다!

 

bean

  • bean(ExampleService): 빈의 이름이 ExampleService인 메서드

bean은 빈의 이름을 명시하여 그 빈으로 지정 된 클래스, 인터페이스에 포함 된 모든 메서드들에게 적용합니다.

 

 

이처럼 PointCut 또한 여러가지 표현 기능들을 가지고 있어 우리에게 필요한 적용 대상들을 선정할수 있습니다.

 

마지막으로 JoinPoint는 실제로 선정 된 메서드를 뜻하는데요. 위의 코드에서는 add메서드나 sub 메서드 혹은 mul 메서드가 될 수 있겠네요. 코드를 보면 joinPoint라는 변수가 proceed 함수를 호출하고, 그 호출의 위 아래에 StopWatch 기능이 작성 된 것을 볼 수 있습니다.

 

우리가 전부터 관심을 가졌던 공통의 기능이 구현 된 것입니다!

 

3. 실제 적용 결과


실제로 적용된 결과를 보겠습니다. 먼저 SpringBootApplication에 다음 애노테이션을 추가해줍니다.

@EnableAspectJAutoProxy

AOP 기능을 사용하기 위하여 애노테이션을 추가하였습니다

간단한 컨트롤러를 구현하였습니다. 

 

TestController.java

@RestController
public class TestController {
    @GetMapping("/test")
    public void test() {
        Calculator calculator = new Calculator(10);
        calculator.add(10);
        System.out.println(calculator.get());
    }
}

add 메서드를 사용했네요. 우리는 공통 기능을 AOP로 구현할 예정이니 StopWatch에 관련 된 모든 코드를 제거해줍니다.

 

Calculator.java

public class Calculator {
    private int value;

    public Calculator(int value) {
        this.value = value;
    }

    public void add(int value) {
        this.value += value;
    }

    public void sub(int value) {
        this.value -= value;
    }

    public void mul(int value) {
        this.value *= value;
    }

    public int get() {
        return value;
    }
}

이제 스프링부트를 실행시키고 직접 브라우저를 통해 테스트로 만든 Get 메서드를 호출하도록 합니다.

 

아래에 StopWatch의 동작이 실행 된 것을 확인할수 있습니다!

 

4. AOP 동작 타임과 위빙(Weaving) 용어


오늘 Spring AOP에 관하여 알아보았는데요. 사실 Spring AOP가 아닌 AspectJ를 사용하게 된다면 위에서 설명드린 내용보다 더 많은 내용을 포함해야합니다. Spring AOP의 경우에는 런타임에 동작하지만 AspectJ의 경우에는 컴파일 타임이나 클래스 로더가 메모리에 올린 byte 코드가 실행 될 때 동작 할 수도 있습니다. 이렇게 컴파일 타임, 클래스로드 타임, 런타임에  Proxy 객체를 생성하는 과정을 위빙(Weaving)이라고 합니다. 

 

이로써 AOP가 실제로 실행되는 타이밍까지 체크를 해 보았는데요. 실제로 우리는 런타임에 Proxy객체를 통하여 AOP를 경험 해본적이 있습니다. 바로 @Transactional 애노테이션입니다. 이 애노테이션이 Spring AOP 기반으로 만들어져 있습니다.

 

5. 마치며


용어가 복잡하여 직접 공부한 Spring AOP의 내용입니다. 공부를 하고 정리를 했는데도 용어는 계속해서 헷갈리네요 @_@;

 

긴 글 읽어주셔 감사합니다!

'Java' 카테고리의 다른 글

제네릭에 대한 간단한 정리  (0) 2021.10.06
Java의 Call by value, Call by reference  (0) 2021.08.09
간단하게 상속과 조합을 이해해보기  (0) 2021.07.26
JPA N+1 문제  (0) 2021.07.22