自定义ObjectMapper与Jersey 2.2和Jackson 2.1

时间:2013-09-18 12:58:50

标签: java jersey jackson jax-rs jersey-2.0

我正在与Grizzly,Jersey和Jackson一起使用REST应用程序,因为Jersey忽略了我的自定义ObjectMapper。

POM依赖项:

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-grizzly2-servlet</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.1.4</version>
    </dependency>
</dependencies>

结果版本为:Grizzly 2.3.3,Jackson 2.1.4和Jersey 2.2。

主类(我希望明确注册Jersey组件):

public class Main {
    public static void main(String[] args) {
        try {
            ResourceConfig rc = new ResourceConfig();
            rc.register(ExampleResource.class);
            rc.register(ObjectMapperResolver.class);

            HttpHandler handler = ContainerFactory.createContainer(
                    GrizzlyHttpContainer.class, rc);

            URI uri = new URI("http://0.0.0.0:8080/");

            HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);

            ServerConfiguration config = server.getServerConfiguration();
            config.addHttpHandler(handler, "/");

            server.start();
            System.in.read();

        } catch (ProcessingException | URISyntaxException | IOException e) {
            throw new Error("Unable to create HTTP server.", e);
        }
    }
}

ObjectMapper的ContextResolver:

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperResolver() {
        System.out.println("new ObjectMapperResolver()");
        mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        System.out.println("ObjectMapperResolver.getContext(...)");
        return mapper;
    }

}

ObjectMapperResolver构造函数和getContext都不会被调用。我错过了什么?我更喜欢使用Jersey 2.2和Jackson 2.1,因为它是另一个lib的依赖。

可以在GitHub上找到完整的示例:https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow

6 个答案:

答案 0 :(得分:38)

以下解决方案适用于以下堆栈(如...这是我用来测试它的设置)

泽西岛2.12,杰克逊2.4.x

我正在添加我的消息/我在这篇文章中提出的解决方案,因为它与我今天提出的许多Google搜索非常相关...这是我认为的一个麻烦的解决方案是一个更麻烦的问题。

1。确保您的maven配置包含jackson-jaxrs-json-provider依赖项:

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

2。确保您的maven配置不包含jersey-media-json-jackson依赖项:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
</dependency>

3。创建一个@Provider组件,扩展com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider,如下所示:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

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

    private static ObjectMapper mapper = new ObjectMapper();

    static {
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
     }

    public CustomJsonProvider() {
        super();
        setMapper(mapper);
    }
}

正如您所看到的,这也是我们定义com.fasterxml.jackson.databind.ObjectMapper

的自定义实例的地方

4。通过javax.ws.rs.core.Feature扩展MarshallingFeature,如下所示:

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

public class MarshallingFeature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        context.register(CustomJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class);
        return true;
    }
}

5。如果您通过org.glassfish.jersey.server.ResourceConfig配置应用程序,则需要像这样注册此自定义提供程序:

import org.glassfish.jersey.server.ResourceConfig;
...

public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...
        register(MarshallingFeature.class);
        ...
     }
 }

其他说明和观察:

  1. 无论您是否使用javax.ws.rs.core.Response来包装控制器的响应,此解决方案都适用。
  2. 请确保您仔细考虑(复制/粘贴)以下代码段,因为唯一的“非强制性”可以说是com.fasterxml.jackson.databind.ObjectMapper的自定义配置。
  3. @jcreason

    很抱歉在这个@jcreason上丢球,我希望你仍然很好玩。 所以我查看了去年的代码,这就是我提出的用于提供自定义映射器的代码。

    问题在于,在功能初始化期间,任何自定义对象映射器都会被

    中的某些代码禁用
      

    org.glassfish.jersey.jackson.JacksonFeature:77   (Jersey的媒体JSON-杰克逊-2.12.jar)

    // Disable other JSON providers.
    context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE);
    

    但此功能仅由此组件注册

      

    org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable

    if (!context.getConfiguration().isRegistered(JacksonFeature.class)) {
        context.register(JacksonFeature.class);
    }
    

    所以我所做的就是注册我自己的功能,该功能会注册我自己的对象映射器提供程序 org.glassfish.jersey.jackson.JacksonFeature被注册并覆盖我的对象映射器......

    import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper;
    import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;
    
    import org.glassfish.jersey.internal.InternalProperties;
    import org.glassfish.jersey.internal.util.PropertiesHelper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.ws.rs.core.Configuration;
    import javax.ws.rs.core.Feature;
    import javax.ws.rs.core.FeatureContext;
    import javax.ws.rs.ext.MessageBodyReader;
    import javax.ws.rs.ext.MessageBodyWriter;
    
    public class MarshallingFeature implements Feature {
    
        private final static String JSON_FEATURE = MarshallingFeature.class.getSimpleName();
    
        @Override
        public boolean configure(FeatureContext context) {
    
          context.register(JsonParseExceptionMapper.class);
          context.register(JsonMappingExceptionMapper.class);
          context.register(JacksonJsonProviderAtRest.class, MessageBodyReader.class, MessageBodyWriter.class);
    
          final Configuration config = context.getConfiguration();
          // Disables discoverability of org.glassfish.jersey.jackson.JacksonFeature
          context.property(
              PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE,
                                                         config.getRuntimeType()), JSON_FEATURE);
    
          return true;
        }
    }
    

    这是自定义对象映射器提供程序......

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
    
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.ext.Provider;
    
    @Provider
    @Produces(MediaType.APPLICATION_JSON)
    public class JacksonJsonProviderAtRest extends JacksonJaxbJsonProvider {
    
        private static ObjectMapper objectMapperAtRest = new ObjectMapper();
    
        static {
            objectMapperAtRest.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapperAtRest.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapperAtRest.configure(SerializationFeature.INDENT_OUTPUT, true); // Different from default so you can test it :)
            objectMapperAtRest.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        }
    
        public JacksonJsonProviderAtRest() {
            super();
            setMapper(objectMapperAtRest);
        }
    }
    

答案 1 :(得分:28)

我找到了解决方案。我必须自己实例化Jackson Provider并设置我的自定义ObjectMapper。可以在GitHub上找到一个工作示例:https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer

我删除了ObjectMapperResolver并修改了我的main方法:

public class Main {
    public static void main(String[] args) {
        try {
            // create custom ObjectMapper
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);

            // create JsonProvider to provide custom ObjectMapper
            JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
            provider.setMapper(mapper);

            // configure REST service
            ResourceConfig rc = new ResourceConfig();
            rc.register(ExampleResource.class);
            rc.register(provider);

            // create Grizzly instance and add handler
            HttpHandler handler = ContainerFactory.createContainer(
                    GrizzlyHttpContainer.class, rc);
            URI uri = new URI("http://0.0.0.0:8080/");
            HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
            ServerConfiguration config = server.getServerConfiguration();
            config.addHttpHandler(handler, "/");

            // start
            server.start();
            System.in.read();

        } catch (ProcessingException | URISyntaxException | IOException e) {
            throw new Error("Unable to create HTTP server.", e);
        }
    }
}

答案 2 :(得分:9)

我想出了这一点,基于一些修补。

问题似乎出现在Jersey的功能自动检测机制中。如果依靠Jersey来加载JacksonJaxbJsonProvider,则会忽略ObjectMapper的自定义上下文提供程序。相反,如果您手动注册该功能,则可以正常工作。我假设这与自动检测的提供程序被加载到不同的上下文范围有关,但对于解决方案,这就是我最终得到的结果。请注意,我将其包装到一个功能中,您应该可以直接在应用程序中注册它而不会出现问题。

public final class RequestMappingFeature implements Feature {

    @Override
    public boolean configure(final FeatureContext context) {

        context.register(ObjectMapperProvider.class);

        // If you comment out this line, it stops working.
        context.register(JacksonJaxbJsonProvider.class);

        return true;
    }
}

2017年11月更新:Jersey2世界的情况发生了一些变化。如果上述方法无效,请尝试以下方法:

现在提供自己的ObjectMapper的新方法如下所示:

public final class JacksonFeature implements Feature {

    private static final ObjectMapper MAPPER;

    static {

        // Create the new object mapper.
        MAPPER = new ObjectMapper();

        // Enable/disable various configuration flags.
        MAPPER.configure(
                DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);

        // ... Add your own configurations here.

    }
    @Override
    public boolean configure(final FeatureContext context) {
        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(
                MAPPER, DEFAULT_ANNOTATIONS);
        context.register(provider);

        return true;
    }
}

答案 3 :(得分:8)

请这样做:

1)添加pom.xml依赖

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.2</version>
</dependency>

2)在Main.java中注册JacksonFeature

public class Main {

    public static void main(String[] args) {
        try {
            ResourceConfig rc = new ResourceConfig();
            rc.register(ExampleResource.class);
            rc.register(ObjectMapperResolver.class);
            rc.register(JacksonFeature.class);

            HttpHandler handler = ContainerFactory.createContainer(
                    GrizzlyHttpContainer.class, rc);

            URI uri = new URI("http://0.0.0.0:8080/");

            HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);

            ServerConfiguration config = server.getServerConfiguration();
            config.addHttpHandler(handler, "/");

            server.start();
            System.in.read();

        } catch (ProcessingException | URISyntaxException | IOException e) {
            throw new Error("Unable to create HTTP server.", e);
        }
    }
}

3)在资源中使用org.codehaus.jackson.map.ObjectMapper

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperResolver() {
        System.out.println("new ObjectMapperResolver()");
        mapper = new ObjectMapper();
        mapper.enable(Feature.INDENT_OUTPUT);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        System.out.println("ObjectMapperResolver.getContext(...)");
        return mapper;
    }
}

答案 4 :(得分:7)

由于我花了几个小时才能使用Java EE7和Glassfish4,这是我的解决方案:

@javax.ws.rs.ApplicationPath("withJackson")
public class ApplicationConfig extends Application {

    private static final Logger log = java.util.logging.Logger.getLogger(ApplicationConfig.class.getName());

    @Override
    public Set<Object> getSingletons() {
        Set<Object> set = new HashSet<>();
        log.log(Level.INFO, "Enabling custom Jackson JSON provider");
        set.add(new JacksonJsonProvider().configure(SerializationFeature.INDENT_OUTPUT, true));
        return set;
    }

    @Override
    public Map<String, Object> getProperties() {
        Map<String, Object> map = new HashMap<>();
        log.log(Level.INFO, "Disabling MOXy JSON provider");
        map.put("jersey.config.disableMoxyJson.server", true);
        return map;
    }

    @Override
public Set<Class<?>> getClasses() {
    Set<Class<?>> resources = new java.util.HashSet<>();
    addRestResourceClasses(resources);
    return resources;
}

/**
 * Do not modify addRestResourceClasses() method.
 * It is automatically populated with
 * all resources defined in the project.
 * If required, comment out calling this method in getClasses().
 */
private void addRestResourceClasses(Set<Class<?>> resources) {
    resources.add(com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper.class);
    resources.add(com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper.class);
    resources.add(com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper.class);
    resources.add(com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper.class);
    resources.add(de.lathspell.java_test_ee7_json.Api.class);
    resources.add(de.lathspell.java_test_ee7_json.with_jackson.MyExceptionMapper.class);
}

唯一相关的POM依赖项是:

    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.2.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.2.3</version>
    </dependency>

    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>

答案 5 :(得分:2)

使用Jackson 2.7,这样做没有用:

public class MyApplication extends ResourceConfig {
    public MyApplication() {
    register(MyObjectMapperProvider.class);
}}

调用了MyObjectMapperProvider构造函数,但从未调用 getContext()

在super()构造函数中注册MyObjectMapperProvider使其工作:

public class MyApplication extends ResourceConfig {
   public MyApplication() {
       super(
            // register Jackson ObjectMapper resolver
            MyObjectMapperProvider.class
       );
}}

请参阅this Jersey example code