Обработка ошибок при возникновении исключения на JsonSerializer в веб-приложении spring (не при загрузке spring)

#exception #java-8 #jackson #tomcat8 #spring-rest

Вопрос:

Я определил пользовательский JsonSerializer в своем веб-приложении spring (НЕ Spring Boot).

 public class CalendarSerializer extends StdSerializer<Calendar>
{
    public CalendarSerializer()
    {
        super(Calendar.class);
    }

    /** {@inheritDoc} */
    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider serializers) throws IOException
    {
        ZoneId zoneId = TimeZone.getDefault().toZoneId();
        return value.toInstant().atZone(zoneId);
    }
}
 

Он был включен в определение картографа json, и если в методе не возникает исключений serialize() , все работает хорошо.

Чтобы справиться с ошибками, я создал ResponseEntityExceptionHandler

 @ControllerAdvice
public class RestApiExceptionHandler extends ResponseEntityExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(RestApiExceptionHandler.class);

    @ExceptionHandler(RestApiException.class)
    protected ResponseEntity<RestApiErrorResponse> handleRestApiException(RestApiException ex)
    {
        RestApiErrorResponse apiErrorResponse = ex.getRestApiErrorResponse();
        log.error("RestApiException occurs.", ex);
        return new ResponseEntity<>(apiErrorResponse, apiErrorResponse.getHttpStatus());
    }

    [...]

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<RestApiErrorResponse> handleUnexpectedError(Exception ex)
    {
        RestApiErrorResponse unexpectedError = RestApiErrorResponse.getUnexpectedError();
        log.error("Unexpected exception. {}", unexpectedError, ex);
        return new ResponseEntity<>(unexpectedError, unexpectedError.getHttpStatus());
    }
}
 

Он обрабатывает некоторые пользовательские исключения, но в любом случае любой вид исключения управляется handleUnexpectedError , и вызывающий правильно получает сериализацию RestApiErrorResponse.

Однако, если в системе возникает исключение CalendarSerializer , ResponseEntityExceptionHandler оно обходится, и вызывающий абонент получает страницу ошибки tomcat по умолчанию с трассировкой стека

Есть ли способ управлять исключением, вызванным JsonSerializers так, чтобы вызывающий абонент получал пользовательский json с подробными сведениями об ошибке?

Вот трассировка стека

 org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.foo.dataobjects.Holiday["startDate"])
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:296)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)
Root Cause

com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.foo.dataobjects.Holiday["startDate"])
    com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
    com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
    com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
    com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:727)
    com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
    com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1392)
    com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:287)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)
Root Cause

java.lang.NullPointerException
    com.foo.rest.serializers.CalendarSerializer.serialize(CalendarSerializer.java:44)
    com.foo.rest.serializers.CalendarSerializer.serialize(CalendarSerializer.java:1)
    com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer.serialize(StdDelegatingSerializer.java:168)
    com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
    com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
    com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
    com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1392)
    com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)
    org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:287)
    org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    com.foo.rest.web.RestApiAuthenticationFilter.doFilter(RestApiAuthenticationFilter.java:122)
 

ОБНОВЛЕНИЕ: Решено добавить в RestApiExceptionHandler метод

 @Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(
    HttpMessageNotWritableException ex,
    HttpHeaders headers,
    HttpStatus status,
    WebRequest request)
{
    String details = "Error exporting the results of the incoming request.";
    RestApiErrorResponse error = new RestApiErrorResponse(details, RestApiErrorCode.SERIALIZATION_ERROR);

    String uri = getUri(request);
    log.error("Error serializing the results for request {}. {}", uri, error, ex);
    return new ResponseEntity<>(error, error.getHttpStatus());
}
 

Комментарии:

1. API для определения java.util даты и времени устарел и подвержен ошибкам. Рекомендуется полностью прекратить его использование и переключиться на современный API даты и времени .

2. Привет @ArvindKumarAvinash , я не искал способ исправить NPE, а также способ работы с календарями. NPE был искусственным, просто чтобы иметь исключение в JsonSerializer, Календари, к сожалению, обязательны для устаревшей проблемы. Решение, которое мне подходит, — это то, о котором я упоминал в разделе ОБНОВЛЕНИЯ.

3. Мора — Пожалуйста, опубликуйте свое обновление в качестве ответа. Через 48 часов вы даже сможете принять свой собственный ответ. Это будет полезно будущим посетителям этой страницы.

4. @ArvindKumarAvinash готово, спасибо

Ответ №1:

Решил сам, добавив в обработчик RestApiExceptionHandler метод

 @Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(
    HttpMessageNotWritableException ex,
    HttpHeaders headers,
    HttpStatus status,
    WebRequest request)
{
    String details = "Error exporting the results of the incoming request.";
    RestApiErrorResponse error = new RestApiErrorResponse(details, RestApiErrorCode.SERIALIZATION_ERROR);

    String uri = getUri(request);
    log.error("Error serializing the results for request {}. {}", uri, error, ex);
    return new ResponseEntity<>(error, error.getHttpStatus());
}