AOP -활용편-

곽현민·2026년 1월 27일

본문을 로깅하는 AOP


발단 : 요청 본문, 응답 본문에 대해서 로그를 남기고 싶었다. 하지만 내 생각만큼 그렇게 간단한 문제가 아니었다.

우선 AOP에서 그냥 요청 본문과 응답 본문을 읽을 경우, 후에 실제로 컨트롤러나 클라이언트가 데이터를 읽지 못하는 문제가 발생한다는 것이다.

HTTP 스트림은 한 번 읽으면 다시 읽을 수 없음!!


전개 : 이를 해결하기 위해 우리는 요청과 응답을 캐싱이 가능한 래퍼 객체로 감싸줘야 한다.

캐싱(Caching) : 복사본을 임시 저장소 (캐시)에 저장하여 원본 데이터에 접근하지 않고 빠르게 응답하는 기술

필터에서 In Filter

public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
            
        // 캐싱 가능한 래퍼로 감싸기
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(requestWrapper, responseWrapper);

        // 중요: 응답 본문을 다시 복사해줘야 클라이언트가 응답을 받을 수 있음, 응답 본문을 복사하여 원래 객체로 응답하여 정상 작동
        responseWrapper.copyBodyToResponse();
    }
}

위기 : 하지만 여기서 주의사항이 두 가지 있다.


!!주의!! ContentCachingResponseWrapper의 메서드 copyBodyToResponse를 통해 응답 본문을 다시 복사하여 원래 객체로 응답해야 정상적으로 작동한다.

이 메서드를 호출하지 않으면 빈 응답이 반환된다..!


!!주의!! 요청 본문과 응답 본문 로그는 반드시 메서드 실행 이후 불러와야 한다.

요청에 관하여 ContentCachingRequestWrapper는 본문을 즉시 복사하는 게 아니고, 실제 스트림을 읽을 때, 바이트 배열로 복사한다.
결국 메서드 실행 전에 로그를 남길 경우 아무것도 남겨지지 않는다.

응답 본문은 당연히 메서드 실행 이후 응답이 생기니 실행 후에만 확인이 가능하다.


결말 : 안전하게 요청과 응답에 대한 본문을 로그로 남길 수 있게 filter에서 설정했으니 본격적인 AOP를 생성해보자

@Aspect
@Component
@Slf4j
public class AdminCheckAop {

    @Around("execution(* org.example.main.user.service..*(..))")
    public Object executionAdminCheck(ProceedingJoinPoint joinPoint) throws Throwable {
    
        // 메서드 실행
        Object result = joinPoint.proceed();

        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();

        if (attrs instanceof ServletRequestAttributes sra) {
            HttpServletRequest request = sra.getRequest();
            HttpServletResponse response = sra.getResponse();
        }

        // 요청 본문
        if (request instanceof ContentCachingRequestWrapper requestWrapper) {
            byte[] requestBody = requestWrapper.getContentAsByteArray();
            log.info("Request Body: {}", new String(requestBody, StandardCharsets.UTF_8));
        }

        // 응답 본문
        if (response instanceof ContentCachingResponseWrapper responseWrapper) {
            byte[] responseBody = responseWrapper.getContentAsByteArray();
            log.info("Response Body: {}", new String(responseBody, StandardCharsets.UTF_8));
        }

        return result;
    }
}

요청과 응답에 대한 본문을 로그로 한 번 남겨보았다.

이번 경험을 바탕으로 AOP에 대해서 많이 알아가는 유익한 시간이었다고 생각한다.
처음에는 요청과 응답 본문을 그냥 간단히 로그로 남기면 될 줄 알았는데, 그게 안된다고 하니까 막막했었다. 근데 점차 배울수록 재밌다고 생각이 든 경험이었다.

AOP이외에도 다양한 지식이 연계되어 배울 수 있는 점들이 많아서 그랬던 것 같다.
앞으로의 과정에 있어 도움이 많이 될 경험이었다고 생각한다.

0개의 댓글