How to hook Jackson ObjectMapper with Guice / Jersey

落花浮王杯 提交于 2019-12-03 05:15:44

问题


I can't seem to get my Jackson ObjectMapper Module registered correctly.

I'm using a Guice + Jersey + Jackson (FasterXML) stack.

I've followed how to customise the ObjectMapper based on various question here. In particular, I have a ContextResolver declared, marked as an @javax.ws.rs.ext.Provider and a @javax.inject.Singleton.

I have a GuiceServletContextListener along the lines of:

@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;
}

Mapper defined

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;
  }
}

However - with this configuration alone, getContext() is never called, so the mapper is never registered. I'm stuck in a typical guice--annotations-mystery, where it's practically untraceable to what I'm actually supposed to be doing. Spring users just report registering the component, and the container just picks it up.

This answer talks about overriding my own javax.ws.rs.core.Application implementation. However, this looks hard-wired in the jersey-guice impementation of GuiceContainer to be DefaultResourceConfig():

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

Am I supposed subclass GuiceContainer here? Or is there some other magic annotation that I'm missing?

This seems a fairly common thing to want to do - I'm surprised at how hard it's proving to do with this guice combination.


回答1:


I'm stuck in a typical guice--annotations-mystery, where it's practically untraceable to what I'm actually supposed to be doing. Spring users just report registering the component, and the container just picks it up.

You really should read excellent Guice documentation. Guice is very easy to use, it has very small number of basic concepts. Your problem is in that you mixed Jersey JAX-RS dependency injection and Guice dependency injection. If you are using GuiceContainer then you declare that you will be using Guice for all of your DI, so you have to add bindings with Guice and not with JAX-RS.

For instance, you do not need ContextResolver, you should use plain Guice Provider instead:

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;
    }
}

Then you should add corresponding binding to your module:

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

This will bind ObjectMapper, but it is not enough to use Jersey with Jackson. You will need some kind of MessageBodyReader/MessageBodyWriter, e.g. JacksonJsonProvider. You will need another provider for it:

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);
    }
}

Then bind it:

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

This is all you need to do - no subclassing is needed.

There is a room for code size optimization though. If I were you I would use @Provides-methods:

@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);
}

These methods should be added to one of your modules, e.g. to anonymous ServletModule. Then you won't need separate provider classes.
BTW, you should use JerseyServletModule instead of plain ServletModule, it provides a lot of useful bindings for you.



来源:https://stackoverflow.com/questions/16757724/how-to-hook-jackson-objectmapper-with-guice-jersey

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!