[WebFlux] WebFlux AOP 사용하기
들어가기 전
Spring WebFlux 는 리액티브 프로그래밍 모델을 기반으로 하며, 일반적으로 AOP (Aspect-Oriented Programming) 은 Spring 의 핵심 기능 중의 하나이다. 해당 기능은 어플리케이션에서 공통적으로 사용되는 기능을 분리하여 관점 지향 프로그래밍을 통해 공통 기능을 처리 할 수 있게 해준다. 이러한 공통 기능은 일반 Spring 에서 Filter, Interceptor, AOP 를 통해서 구현 할 수 있다.
Spring WebFlux 는 비동기/논블럭킹으로 동작 하기 때문에, AOP를 사용하여 비동기 요청 및 응답 처리 과정에서 로깅, 및 기타 공통 기능을 분리하여 재사용성과 유지 보수성을 높일 수 있다.
AOP 를 사용하기 위해서는 WebFlux 에서도 @Aspect 어노테이션을 사용하여 AOP 클래스를 정의하고, 적용하고자 하는 메서드에 Advice 를 정의하여 사용하면 된다.
해당 글에서는 요청/응답 처리 구간에서 응답 시간을 측정하여 응답 값에 추가하여 응답 하는 예제를 통해서 AOP를 어떻게 사용 하는지에 대해서 이야기 하고자 한다.
코드 분석
@Around("execution(* com..*Controller.*(..))")
fun process(pjp: ProceedingJoinPoint): Any {
val signatureArgs = pjp.args
// 1. 시간 측정 시작
val stopWatch = StopWatch()
stopWatch.start()
val strRequest = StringBuilder("[RestRequest]")
if (signatureArgs.isNotEmpty()) {
val request = signatureArgs[0] as Any
strRequest.append(" : ${gson.toJson(request)}")
}
// 2. 요청 Body 로깅
log.info(strRequest.toString())
return (pjp.proceed() as Mono<ResponseEntity<Any>>)
.flatMap {
stopWatch.stop()
// 3. 응답 시간 측정
val response = ResponseEntity<Any>(RestResponse(ApiResponseCode.OK.code, ApiResponseCode.OK.message, stopWatch.totalTimeMillis.toString(), "","", it.body), it.statusCode)
log.info("[RestResult] : ${gson.toJson(response)}")
// 4. 응답
Mono.just(
response
)
}
}
위 소스 코드는 비동기 요청/응답 구간에서 공통 기능을 AOP 로 구현한 예제이다. 그 기능으로는 Presentation Layer 인 Controller 로 호출 전 시간을 측정 한 뒤 , 해당 Controller 메서드를 나갈 때 까지의 경과 시간을 응답 값으로 같이 추가 하는 예제 이다.
시간 측정
시간 측정은 아래와 같이 StopWatch 클래스를 사용하여, 시간 측정을 시작 한다.
// 1. 시간 측정 시작
val stopWatch = StopWatch()
stopWatch.start()
요청 값 로깅
요청 값에 대한 로깅은, 아래와 같이 SignatureArgs 객체의 값이 없을 경우, 요청 된 json 값을 String 으로 변환하여 출력 되도록 한다.
val strRequest = StringBuilder("[RestRequest]")
if (signatureArgs.isNotEmpty()) {
val request = signatureArgs[0] as Any
strRequest.append(" : ${gson.toJson(request)}")
}
// 2. 요청 Body 로깅
log.info(strRequest.toString())
응답
함수형 프로그래밍 스타일로 proceed 를 호출 한 결과 값을 핸들링 하여, 시간 객체를 종료 시킨 후 RestResponse 객체에 담은 후 응답 객체를 만든 후 Mono 객체로 변환하여 Return 한다.
return (pjp.proceed() as Mono<ResponseEntity<Any>>)
.flatMap {
stopWatch.stop()
// 3. 응답 시간 측정
val response = ResponseEntity<Any>(RestResponse(ApiResponseCode.OK.code, ApiResponseCode.OK.message, stopWatch.totalTimeMillis.toString(), "","", it.body), it.statusCode)
log.info("[RestResult] : ${gson.toJson(response)}")
// 4. 응답
Mono.just(
response
)
}
RestResponse Response DTO 샘플
아래는 Response 로 사용되는 DTO 클래스의 구조이다. 응답 지연시간 정보는 아래 elapsedTime 필드에 적용되어 응답 하게 된다.
data class RestResponse<T> (
val returnCode:String,
val returnMsg:String,
val elapsedTime: String,
val errorCode: String,
val errorMsg: String,
val returnData: T
)