Джерси. Экземпляры исключения не работают, когда десериализация джексона не работает

Я использую Jersey 2.10 с функцией сериализации/десериализации Jackson в моем REST API.

Моя идея - заставить мой REST API всегда возвращать стандартный ответ об ошибке JSON. Для этого у меня есть классы ExceptionMapper, которые создают правильные ответы об ошибках json для любого исключения, которое бросается в приложение Джерси. У меня также есть jsp, который производит такой же ответ JSON, который я зарегистрировал как страницу с ошибкой в ​​web.xml, который охватывает все ошибки, которые могут возникнуть до загрузки Джерси.

Но есть один случай, когда ни мои контролеры исключений, ни мой json, создающий jsp, не работают, то есть при отправке плохо сформированного json в конечную точку POST REST, которая возвращает только следующее сообщение:

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Content-Type: text/plain
Content-Length: 210
Date: Tue, 24 Jun 2014 22:14:11 GMT
Connection: close

Can not deserialize instance of com.example.rest.User[] out of START_OBJECT token
 at [Source: [email protected]; line: 1, column: 1]

Как я могу заставить Джерси вернуть свой пользовательский ответ об ошибке вместо этого?

UPDATE:

Основываясь на ответе @Lucasz, я сделал больше исследований и обнаружил, что в пакете определены два экземпляра исключения. com.fasterxml.jackson.jaxrs.base(https://github.com/FasterXML/jackson-jaxrs-providers/tree/master/base/src/main/java/com/fasterxml/jackson/jaxrs/base) JsonMappingExceptionMapper и JsonParseExceptionMapper, которые, кажется, затеняют мои настраиваемые карты.

Как я могу отменить регистрацию этих карт?

Вот как я сейчас регистрирую mappers:

@ApplicationPath("/")
public class MyApp extends ResourceConfig{
    public SyntheticAPIApp() {
        packages("com.example.resource", "com.example.mapper");
        register(org.glassfish.jersey.jackson.JacksonFeature.class);
    }
}

Ответ 1

Я тестировал его с помощью указателя исключений, как показано ниже:

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.core.JsonProcessingException;

@Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException>{

        public static class Error {
            public String key;
            public String message;
        }

        @Override
        public Response toResponse(JsonProcessingException exception) {
            Error error = new Error();
            error.key = "bad-json";
            error.message = exception.getMessage();
            return Response.status(Status.BAD_REQUEST).entity(error).build();
        }
}

и он работал.


Обновление: изменило JsonParseException на исключение JsonProcessingException (более общее)


Update2: Чтобы избежать регистрации нежелательных картографов, замените

register(org.glassfish.jersey.jackson.JacksonFeature.class);

с

register(com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class);

Посмотрите на исходный код JacksonFeature, и вы поймете, что происходит.

Ответ 2

У меня была та же проблема, и предыдущий ответ привел меня к решению, но не разворачивал для меня настоящий Джерси (2.22). Сначала мне нужно было использовать org.glassfish.jersey.spi.ExtendedExceptionMapper, как описано в https://jersey.java.net/documentation/latest/representations.html.

Кроме того, Джерси проверяет механизм отображения исключений, который как можно ближе к исключенному исключению (от org.glassfish.jersey.internal.ExceptionMapperFactory):

for (final ExceptionMapperType mapperType : exceptionMapperTypes) {
        final int d = distance(type, mapperType.exceptionType);
        if (d >= 0 && d <= minDistance) {
            final ExceptionMapper<T> candidate = mapperType.mapper.getService();

            if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) {
                mapper = candidate;
                minDistance = d;
                if (d == 0) {
                    // slight optimization: if the distance is 0, it is already the best case, so we can exit
                    return mapper;
                }
            }
        }
    }

Поэтому мне нужно было точно отобразить исключение, а не более общее исключение.

В конце концов, мой провайдер выглядит следующим образом:

@Provider
public final class JsonParseExceptionExceptionHandler implements ExtendedExceptionMapper<JsonParseException> {
    @Override
    public Response toResponse(final JsonParseException exception) {
        exception.printStackTrace();
        return Response.status(Response.Status.BAD_REQUEST).entity("JSON nicht in korrektem Format.").build();
    }

    @Override
    public boolean isMappable(final JsonParseException arg0) {
        return true;
    }
}

Ответ 3

Я использовал "jackson-jaxrs-json-provider 2.8.8" и JAX-RS 2.0

Класс приложения - вам необходимо зарегистрировать класс реализации ExceptionMapper:

@ApplicationPath("pathApplication")
public class ApplicationConfiguration extends Application{


   @Override
   public Set<Class<?>> getClasses() {

      Set<Class<?>> resources = new HashSet<>();
      resources.add(YourJAXRSClass.class);
      resources.add(JsonJacksonEM.class); //ExceptionMapper class implementation
      //others resources that you need...
      return resources; 

   }


}

Внедрение класса ExceptionMapper:

@Provider
public class JsonJacksonEM implements ExceptionMapper<JsonParseException>{


   @Override
   public Response toResponse(JsonParseException exception) {
      //you can return a Response in the way that you want!
      return Response.ok(new YourObject()).build();
   }


}

Ответ 4

У меня была та же проблема и решалось переопределить ExceptionMapper. Отлично! Еще одна вещь, которую мне нужно было сделать и которая не понимала 100%, заключалась в том, как переопределить JacksonProvider для моего приложения (я не знаю, было ли это связано с версией Джерси, которую я использовал - 2.19). Здесь моя часть web.xml, которая ее переопределяет:

<init-param>
    <param-name>jersey.config.server.provider.classnames</param-name>
       <param-value>
          com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
       </param-value>
</init-param>

Ответ 5

Начиная с Джерси 2.26 (1, 2), этого должно быть достаточно, чтобы аннотировать специальный сопоставитель исключений с достаточно высоким Priority (здесь высокое означает низкое, строго положительное число). Чтобы переопределить сопоставления "по умолчанию", предоставляемые org.glassfish.jersey.media:jersey-media-json-jacksonregister(JacksonFeature.class)), мы предоставляем только эти два пользовательских сопоставителя:

@Provider
@Priority(1)
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
  /* ... */
}
@Provider
@Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
  /* ... */
}

К сожалению, JAX-RS 2 Spec игнорирует приоритеты и только заявляет:

При выборе поставщика сопоставления исключений для сопоставления исключения, реализация ДОЛЖНА использовать провайдера, универсальный тип которого является ближайшим суперклассом исключения.

Если не зарегистрировать JacksonFeature.class и зарегистрировать JacksonJaxbJsonProvider.class, как указано в другом ответе, это не привело к согласованным результатам.