[Spring] Spring AOP 개념 및 구현

AOP라는 개념을 접하면 먼가 추상적이다. 이 개념은 OOP의 SOLID 원칙을 지키면서 , 원하는 기능을 모두 구현하려면 한계점을 봉착할 때가 많다. 간단한 예를 들면 사용자에 대한 인증이다. 어떠한 서비스를 사용하기 위하여 사용자 권한이 있는지 판단하기 위해서는 권한 체크부분을 서비스에 포함 시켜야 된다. 이렇게 설계하다보면 여기저기 코드가 난립해 있고 관리도 않된다. 또한 OOP 설계 원칙도 지키지 못하게 된다.

위의 문제를 해결하기 위해 Spring에서는 JAVA의 Aspectj를 AOP(Aspect Oriented Programming) 방식으로 도입 했다. Spring에서의 AOP는 프록시 기반의 AspectJ이다.

Spring AOP의 몇개 개념

Aspect

Advice, PointCut의 조합으로써 로직을 구현한 구현체이다. 일반 class에 Aspect annotation을 추가하면 Aspect class로 사용 가능, 밑의 코드 참조하면 쉽게 이해 가능 하다.

PointCut

advice가 실행할 곳을 지정해 준다. 하나의 aspect에는 여러개의 PointCut이 존재 할 수 있다.

Advice

실행할 코드를 정의하는 곳이다. Advice의 타입에는 아래와 같은 5가지가 있다.

@Before

함수 실행 전 실행된다.

@After

함수 실행 후 실행된다.

@AfterReturning

함수가 정상적으로 실행된 후 실행된다.

@AfterThrowing

함수에서 Exception이 발생했을 경우만 실행 된다.

@Around

모든 경우에 대하에 실행된다. 즉 함수가 호출 되면 함수자체를 파라미터로 받아서 실행하고 원하는 기능을 앞뒤에 붙일 수 있다. 즉 @Before @After를 모두 만족할 수 있는 기능을 구현 할 수 있다. 이 글에서는 @Around로 설명을 했다

사용법

build.gradle.kts 설정

Spring AOP를 사용하려면 아래 처럼 종속성을 추가해야 한다.

implementation("org.springframework.boot:spring-boot-starter-aop:2.5.6")

@Aspect 정의

스프링 빈으로 정의된 class에 @Aspect 을 추가하여 Aspect 동작하게 한다.

@Aspect
@Component
class MetricsAOP {
}

@PointCut

PointCut 함수를 정의하여 진입점을 명시한다.

    @Pointcut(value="execution(public * com.techfox.order.Controller.*.*(..))")
    fun withMetricsAnnotation() {}

    @Pointcut(value="execution(public * com.techfox.order.service.*.*(..))")
    fun withMetricsService() {}

진입 시점을 이 방식으로 정의 할 수도 있고 annotation 방식으로 정의해도 된다. annotation 방식으로 정의하면 호출전에 해당 annotation을 달아야 한다. 이글에서의 방식을 사용하면 정의한 패키지 밑의 모든 함수에 대하여 적용이 된다.

Advice

5가지 방법 중 이 글에서는 Around 방식으로 구현 했다.

@Around(value = "withMetricsAnnotation() || withMetricsService()")
    @Throws(Throwable::class)
    fun metrics(pjp: ProceedingJoinPoint) {
        //sample AOP
        val signatureStr =  pjp.signature.toString();
        val start = System.currentTimeMillis()
        pjp.proceed()
        val executionTime = System.currentTimeMillis() - start
        println("${signatureStr} are processed excute time is ${executionTime}")
    }

전체class 코드를 보면 아래와 같다.

package com.techfox.order.common

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import org.springframework.stereotype.Component
import kotlin.jvm.Throws

@Aspect
@Component
class MetricsAOP {
    @Pointcut(value="execution(public * com.techfox.order.Controller.*.*(..))")
    fun withMetricsAnnotation() {}

    @Pointcut(value="execution(public * com.techfox.order.service.*.*(..))")
    fun withMetricsService() {}

    @Around(value = "withMetricsAnnotation() || withMetricsService()")
    @Throws(Throwable::class)
    fun metrics(pjp: ProceedingJoinPoint) {
        //sample AOP
        val signatureStr =  pjp.signature.toString();
        val start = System.currentTimeMillis()
        pjp.proceed()
        val executionTime = System.currentTimeMillis() - start
        println("${signatureStr} are processed excute time is ${executionTime}")
    }
}

이 방식으로 정의하면 원래 구현했던 코드를 건드리지 않고 PointCut만 잘 정의하면 Aspect를 쉽게 구현 할 수 있다.

예제 코드에서는 Terminal에 프린트하는 방식을 사용했지만 다음 글에서는 Log을 체계화하여 관리하는 방법에 대하여 다룰 예정이다.