使用自定义消息捕获和处理Jackson异常

22

我希望能够捕获在我正在开发的Spring Boot API中发生的Jackson异常。 例如,我有以下请求类,并且想要捕获当JSON请求对象中的“questionnaireResponse”键为null或空白即“ ”时发生的错误。

@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为空或无效),类似于@ControllerAdvice扩展ResponseEntityExceptionHandler的方式?


这里提供了一个 Quarkus 用户的解决方案:https://stackoverflow.com/q/65206075/812102。 - Skippy le Grand Gourou
4个回答

9

可以尝试以下内容:

@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(...)


1
谢谢您的回复!我已经尝试使用异常导入com.fasterxml.jackson.databind.exc.InvalidTypeIdException; 然而,当调用异常时,处理程序方法从未被捕获或执行。 - Funsaized
@Broncos423 尝试仅将 map 更改为 'RuntimeException.class' 并在处理程序内进行调试,我发现许多异常都被包装在 Spring 异常中。 - Eduardo
是否有一种与Spring无关的方法来提取自定义异常? - jcflorezr

1

我发现了一个类似的问题,只是我的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(在Spring Boot中使用@RestController注释,对我有效)来捕获JSON解析错误。即使我无法使用@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;
    }
}

0
您可以按照以下方式进行操作:
@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 中,您可以检查 ex 的原因是否为某个实例,并相应地返回自定义错误消息。


-1

是的,你可以通过实现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方法中放什么..那会不会像尝试/捕获将请求映射到对象?还是我错过了什么(我只想为某些jackson databind错误捕获和抛出自定义错误消息).. - Funsaized
如果您想进行数据绑定错误,则在GlobalExceptionalHandler类的handleJsonException中构建消息。 - GolamMazid Sajib
在prehandle方法中,没有任何事情要做。 - GolamMazid Sajib
无法工作,我根本无法到达ControllerAdvice! - Matley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接