중복된 부가기능을 커스텀 어노테이션으로 (AOP)

2024. 6. 4. 17:21TIL

✔오늘 배운 중요한 🔑 point

  • AOP 라는건 결국 중복된 부가기능의 작성을 없애고 싶어서 커스텀 어노테이션을 만들어서 적용시키는것

🎯 오늘 배운 내용

 

AOP : 핵심 로직 ,부가기능 분리해서 부가기능을 모듈화를 하여 재사용성을 매우 좋게 만드는 패러다임

 

Cross-Cutting

옆에서 객체들을 바라보았을때 중복되는 부가기능들을 모듈화 하는것이 바로 AOP의 핵심이다

 

AOP 주요 개념!

  • Aspect: 부가기능 모듈화한 단위
  • PointCut: Aspect가 적용될 프로그램상 실제 위치
  • JoinPoint: PointCutd의 후보군, Aspect가 적용될 수 있는 위치
  • Advice: 실질적인 로직(함수)
  • Weaving: Aspect를 실제 코드에 적용하는 과정

 

AOP를 적용할 수 있는 대표적인 2개의 프레임워크

Spring AOP:중간에 프록시를 통해서 객체에 접근하여 AOP 적용

AspectJ: JVM에서 클래스를 로딩할때 변경된 바이트 코드를 사용해 AOP 적용

 

SpringAop는 매우 사용하기 쉽지만 성능이 낮고

AspectJ는 성능이 뛰어나지만 사용하기 매우 어렵다

 

 

aop 사용방법!!(Spring AOP)

STEP 1  설정하기

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-aop")
}

build.gradle.kts에 해당  의존성을 추가한다

 

 

SpringAop이지만 AspectJ를 차용했기 때문에 AspectJAutoProxy라는 이름이다

@EnableAspectJAutoProxy 어노테이션 작성

 

이제 proxy 기반으로 AOP를 사용할 수 있게 되었다

 

step 2 작성하기

 

커스텀 어노테이션 정의하기

package org.example.spartatodolist.infra.Aop

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME) 
annotation class StopWatch()

infra 하위에 Aop라는 패키지를 생성한 뒤 annotation class를 만들어준다!

@Target은 어디에 적용이 될건지! -> 즉 AnnotationTarget.FUNCTION으로 설정해주면 함수 위에 붙이는 식으로 적용된다!

@StopWatch
override fun getCardById(cardId: Long): CardResponse {}

요런식으로 함수 위에 작성할수 있게 된다

 

@Retention은 이 어노테이션이 언제까지 유지될지를 설정한다. Kotlin에서는 AnnotationRetention.RUNTIME이 default값이다

 

커스텀 어노테이션 기능 작성하기

@Aspect 
@Component 
class StopWatchAspect {
    
    private val logger= LoggerFactory.getLogger("Execution Time Logger") 

    @Around("@annotation(org.example.spartatodolist.infra.Aop.StopWatch)")
    fun run(joinPoint: ProceedingJoinPoint) {
        val stopWatch = StopWatch()

        stopWatch.start()
        joinPoint.proceed() 
        stopWatch.stop()

        val methodName=joinPoint.signature.name 
        val methodArguments = joinPoint.args.joinToString(","){arg->arg?.toString() ?:"null"} 

        val timeElapsedMs= stopWatch.totalTimeMillis
        logger.info("Method Name: $methodName | Arguments: ${methodArguments} | Execution Time: ${timeElapsedMs}ms")

    }
}

@Aspect 어노테이션을 작성해서 해당 클래스가 Aspect임을 명시

@Component를 통해서 Bean으로 등록

 

private val logger= LoggerFactory.getLogger("Execution Time Logger")

로깅(시스템의 동작을 기록)을 위한 로거 설정

 

@Around("@annotation(org.example.spartatodolist.infra.Aop.StopWatch)")
fun run(joinPoint: ProceedingJoinPoint){}

@Around는 Advise의 적용시점 중 하나인데 실행 전후로 동작을 한다

"@annotation(org.example.spartatodolist.infra.Aop.StopWatch)" 는 해당 위치에 있는 annotation을 달았을때 run 함수를 작동시켜라! 라는 의미이다. 

@StopWatch 라는 어노테이션을 작성했을때 run 함수가 실행된다고 보면 된다

 

 

🤔 어떻게 활용할까?

해당 서비스의 함수가 실행되는 시간 뿐만 아니라 다양한 부가기능에 대해서 AOP를 활용하여  커스텀 어노테이션을 작성해서 코드의 재사용을 용이하게 할 수 있다.

하지만 Spring AOP를 사용했을 경우에 내부함수로 호출할경우에 작동이 되지 않는 등 여러 문제가 있는데 이러한 문제를 Trailing Lambda 문법을 사용해서 해결할 수도 있다.

https://tech.kakaopay.com/post/overcome-spring-aop-with-kotlin/#kotlin-functional-programing---trailing-lambdas

 

Kotlin으로 Spring AOP 극복하기! | 카카오페이 기술 블로그

Kotlin의 문법적 기능을 사용해서 Spring AOP 아쉬운 점을 극복한 경험을 공유합니다.

tech.kakaopay.com

 

 

📓 오늘의 한줄

For sale: baby shoes, never worn

- a six-word story -

'TIL' 카테고리의 다른 글

인가  (0) 2024.06.06
JWT토큰을 이용한 로그인 기능  (0) 2024.06.05
팀 프로젝트 회고  (0) 2024.06.03
적합한 유효성 검사?  (0) 2024.06.02
(알고리즘) 햄버거 만들기  (0) 2024.06.01