【问题标题】:Catching & Handling Jackson Exceptions with a custom message使用自定义消息捕获和处理 Jackson 异常
【发布时间】:2019-01-02 07:14:11
【问题描述】:

我希望能捕捉到我正在开发的 spring-boot API 中发生的一些 jackson 异常。例如,我有以下请求类,我想捕获当 JSON 请求对象中的“questionnaireResponse”键为空或空白时发生的错误,即请求对象中的" "

@Validated
@JsonRootName("questionnaireResponse")
public class QuestionnaireResponse {

    @JsonProperty("identifier")
    @Valid
    private Identifier identifier = null;

    @JsonProperty("basedOn")
    @Valid
    private List<Identifier_WRAPPED> basedOn = null;

    @JsonProperty("parent")
    @Valid
    private List<Identifier_WRAPPED> parent = null;

    @JsonProperty("questionnaire")
    @NotNull(message = "40000")
    @Valid
    private Identifier_WRAPPED questionnaire = null;

    @JsonProperty("status")
    @NotNull(message = "40000")
    @NotEmptyString(message = "40005")
    private String status = null;

    @JsonProperty("subject")
    @Valid
    private Identifier_WRAPPED subject = null;

    @JsonProperty("context")
    @Valid
    private Identifier_WRAPPED context = null;

    @JsonProperty("authored")
    @NotNull(message = "40000")
    @NotEmptyString(message = "40005")
    @Pattern(regexp = "\\d{4}-(?:0[1-9]|[1-2]\\d|3[0-1])-(?:0[1-9]|1[0-2])T(?:[0-1]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ", message = "40001")
    private String authored;

    @JsonProperty("author")
    @NotNull(message = "40000")
    @Valid
    private QuestionnaireResponseAuthor author = null;

    @JsonProperty("source")
    @NotNull(message = "40000")
    @Valid
    private Identifier_WRAPPED source = null; //    Reference(Patient | Practitioner | RelatedPerson) resources not implemented

    @JsonProperty("item")
    @NotNull(message = "40000")
    @Valid
    private List<QuestionnaireResponseItem> item = null;

    public Identifier getIdentifier() {
        return identifier;
    }

    public void setIdentifier(Identifier identifier) {
        this.identifier = identifier;
    }

    public List<Identifier_WRAPPED> getBasedOn() {
        return basedOn;
    }

    public void setBasedOn(List<Identifier_WRAPPED> basedOn) {
        this.basedOn = basedOn;
    }

    public List<Identifier_WRAPPED> getParent() {
        return parent;
    }

    public void setParent(List<Identifier_WRAPPED> parent) {
        this.parent = parent;
    }

    public Identifier_WRAPPED getQuestionnaire() {
        return questionnaire;
    }

    public void setQuestionnaire(Identifier_WRAPPED questionnaire) {
        this.questionnaire = questionnaire;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Identifier_WRAPPED getSubject() {
        return subject;
    }

    public void setSubject(Identifier_WRAPPED subject) {
        this.subject = subject;
    }

    public Identifier_WRAPPED getContext() {
        return context;
    }

    public void setContext(Identifier_WRAPPED context) {
        this.context = context;
    }

    public String getAuthored() {
        return authored;
    }

    public void setAuthored(String authored) {
        this.authored = authored;
    }

    public QuestionnaireResponseAuthor getAuthor() {
        return author;
    }

    public void setAuthor(QuestionnaireResponseAuthor author) {
        this.author = author;
    }

    public Identifier_WRAPPED getSource() {
        return source;
    }

    public void setSource(Identifier_WRAPPED source) {
        this.source = source;
    }

    public List<QuestionnaireResponseItem> getItem() {
        return item;
    }

    public void setItem(List<QuestionnaireResponseItem> item) {
        this.item = item;
    }
}

导致此 Jackson 错误:

{
    "Map": {
        "timestamp": "2018-07-25T12:45:32.285Z",
        "status": 400,
        "error": "Bad Request",
        "message": "JSON parse error: Root name '' does not match expected ('questionnaireResponse') for type [simple type, class com.optum.genomix.model.gel.QuestionnaireResponse]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name '' does not match expected ('questionnaireResponse') for type [simple type, class com.optum.genomix.model.gel.QuestionnaireResponse]\n at [Source: (PushbackInputStream); line: 2, column: 3]",
    "path": "/api/optumhealth/genomics/v1.0/questionnaireResponse/create"
    }
}

有没有办法捕捉和处理这些异常(在示例中 JsonRootName 为空/无效),可能类似于扩展 ResponseEntityExceptionHandler 的 @ControllerAdvice 类?

【问题讨论】:

标签: java spring-boot exception-handling jackson jackson-databind


【解决方案1】:

尝试以下方式:

@ControllerAdvice
public class ExceptionConfiguration extends ResponseEntityExceptionHandler {

    @ExceptionHandler(JsonMappingException.class) // Or whatever exception type you want to handle
    public ResponseEntity<SomeErrorResponsePojo> handleConverterErrors(JsonMappingException exception) { // Or whatever exception type you want to handle
        return ResponseEntity.status(...).body(...your response pojo...).build();
    }

}

这允许您处理任何类型的异常并做出相应的响应。如果响应状态始终相同,只需在方法上粘贴 @ResponseStatus(HttpStatus.some_status) 并调用 ResponseEntity.body(...)

【讨论】:

  • 感谢您的回复!我已经尝试过使用 Exception import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;但是,在调用异常时,处理程序方法永远不会被捕获或执行..
  • @Broncos423 尝试更改为仅映射“RuntimeException.class”并在处理程序中进行调试,我发现很多异常都包含在 Spring 异常中
  • 是否有与 spring 无关的方法来提取自定义异常?
【解决方案2】:

您可以执行以下操作:

@ExceptionHandler(HttpMessageNotReadableException.class)
public CustomResponse handleJsonException(HttpServletResponse response, HttpMessageNotReadableException ex) {
        return customGenericResponse(ex);
    }

public CustomResponse customGenericResponse(HttpMessageNotReadableException ex) {
    //here build your custom response
    CustomResponse customResponse = new CustomResponse();
    GenericError error = new GenericError();
    error.setMessage(ex.getMessage()); 
    error.setCode(500);
    customResponse.setError(error);
    return customResponse;
}

CustomResponse 将是:

public class CustomResponse {
    Object data;
    GenericError error;
}

public class GenericError {
    private Integer code;
    private String message;
}

customGenericResponse 中,您可以检查 instanceOf 导致 ex 的原因并相应地返回您的自定义错误消息。

【讨论】:

    【解决方案3】:

    发现这个问题有一个类似的问题,只有我的是一个不同的 JSON 解析错误:

    JSON parse error: Unrecognized character escape 'w' (code 119); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unrecognized character escape 'w' (code 119)\n at [Source: (PushbackInputStream); line: 1, column: 10] 
    

    来自这样的 REST JSON 请求

    {"query":"\\w"}
    

    如果您可以修改 Rest Controller,则可以使用 HttpMessageNotReadableException 捕获 JSON 解析错误(在 Spring Boot 中使用 @RestController 注释为我工作)。即使我无法通过@ExceptionHandler(Exception.class) 发现错误

    您可以使用序列化对象(自然转换为 JSON)以自定义 JSON 响应。您还可以指定首先需要导致问题的请求和异常。这样您就可以获取详细信息,或者修改错误消息。

    @ResponseBody
    @ExceptionHandler(HttpMessageNotReadableException.class)
    private SerializableResponseObject badJsonRequestHandler(HttpServletRequest req, Exception ex) {
    
        SerializableResponseObject response = new SerializableResponseObject(404,
                    "Bad Request",
                    "Invalid request parameters, could not create query",
                    req.getRequestURL().toString())
    
        Logger logger = LoggerFactory.getLogger(UserController.class);
        logger.error("Exception: {}\t{}\t", response);
    
        return response;
    }
    

    代码会返回类似

    {
      "timestamp": "Thu Oct 17 10:19:48 PDT 2019",
      "status": 404,
      "error": "Bad Request",
      "message": "Invalid request parameters, could not create query",
      "path": "http://localhost:8080/user/query"
    }
    

    并且会记录类似的东西

    Exception: [Thu Oct 17 10:19:48 PDT 2019][404][http://localhost:8080/user/query][Bad Request]: Invalid request parameters, could not create query
    

    SerializableResponseObject 的代码

    public class SerializableResponseObject implements Serializable {
        public String timestamp;
        public Integer status;
        public String error;
        public String message;
        public String path;
    
        public SerializableResponseObject(Integer status, String error, String message, String path) {
            this.timestamp = (new Date()).toString();
            this.status = status;
            this.error = error;
            this.message = message;
            this.path = path;
        }
    
        public String getTimestamp() {
            return timestamp;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public String getError() {
            return error;
        }
    
        public String getMessage() {
            return message;
        }
    
        public String getPath() {
            return path;
        }
    
        public void setTimestamp(String timestamp) {
            this.timestamp = timestamp;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        public void setError(String error) {
            this.error = error;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        public String toString() {
            return "[" + this.timestamp + "][" + this.status + "][" + this.path + "][" + this.error + "]: " + this.message;
        }
    }
    

    【讨论】:

      【解决方案4】:

      是的,你可以实现它实现 HandlerIntercepter。有了这个,您可以预先处理请求 && 如果您想提供自定义消息,然后使用 @ControllerAdvice 处理异常。

      public class CustomInterceptor implements HandlerInterceptor{
      
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
          //your custom logic here.
          return true;
      }
      }
      

      你需要配置这个拦截器:

      @Configuration
      public class WebMvcConfig extends WebMvcConfigurerAdapter {
      @Override
      public void addInterceptors(InterceptorRegistry registry){
          registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
      }
      }
      

      这里是处理异常:

      @Order(Ordered.HIGHEST_PRECEDENCE)
      @ControllerAdvice
      public class GlobalExceptionHandler {
      
      private static final Logger logger = LogManager.getLogger(GlobalExceptionHandler.class);
      
      @ExceptionHandler(JsonProcessingException.class)
      public void handleJsonException(HttpServletResponse response, Exception ex) {
         //here build your custom response
          prepareErrorResponse(response,UNPROCESSABLE_ENTITY,"");
      }
      
      
      private void prepareErrorResponse(HttpServletResponse response, HttpStatus status, String apiError) {
          response.setStatus(status.value());
          try(PrintWriter writer = response.getWriter()) {
              new ObjectMapper().writeValue(writer, apiError);
          } catch (IOException ex) {
              logger.error("Error writing string to response body", ex);
          }
      }
      }
      

      【讨论】:

      • 嗯,这看起来和我正在寻找的完全一样 - 但我只是有点困惑在 preHandle 方法中放入什么......这就像一个 try/catch 映射请求一个对象?还是我错过了这条船(我只想捕获并针对某些杰克逊数据绑定错误抛出自定义错误消息)..
      • 如果要数据绑定错误,则在 GlobalExceptionalHandler 类的 handleJsonException 中构建消息
      • 在预处理方法中。无事可做
      • 不起作用,我根本无法访问 ControllerAdvice!
      猜你喜欢
      • 1970-01-01
      • 2018-05-30
      • 2013-07-05
      • 1970-01-01
      • 2013-05-28
      • 2018-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多