@RestControllerAdvice
혹은 @ControllerAdvice
를 사용하여 로깅하는 방법도 존재한다.
Request
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.core.MethodParameter
import org.springframework.http.HttpInputMessage
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter
import java.lang.reflect.Type
@RestControllerAdvice
class RequestBodyLoggingAdvice : RequestBodyAdviceAdapter() {
private val logger = KotlinLogging.logger {}
override fun supports(
methodParameter: MethodParameter,
targetType: Type,
converterType: Class<out HttpMessageConverter<*>>
): Boolean {
return true
}
override fun afterBodyRead(
body: Any,
inputMessage: HttpInputMessage,
parameter: MethodParameter,
targetType: Type,
converterType: Class<out HttpMessageConverter<*>>
): Any {
logger.info { "[ADVICE Request] ${parameter.method.name} body : $body" }
return body
}
}
RequestBodyAdviceAdapter
는 abstract class 이며 RequestBodyAdvice
인터페이스를 구현하였다.
여기서 supports()
가 가장 처음에 실행되는 메서드이며 이를 통해 다음으로 실행될 메서드인 beforeBodyRead()
또는 handleEmptyBody()
그리고 afterBodyRead()
가
실행될지 결정한다.
beforeBodyRead()
는 body 를 처리하기 전에 사용되며 현재 찍으려는 로그에는 body 가 포함되어야 하므로 오버라이딩 하지 않았다.
handleEmptyBody()
는 비어있는 경우 처리할 로직이기에 역시 따로 오버라이딩 하지 않았다.
afterBodyRead()
는 body를 읽고 나서 파라미터로 이번 글에서 원하는 값을 받고 있어서 이 메서드를 오버라이딩 한 후 로그를 추가 해 주었다.
Response
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.core.MethodParameter
import org.springframework.http.HttpInputMessage
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter
import java.lang.reflect.Type
@RestControllerAdvice
class ResponseBodyLoggingAdvice : ResponseBodyAdvice<Any> {
private val logger = KotlinLogging.logger {}
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean {
return true
}
override fun beforeBodyWrite(
body: Any?,
returnType: MethodParameter,
selectedContentType: MediaType,
selectedConverterType: Class<out HttpMessageConverter<*>>,
request: ServerHttpRequest,
response: ServerHttpResponse
): Any? {
logger.info { "[ADVICE Response] body : $body" }
return body
}
}
Response 에는 ResponseBodyAdviceAdapter
가 존재하지 않아 바로 ResponseBodyAdvice
인터페이스를 구현 하여 사용하였다.
supports()
와 beforeBodyWrite()
만 존재하며 supports()
는 RequestBodyAdvice
에서 처럼 filter 와 같은 역할을 처리하며
beforeBodyWrite()
에서 원하는 로직을 처리하도록 하였다.
RestControllerAdvice (ControllerAdvice)
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
initMessageConverters();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
}
@ControllerAdvice
는 RequestMappingHandlerAdapter
클래스의 afterPropertiesSet()
가 실행된 이후 initControllerAdviceCache()
에서
ControllerAdviceBean.findAnnotatedBeans()
을 통하여 해당 어노테이션이 달려있는 모든 찾아서 미리 리스트에 등록 해둔 다음 타겟에 해당하는 메서드가 실행 될때 마다 처리하도록 한다.
'IT > Spring' 카테고리의 다른 글
스프링의 트랜잭션 (0) | 2023.10.12 |
---|---|
Spring AOP 분석 (0) | 2023.10.10 |
AOP 사용하여 로깅 (0) | 2023.09.25 |
Interceptor를 사용하여 Request/Reponse Logging (0) | 2023.09.20 |
Private Method Test 하기 (0) | 2023.09.14 |
댓글