상세 컨텐츠

본문 제목

RequestBodyAdvice 인터페이스 파해치기

Spring/JAVA

by Chan.94 2024. 12. 30. 09:49

본문

반응형

Intro

  • RequestBodyAdvice는 Spring MVC에서 요청 본문을 읽고 처리하기 전에 특정 작업을 수행하거나, 읽은 본문 데이터를 변경하고자 할 때 사용된다. 주로 JSON, XML 등 요청 본문에 포함된 데이터를 Handler(Controller)로 전달하기 전에 특정 요구사항에 맞게 가공하거나 검증하는 데 활용된다.
  • @RequestBody가 적용된 요청에서만 호출된다.
  • RequestBodyAdvice가 실행되고, 그 후 ArgumentResolver가 실행된다.
  • RequestBodyAdvice는 여러 개를 등록할 수 있다.
    • Spring은 @ControllerAdvice를 통해 등록된 모든 RequestBodyAdvice를 스캔하고 실행한다.
    • @Order를 활용해 실행 순서를 조정할 수 있다.
    • supports 메서드로 적용 범위를 제한할 수 있다.
    • 여러 ResponseBodyAdvice를 사용할 때는 데이터 일관성과 중복 작업에 주의해야 한다.

실행 흐름

  1. Client → DispatcherServlet (요청 수신)
  2. HandlerMapping
  3. RequestBodyAdvice의 supports(), beforeBodyRead() 실행
    • supports() 메서드를 호출하여 적용 여부를 결정한다.
    • beforeBodyRead() 메서드는 요청 본문을 읽기 전에 데이터 가공, 변환, 또는 로깅 등의 작업을 수행한다.
  4. HttpMessageConverter 실행 (요청 본문 변환)
    • 요청 본문을 자바 객체로 변환하는 단계
      • 요청의 Content-Type 헤더를 기반으로 적절한 HttpMessageConverter를 선택한다. 예를 들어, application/json이면 MappingJackson2HttpMessageConverter가 사용한다.
      • HTTP 요청 본문이 Java 객체(@RequestBody와 매핑된 객체)로 변환한다.
  5. RequestBodyAdvice의 afterBodyRead() 실행
    • 요청 본문이 자바 객체로 변환된 후, RequestBodyAdvice의 afterBodyRead() 메서드가 실행된다.
      • 변환된 객체를 수정하거나 후속 처리를 수행할 수 있다.
      • 주로 데이터 검증, 로깅, 또는 추가적인 데이터 가공에 사용된다.
  6. ArgumentResolver실행
    • ArgumentResolver는 요청 데이터를 컨트롤러 메서드의 매개변수에 맞게 변환하는 역할을 수행한다.
    • Handler(Controller) 매개변수로 전달한다.
  7. Handler(Controller) 로직 실행
  8. ReturnValueHandler실행
  9. ResponseBodyAdvice의 supports(), beforeBodyWrite() 실행
  10. HttpMessageConverter 실행 (응답 본문 변환)
  11. DispatcherServlet → Client (응답 반환)

RequestBodyAdvice

RequestBodyAdvice 인터페이스

public interface RequestBodyAdvice {

	boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType);

	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

	@Nullable
	Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

}
  • supports
    • 어떤 요청에 대해 이 Advice를 적용할지 여부를 결정.
    • 반환값이 true인 경우에만 나머지 메서드가 호출
  • beforeBodyRead
    • 요청 본문을 읽기 전에 호출된다.
    • 수정이 필요하다면 수정 후 HttpInputMessage를 반환한다.
  • afterBodyRead
    • 요청 본문이 읽힌 후 호출된다.
    • 매핑된 객체를 수정하거나 추가 작업을 수행할 수 있다. 파라미터의 Object는 @RequestBody에 사용되는 객체를 의미한다.
  • handleEmptyBody
    • 요청 본문이 비어 있을 경우 호출된다.
    • 빈 본문을 처리하거나 기본 값을 설정할 수 있다.

주요 역할

  • 요청 데이터 전처리
    요청 데이터를 컨트롤러 메서드에 매핑하기 전에 데이터를 수정하거나 변환해야 할 때 사용한다.
  • 데이터 검증
    요청 데이터가 특정 조건을 만족하는지 확인하거나 추가적인 검증을 수행할 때 사용한다.
  • 로깅 및 디버깅
    요청 데이터를 로깅하거나 분석하기 위해 가로채는 데 사용한다.

주의 사항

HTTP 요청의 본문은 스트림 형식으로 전달되며, 한 번 읽으면 스트림은 소모된다는 것을 기억하자.

따라서, 요청 본문을 RequestBodyAdvice에서 읽고 수정하려면 스트림을 다시 생성하거나 복사하여 사용할 수 있도록 해야한다.

RequestBodyAdviceAdapter 추상 클래스

public abstract class RequestBodyAdviceAdapter implements RequestBodyAdvice {

	/**
	 * The default implementation returns the InputMessage that was passed in.
	 */
	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

		return inputMessage;
	}

	/**
	 * The default implementation returns the body that was passed in.
	 */
	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

		return body;
	}

	/**
	 * The default implementation returns the body that was passed in.
	 */
	@Override
	@Nullable
	public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

		return body;
	}

}

 

RequestBodyAdviceAdapter를 상속받아 필요한 부분만 Override 한다.


Example

로깅

@Slf4j
@RestControllerAdvice
public class CustomRequestBodyAdvice extends RequestBodyAdviceAdapter{
    
    private final HttpServletRequest httpServletRequest;

    // HttpServletRequest는 생성자 주입 또는 스프링 빈 주입으로 사용 가능
    public CustomRequestBodyAdvice(HttpServletRequest httpServletRequest) {
        this.httpServletRequest = httpServletRequest;
    }
    
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        String clientIp = getClientIp(httpServletRequest);
        String remoteIp = httpServletRequest.getRemoteAddr();
        String requestUri = httpServletRequest.getRequestURI();
        log.info("[Remote IP] : {}, [Client IP] : {}, [Request URI] : {}", remoteIp, clientIp, requestUri);
        log.info("RequestBody Input Data : {}", body);
        return body;
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        
        //IPv6 : 0:0:0:0:0:0:0:1
        //IPv4 : 127.0.0.1
        if (ip.contains("0.0") || ip.contains("0:0")) { 
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        return ip;
    }
}

 


ResponseBodyAdvice에 대한 내용은 아래 글을 참고하기 바란다.

 

ResponseBodyAdvice 인터페이스 파해치기

 

ResponseBodyAdvice 인터페이스 파해치기

IntroResponseBodyAdvice는 Spring MVC에서 Handler(Controller) 처리 후, 클라이언트로 전달되기 전에 공통적으로 처리하거나 수정하고자 할 때 사용된다.ResponseBodyAdvice는 @ResponseBody가 설정된 controller가 반환된

fvor001.tistory.com

 

 

 

 

반응형

관련글 더보기

댓글 영역

>