복붙노트

[SPRING] @ExceptionHandler에서 @RequestBody를 얻는 방법 (Spring REST)

SPRING

@ExceptionHandler에서 @RequestBody를 얻는 방법 (Spring REST)

나는 spring-web-4.3.3을 포함하는 Spring Boot 1.4.1을 사용하고있다. 서비스 코드에 의해 던진 예외를 처리하기 위해 @ControllerAdvice로 주석 처리 된 클래스와 @ExceptionHandler로 주석 처리 된 메소드가 있습니다. 이러한 예외를 처리 할 때 PUT 및 POST 작업에 대한 요청의 일부인 @RequestBody를 기록하여 내 문제의 원인이되는 요청 본문을 볼 수 있으므로 내 경우에는 진단에 중요합니다.

Spring의 Docs에서 @ExceptionHandler 메소드의 메소드 서명은 HttpServletRequest를 포함하여 다양한 것들을 포함 할 수있다. 요청 본문은 일반적으로 getInputStream () 또는 getReader ()를 통해 여기에서 가져올 수 있지만 내 컨트롤러 메소드가 요청 본문을 "@RequestBody Foo fooBody"와 같이 광산에서 파싱 할 경우 HttpServletRequest의 입력 스트림이나 판독기가 이미 닫혀 있습니다. 내 예외 처리 메서드가 호출 될 때. 기본적으로 요청 본문은 Spring에서 이미 읽었으며 여기에서 설명한 문제와 유사합니다. 서블릿을 사용하여 요청 본문을 한 번만 읽을 수있는 것은 일반적인 문제입니다.

불행히도 @RequestBody는 예외 핸들러 메소드에서 사용할 수있는 옵션 중 하나가 아닙니다. 그렇다면 사용할 수 있습니다.

예외 처리기 메서드에 InputStream을 추가 할 수 있지만 HttpServletRequest의 InputStream과 동일하므로 동일한 문제가 발생합니다.

나 또한 (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes ()로 현재 요청을 얻으려고했다. getRequest ()는 현재 요청을 얻는 또 다른 트릭이지만, 끝내는 HttpServletRequest는 Spring이 예외 처리 메소드에 전달하는 것과 같다. 같은 문제가 있습니다.

필자는이 같은 몇 가지 솔루션에 대해 읽었으며 필터 체인에 사용자 지정 요청 래퍼를 삽입하여 요청의 내용을 읽고이를 한 번 이상 읽을 수 있도록 캐시합니다. 로깅을 구현하기 위해 전체 필터 / 요청 / 응답 체인 (및 잠재적으로 성능 또는 안정성 문제 도입)을 중단하고 싶지 않기 때문에이 솔루션을 좋아하지 않습니다. 업로드 된 문서 (예 : 나는 그렇다), 나는 그것을 메모리에 캐쉬하고 싶지 않다. 게다가 Spring은 아마 @RequestBody를 이미 어딘가에 캐시했을 것입니다.

덧붙여 말하자면, 많은 솔루션은 ContentCachingRequestWrapper Spring 클래스를 사용하는 것을 권장합니다.하지만 제 경험상 이것이 작동하지 않습니다. 문서화되지 않은 것 외에도 소스 코드를 살펴보면 요청 본문이 아니라 매개 변수 만 캐시하는 것처럼 보입니다. 이 클래스에서 요청 본문을 가져 오려고하면 항상 빈 문자열이됩니다.

그래서 내가 놓친 다른 옵션을 찾고 있습니다. 읽어 주셔서 감사합니다.

해결법

  1. ==============================

    1.요청 본문 오브젝트를 요청 범위의 Bean으로 참조 할 수 있습니다. 그런 다음 해당 요청 범위의 bean을 예외 핸들러에 삽입하여 요청 본문 (또는 참조하려는 다른 요청 컨텍스트 bean)을 검색하십시오.

    요청 본문 오브젝트를 요청 범위의 Bean으로 참조 할 수 있습니다. 그런 다음 해당 요청 범위의 bean을 예외 핸들러에 삽입하여 요청 본문 (또는 참조하려는 다른 요청 컨텍스트 bean)을 검색하십시오.

    // @Component
    // @Scope("request")
    @ManagedBean
    @RequestScope
    public class RequestContext {
        // fields, getters, and setters for request-scoped beans
    }
    
    @RestController
    @RequestMapping("/api/v1/persons")
    public class PersonController {
    
        @Inject
        private RequestContext requestContext;
    
        @Inject
        private PersonService personService;
    
        @PostMapping
        public Person savePerson(@RequestBody Person person) throws PersonServiceException {
             requestContext.setRequestBody(person);
             return personService.save(person);
        }
    
    }
    
    @ControllerAdvice
    public class ExceptionMapper {
    
        @Inject
        private RequestContext requestContext;
    
        @ExceptionHandler(PersonServiceException.class)
        protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) {
             Object requestBody = requestContext.getRequestBody();
             // ...
             return responseEntity;
        }
    }
    
  2. ==============================

    2.Accepted answer는 새로운 POJO를 만들어 전달하지만, http 요청을 다시 사용하여 추가 객체를 만들지 않고도 동일한 동작을 얻을 수 있습니다.

    Accepted answer는 새로운 POJO를 만들어 전달하지만, http 요청을 다시 사용하여 추가 객체를 만들지 않고도 동일한 동작을 얻을 수 있습니다.

    컨트롤러 매핑 예제 코드 :

    public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {
        webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
    

    그리고 나중에 ExceptionHandler 클래스 / 메소드에서 사용할 수 있습니다 :

    @ExceptionHandler(Exception.class)
    public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
    
        Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
    
  3. from https://stackoverflow.com/questions/43502332/how-to-get-the-requestbody-in-an-exceptionhandler-spring-rest by cc-by-sa and MIT license