Как подключить Jackson ObjectMapper с Guice/Jersey

Кажется, я не смог правильно зарегистрировать модуль модуля Jackson ObjectMapper.

Я использую стек Guice + Jersey + Jackson (FasterXML).

Я следил за тем, как настроить ObjectMapper на основе различных вопросов здесь. В частности, у меня есть объявленный ContextResolver, помеченный как @javax.ws.rs.ext.Provider и @javax.inject.Singleton.

У меня есть GuiceServletContextListener по строкам:

@Override
protected Injector getInjector() {

     Injector injector = Guice.createInjector(new DBModule(dataSource),
            new ServletModule()
            {
                @Override
                protected void configureServlets() {


                    // Mapper
                    bind(JacksonOMP.class).asEagerSingleton();

                    // ... 

                    Map<String, String> initParams = new HashMap<String, String>();
                    initParams.put("com.sun.jersey.config.feature.Trace",
                            "true");
                    initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");

                    serve("/services/*").with(
                            GuiceContainer.class,
                            initParams);
                }
            });

    return injector;
}

Определено отображение

import javax.inject.Singleton;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
@Singleton
@Produces
public class JacksonOMP implements ContextResolver<ObjectMapper> {

  @Override
  public ObjectMapper getContext(Class<?> aClass) {
    final ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new Hibernate4Module());
    return mapper;
  }
}

Однако - только с этой конфигурацией getContext() никогда не вызывается, поэтому преобразователь никогда не регистрируется. Я застрял в типичном узоре - аннотации-таинственности, где он практически не отслеживается тем, что я на самом деле должен делать. Spring пользователи просто сообщают о регистрации компонента, а контейнер просто выбирает его.

Этот ответ говорит о переопределении моей собственной реализации javax.ws.rs.core.Application. Тем не менее, это выглядит жестко связанным с jersey-guice impementation GuiceContainer как DefaultResourceConfig():

@Override
protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
        WebConfig webConfig) throws ServletException {
    return new DefaultResourceConfig();
}

Я предполагаю, что подкласс GuiceContainer здесь? Или есть еще одна магическая аннотация, которую я пропускаю?

Это кажется довольно распространенным явлением - я удивлен тем, как трудно это доказать с этой комбинацией.

Ответ 1

Я застрял в типичном узоре - аннотации-тайны, где это практически не отслеживая того, что я на самом деле должен делать. Spring пользователи просто сообщают о регистрации компонента, а контейнер просто подбирает его.

Вы действительно должны прочитать отличную документацию по Guice. Guice очень прост в использовании, он имеет очень небольшое количество базовых понятий. Ваша проблема заключается в том, что вы смешали инъекцию зависимостей Джерси JAX-RS и инъекцию зависимости Guice. Если вы используете GuiceContainer, тогда вы заявляете, что будете использовать Guice для всех своих DI, поэтому вам нужно добавить привязки с помощью Guice, а не с JAX-RS.

Например, вам не нужно ContextResolver, вместо этого вы должны использовать простой Guice Provider:

import com.google.inject.Provider;

public class ObjectMapperProvider implements Provider<ObjectMapper> {
    @Override
    public ObjectMapper get() {
        final ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Hibernate4Module());
        return mapper;
    }
}

Затем вы должны добавить соответствующую привязку к вашему модулю:

bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class).in(Singleton.class);

Это свяжет ObjectMapper, но недостаточно использовать Джерси с Джексоном. Вам понадобится какой-то MessageBodyReader/MessageBodyWriter, например. JacksonJsonProvider. Для этого вам понадобится другой провайдер:

public class JacksonJsonProviderProvider implements Provider<JacksonJsonProvider> {
    private final ObjectMapper mapper;

    @Inject
    JacksonJsonProviderProvider(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public JacksonJsonProvider get() {
        return new JacksonJsonProvider(mapper);
    }
}

Затем привяжите его:

bind(JacksonJsonProvider.class).toProvider(JacksonJsonProviderProvider.class).in(Singleton.class);

Это все, что вам нужно сделать - никакого подкласса не требуется.

Однако есть место для оптимизации размера кода. Если бы я был вами, я бы использовал @Provides -методы:

@Provides @Singleton
ObjectMapper objectMapper() {
    final ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new Hibernate4Module());
    return mapper;
}

@Provides @Singleton
JacksonJsonProvider jacksonJsonProvider(ObjectMapper mapper) {
    return new JacksonJsonProvider(mapper);
}

Эти методы должны быть добавлены к одному из ваших модулей, например. анонимному ServletModule. Тогда вам не понадобятся отдельные классы поставщиков.
BTW, вы должны использовать JerseyServletModule вместо простого ServletModule, он предоставляет множество полезных привязок для вас.