Post

Spring AOP

AOP(Aspect Oriented Programming, 관심 지향 프로그래밍)의 개념과 예제에 대한 가이드

Spring AOP

1. 개요


AOP(Aspect Oriented Programming, 관심 지향 프로그래밍) 는 관심사를 분리하여 모듈성을 높이는 것을 목표로 하는 프로그래밍 패러다임이다. 기존 코드를 수정하지 않고 코드에 작업을 추가하여 동작시킨다. Spring의 AOP 프레임워크를 사용하여 구현해보자.


2. AOP 개념 및 용어


AOP와 관련된 개념과 용어를 살펴보자.

spring-aop1


2.1. Business Object


Business Object는 일반적인 비즈니스 로직이 수행되는 일반 클래스이다.


2.2. Aspect


Aspect는 여러 클래스에 적용되는 관심사의 모듈화이다.

통합 로깅은 이러한 cross-cutting concern(교차적 관심)의 예시이다.

1
2
3
4
5
6
7
class AdderAfterReturnAspect {
  private val logger = LoggerFactory.getLogger(this::class.java)

  fun afterReturn(returnValue: Any) {
    logger.info("value return was {}", returnValue)
  }
}


2.3. Joinpoint


Joinpoint 는 메소드 실행이나 예외 처리 등 실행 지점이다.

Spring AOP에서 JoinPoint 는 메소드 실행을 의미한다.


2.4. Pointcut


Pointcut 은 특정 JoinPoint 에서 Aspect 에 의해 적용되는 Advice 를 일치시키는 데 도움이 되는 조건자이다. Spring AOP는 보통 AdvicePointcut 표현식과 연관시키고 Pointcut 과 일치하는 모든 Joinpoint 에서 실행된다.


2.5. Advice


Advice 는 특정 Joinpoint 에서 Aspect가 취하는 조치이다.

Advice 에는 aroundbefore , after 유형이 존재한다.


3. AOP 실습


3.1. 의존성 추가


Spring AOP 관련 의존을 추가한다.

1
2
// aop
implementation("org.springframework.boot:spring-boot-starter-aop")


3.2. 사용자 정의 어노테이션 만들기


메소드의 실행 시간을 측정하는 어노테이션을 만들어보자.

1
2
3
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogExecutionTime
  • @Target : 어노테이션이 적용될 위치를 정의
  • @Retention : 어느 시점까지 어노테이션을 유지시킬지 정의


3.3. Aspect 만들기


메소드 실행 시간을 측정할 Aspect를 만들어보자.

1
2
3
4
5
@Aspect
@Component
class ExampleAspect {

}
  • @Aspect 어노테이션을 사용하여 Aspect를 정의함
  • Spring에서 클래스를 감지하려면 bean으로 등록되어야 하므로 @Component 어노테이션 사용함


3.4. Pointcut과 Advice 만들기


Aspect에 Pointcut과 Advice를 만들어보자.

1
2
3
4
@Around("@annotation(LogExecutionTime)")
fun logExecutionTime(joinPoint: ProceedingJoinPoint): Any? {
    return joinPoint.proceed()
}
  • @Around 어노테이션을 달아서 메소드 실행 전과 후에 코드 추가를 정의
  • @Around 의 포인트컷 인수에는 @LogExecutionTime 어노테이션이 달린 메서드에 적용하라는 조건을 정의
  • logExecutionTime() 메소드가 Advice를 의미
  • @LogExecutionTime 어노테이션이 달린 메소드가 호출되면, Advice인 logExecutionTime() 메소드가 호출되며 종료된 이후에도 해당 메소드가 호출된다.


3.5. 실행 시간 로깅

Advice에 메소드의 실행 시간을 로깅하는 동작을 추가해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Component
class ExampleAspect {

    @Around("@annotation(LogExecutionTime)")
    fun logExecutionTime(joinPoint: ProceedingJoinPoint): Any? {
        val start = System.currentTimeMillis()
        val proceed = joinPoint.proceed()
        val executionTime = System.currentTimeMillis() - start
        println("${joinPoint.signature} executed in $executionTime ms")
        return proceed
    }
}
  • joinpoint 인스턴스를 사용하기 위해 파라미터로 joinpoint를 정의
  • 메서드의 파라미터와 다른 정보에도 접근할 수 있다

다른 클래스에서 메서드를 구현하고 @LogExecutionTime을 달아서 결과를 확인해보자.

1
2
3
4
5
6
7
8
@Component
class BusinessObject {

    @LogExecutionTime
    fun serve() = runBlocking {
        sleep(1000)
    }
}

실행할 경우 다음과 같은 결과를 볼 수 있다.

1
void io.tutorial.notificationservice.aop.BusinessObject.serve() executed in 1006 ms


4. 예제 코드


예제 코드는 다음 링크에서 확인할 수 있다.

GitHub: notification-service


Reference


Bealdung: Introduction to Spring AOP

Bealdung: Implementing a Custom Spring AOP Annotation

This post is licensed under CC BY 4.0 by the author.