Как использовать две версии Jackson (1.x и 2.x) в одном приложении Jersey JAX-RS?

Я работаю в проекте со старой версией Джерси (1.19) вместе со старым Джексоном (1.19.13). Я хотел бы переключиться на новую десериализацию Jackson (2.x), но только для новых конечных точек (и старых постепенно), потому что миграция за один шаг на Jackson 2 и/или Jersey 2 будет очень сложной (о, монолит! ).

Я видел несколько тем о том, как предоставить настроенный ObjectMapper с поставщиками Jackson в Джерси или как установить новый Jackson в 1.x Джерси, но это не то, что я ищу, по крайней мере, не все.

В качестве решения я представляю (предпочтительно) аннотирование моей новой конечной точки JAX-RS чем-то вроде @UseJackson2 или наличие некоторого базового класса с некоторой магией, извлекающей ObjectMapper из правильного пакета для этой конкретной конечной точки и расширяющей ее позже - в другом слова, заставляющие данную конечную точку использовать другого поставщика (де)сериализации, чем обычно.

Я видел примеры с провайдерами для по-разному настроенных ObjectMappers (например, здесь Использование Джексона в Джерси с несколькими настроенными ObjectMappers), но в моем случае ObjectMappers будут исходить из совершенно разных артефактов пакетов/maven.


person Michał Wróbel    schedule 02.11.2017    source источник
comment
Я бы попытался создать свой собственный MessageBodyReader/Writer и использовать его для делегирования вызовов программе чтения/записи 1.x или программе чтения/записи 2.x. Проверьте аргумент Annotation[], чтобы увидеть, присутствует ли ваша аннотация @UseJackson2 или нет. Это просто идея. Не то, что я пробовал.   -  person Paul Samsotha    schedule 02.11.2017
comment
Или вы можете попытаться расширить возможности чтения/записи 2.x и переопределить isReadable и isWritable и проверить аннотацию. Убедитесь, что этот провайдер имеет приоритет над 1.x.   -  person Paul Samsotha    schedule 02.11.2017
comment
Насколько я понимаю здесь (docs.oracle. com/javaee/7/api/javax/ws/rs/ext/), однако коллекция аннотаций представляет собой аннотации к десериализуемому параметру, а не окружающий класс конечной точки JAX-RS. (Так, например, void put(@Jackson2 TheType deserializeMe) . Таким образом, код будет легко загрязняться, поскольку аннотацию придется помещать в каждый параметр полезной нагрузки. (И как применить ее к ответу?) Или я что-то не так понял. В любом случае интересная находка.   -  person Michał Wróbel    schedule 02.11.2017
comment
Можно ли получить, скажем, некоторый «контекст (де) сериализации», чтобы узнать, например, из какого класса конечной точки произошла десериализация?   -  person Michał Wróbel    schedule 02.11.2017
comment
И как применить это к ответу. Похоже, что нужно будет перейти к самому методу (писатель проверяет метод на аннотацию, читатель проверяет параметр). Итак, две локации. Я знаю, некрасиво. Не красивое решение. Можно ли получить, скажем, некий «контекст (де)сериализации», чтобы узнать, например, из какого класса конечной точки произошла десериализация?. Я думал о том же. В Jersey 2.x есть ResourceInfo, которые вы можете ввести. Я мало работаю с 1.x, поэтому не уверен.   -  person Paul Samsotha    schedule 02.11.2017
comment
Таким образом, вы можете внедрить @Context ExtendedUriInfo в программу чтения/записи. Там вы можете получить всю необходимую информацию.   -  person Paul Samsotha    schedule 02.11.2017
comment
Вы можете сделать if (info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)   -  person Paul Samsotha    schedule 02.11.2017
comment
в этом сценарии у нас есть один модуль чтения/записи на (де)сериализованный класс?, или вы думаете, что было бы просто написать модуль чтения/записи для ‹Object›, который будет вести себя правильно (т.е. с учетом аннотации @JsonDeserialize, коллекций .. )?   -  person Michał Wróbel    schedule 02.11.2017
comment
Я бы просто расширил модуль чтения 2.x (см. поставщиков). Это то, что вы бы расширили github.com/FasterXML/jackson-jaxrs-providers/blob/master/json/. Переопределите isReadable и isWritable. Верните false, если аннотация отсутствует, или верните супервызов. Я играю с этим прямо сейчас. Пытаюсь работать.   -  person Paul Samsotha    schedule 02.11.2017
comment
Да, я не знаю. Я получаю странное поведение, когда программа чтения/записи не регистрируется, если я каким-либо образом использую поставщика Jackson 2 (расширение или компоновку). Странный. Не уверен, почему это происходит.   -  person Paul Samsotha    schedule 02.11.2017


Ответы (2)


Что вы можете сделать, так это создать MessageBodyReader/Writer, который обрабатывает версию Джексона 2.x. Методы isReadable и isWritable определяют, какие объекты он может обрабатывать. Что вы можете сделать, чтобы проверить, так это внедрить ExtendedUriInfo Джерси в провайдер и проверить класс ресурсов для вашей аннотации @Jackson2. Если аннотация отсутствует, ваш провайдер игнорирует сущность, а среда выполнения переходит к следующему провайдеру и проверяет, может ли он ее обработать; в данном случае поставщик 1.x.

Вместо того, чтобы полностью создавать свой собственный, вы бы просто расширили провайдера, который Джексон уже предоставляет в своем артефакте.

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.9.2</version>
</dependency>

Вы бы расширили JacksonJaxbJsonProvider.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Jackson2 {
}

@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MyProvider extends JacksonJaxbJsonProvider {

    @Context
    private ExtendedUriInfo info;

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
            return false;
        }
        return super.isReadable(type, genericType, annotations, mediaType);
    }



    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
            return false;
        }
        return super.isWriteable(type, genericType, annotations, mediaType);
    }
}

Затем просто аннотируйте свои классы ресурсов с помощью @Jackson2.

Раньше у меня были проблемы, когда я пытался заставить это работать, потому что я использовал Jackson 2.8.4. Похоже, что вся линейка 2.8 имеет проблемы. Не уверен, что это такое, но с 2.8 мой провайдер вообще не регистрировался. Я тестировал минорные версии 2.2-2.9 (кроме 2.8), и все они работают.

Что касается приоритета, я не уверен, как Джерси определяет приоритет. Если бы сначала вызывался провайдер 1.x, то все это решение развалилось бы. Один из способов обойти это - использовать композицию вместо наследования, где вы просто определяете, какой модуль чтения/записи (1.x или 2.x) использовать внутри ваших методов readFrom и writeTo. Версия 1.x тоже JacksonJaxbJsonProvider, но в ней использовалась упаковка codehaus. Однако из того, что я тестировал, мой провайдер всегда вызывается первым, так что это может не понадобиться. К сожалению, я не могу подтвердить, что это сделано специально.

person Paul Samsotha    schedule 03.11.2017
comment
надо попробовать. что касается приоритетов, может быть, эта аннотация сделает это? dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-2rd-edition/ - person Michał Wróbel; 03.11.2017
comment
Я также начал задаваться вопросом, не будет ли дальше вызываться расширенный JacksonJaxbJsonProvider? (нежелательно) - person Michał Wróbel; 03.11.2017
comment
о приоритете: я только что обнаружил следующий фрагмент кода в MessageBodyFactory (для Jersey 1.19): initWriters(customWriterProviders, customWriterListProviders, providerServices.getProviders(MessageBodyWriter.class)); initWriters(writerProviders, writerListProviders, providerServices.getServices(MessageBodyWriter.class)); - это, вероятно, объясняет, почему ваш провайдер вызывается первым. - person Michał Wróbel; 03.11.2017
comment
К сожалению, после возврата false из пользовательского провайдера по умолчанию вызывается Jackson 2.x JacksonJaxbJsonProvider, поэтому он совершенно разрушает концепцию, теперь я ищу способ удалить этого провайдера из рассмотрения, но пока не знаю. - person Michał Wróbel; 03.11.2017
comment
Это не должно называться. Его даже не следует регистрировать, если только вы не используете сканирование пути к классам. Если это так, да, провайдер будет зарегистрирован, потому что он помечен @Provider. Только так я мог видеть его регистрацию. Если вы используете сканирование пути к классам, вам следует перейти на просто сканирование пакетов. - person Paul Samsotha; 03.11.2017
comment
Или на самом деле (хотя я не сталкивался с этим при тестировании - может быть, просто несоответствие загрузки классов), это может быть из-за файлы сервисов. Джерси будет использовать их для загрузки службы провайдеры. Вы можете попробовать удалить их из банки. - person Paul Samsotha; 03.11.2017
comment
Да, я только что проверил, это определенно проблема с загрузкой (порядком) класса. Я просто меняю свои (maven) зависимости, и я столкнулся с той же проблемой. Первоначально у меня сначала был jersey-son 1.x, но я переключился на него после jackson-jaxrs-json-provider, и да, та же проблема. Вам просто нужно удалить файлы служб, чтобы они не регистрировались автоматически. - person Paul Samsotha; 03.11.2017
comment
После удаления его из jackson-jaxrs-json-provider jar в Tomcat он действительно работает. Но удалить его из зависимости mvn во время сборки может быть сложно. - person Michał Wróbel; 03.11.2017
comment
Не пробовал, но выглядит многообещающе - person Paul Samsotha; 03.11.2017
comment
Да, я только что прочитал то же самое, возможно, попробую. Но может быть какой-то способ удаления провайдера из трикотажа post-init, мог бы быть понятнее? Но из того, что я видел в MessageBodyFactory процесс инициализации ну совсем не открыт-закрыт по принципу. Возможно, используя отражение... но это тоже не чисто. - person Michał Wróbel; 03.11.2017
comment
В своем тесте я создал 2 провайдера для 1.x и 2.x соответственно и зарегистрировал обоих. Это прекрасно работает. - person Leon; 13.02.2019
comment
@ Леон, а ты использовал этот метод проверки аннотации в обоих провайдерах? - person Paul Samsotha; 13.02.2019
comment
@PaulSamsotha Я вставил код, так как он длинный. Я не использовал аннотацию. - person Leon; 13.02.2019

Основываясь на решении Пола Самсоты, у меня отлично сработало создание двух провайдеров.

public class Main {
  public static void main(final String[] args) {
    ResourceConfig config = new ResourceConfig();
    config.register(Service1.class)
        .register(Service2.class)
        .register(JacksonV1Provider.class)
        .register(JacksonV2Provider.class);
    GrizzlyHttpServerFactory.createHttpServer(URI.create("http://0.0.0.0:8123"), config);
  }
}

@Provider
public class JacksonV1Provider extends org.codehaus.jackson.jaxrs.JacksonJsonProvider {
  @Context
  private ExtendedUriInfo uriInfo;

  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v1/");
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v1/");
  }
}

@Provider
public class JacksonV2Provider extends com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider {
  @Context
  private ExtendedUriInfo uriInfo;

  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v2/");
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v2/");
  }
}

dependencies {
    compile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.17'
    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.17'

    compile group: "org.codehaus.jackson", name: "jackson-mapper-asl", version: "$jackson1_version"
    compile group: "org.codehaus.jackson", name: "jackson-jaxrs", version: "$jackson1_version"
    compile group: "org.codehaus.jackson", name: "jackson-xc", version: "$jackson1_version"

    compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson2_vervion"
    compile "com.fasterxml.jackson.core:jackson-core:$jackson2_vervion"
    compile "com.fasterxml.jackson.core:jackson-annotations:$jackson2_vervion"
}
person Leon    schedule 13.02.2019