【问题标题】:Using Jackson ObjectMapper with Jersey将 Jackson ObjectMapper 与 Jersey 一起使用
【发布时间】:2014-01-01 01:16:12
【问题描述】:

我正在使用 Jersey 2.4 创建一个提供 JSON 对象的简单 REST 接口。我的问题是我正在尝试使用 fasterxml Jackson 注释来控制输出,这对我不起作用。我已将注释放入我的 bean 类中,但它们被忽略了。

当我显式创建一个 ObjectMapper 并使用它来对 Java bean 进行字符串化时,我得到了我想要的输出,它尊重 Jackson 注释。但是,我希望我不必执行此步骤,以便我的资源类可以简单地返回 bean,而 Jersey 框架会负责对其进行字符串化。

我尝试使用Custom ObjectMapper with Jersey 2.2 and Jackson 2.1 的答案来解决这个问题,但是,这似乎对我不起作用。我看到 ContextResolver 已创建但从未被调用。

我也花了很多时间试图解决这个看似简单的问题。我已将其简化为一个非常简单的测试用例,如下所示。我将不胜感激任何帮助解决这个问题。

资源 Java 类:

@Path("resource")
public class MainResource {

    public static class Foobar {
        @JsonIgnore
        private String foo = "foo";
        private String baa = "baa";
        private Map<String, List<? extends Number>> map = new HashMap<>();

        public Foobar() {
            map.put("even", Arrays.asList(new Integer[] { 2, 4, 6, 8, 10 }));
            map.put("odd", Arrays.asList(new Integer[] { 1, 3, 5, 7, 9 }));
            map.put("float", Arrays.asList(new Float[] { 1.1F, 2.2F, 3.3F }));
        }

        public String getFoo() {
            return foo;
        }

        public void setFoo(String foo) {
            this.foo = foo;
        }

        public String getBaa() {
            return baa;
        }

        public void setBaa(String baa) {
            this.baa = baa;
        }

        @JsonAnyGetter
        public Map<String, List<? extends Number>> getMap() {
            return map;
        }

        public void setMap(Map<String, List<? extends Number>> map) {
            this.map = map;
        }
    }

    private ObjectMapper om = new ObjectMapper();

    @GET
    @Path("get-object")
    @Produces(MediaType.APPLICATION_JSON)
    public Foobar getObject() {
        // In this method, I simply return the bean object but the WRONG JSON syntax is generated.
        return new Foobar();
    }

    @GET
    @Path("get-string")
    @Produces(MediaType.APPLICATION_JSON)
    public String getString() throws JsonProcessingException {
        // This method returns the RIGHT JSON syntax but I don't want to have to explicitly use the ObjectMapper.
        Foobar foobar = new Foobar();
        return om.writeValueAsString(foobar);
    }
}

web.xml:

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <module-name>sample</module-name>

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>ie.cit.nimbus.sample</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

POM 依赖:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-spring3</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.4.1</version>
    </dependency>
</dependencies>

【问题讨论】:

    标签: java json spring rest jersey-2.0


    【解决方案1】:

    编辑:不要使用下面的旧方法,因为它会产生错误(至少对于 android 设备,请参阅 EDIT2 了解更多详细信息)。在我的测试中,Jersey v2.6 似乎解决了@Provide 的问题,这种方法不起作用。我能够使用这个简单的提供者让它工作:

    @Provider
    public class JerseyMapperProvider implements ContextResolver<ObjectMapper> {
        private static ObjectMapper apiMapper = ObjectMapperManager.createMapperForApi();
        @Override
        public ObjectMapper getContext(Class<?> type)
        {
            return apiMapper;
        }
    }
    

    所以请不要从下面使用我的 hack。


    旧方法

    使用

    @Provider
    public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>
    

    对我不起作用(Jersey 2.4 和 Jackson 2.3),这可能是由于 jackson 提供程序中报告了代码中的错误,其中 ContextResolver 应在 JacksonJsonProvider.java (2.3rc1) 中注册:

     @Override
    protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType)
    {
        if (_providers != null) {
            ContextResolver<ObjectMapper> resolver = _providers.getContextResolver(ObjectMapper.class, mediaType);
            /* Above should work as is, but due to this bug
             *   [https://jersey.dev.java.net/issues/show_bug.cgi?id=288]
             * in Jersey, it doesn't. But this works until resolution of
             * the issue:
             */
            if (resolver == null) {
                resolver = _providers.getContextResolver(ObjectMapper.class, null);
            }
            if (resolver != null) {
                return resolver.getContext(type);
            }
        }
        return null;
    }
    

    但至少我无法访问https://jersey.dev.java.net/issues/show_bug.cgi?id=288,所以我不知道这个bug是关于什么的。

    但我找到了一种解决方法(如果你愿意的话,可以使用 hack)。只需使用适当的注释扩展 JacksonJsonProvider 并返回您的 ObjectMapper,如下所示:

    @Provider
    @Consumes(MediaType.APPLICATION_JSON) // NOTE: required to support "non-standard" JSON variants
    @Produces(MediaType.APPLICATION_JSON)
    public class JacksonHackProvider extends JacksonJsonProvider {
        @Override
        protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
            return new MyCustomObjectMapper();
        }
    }
    

    不需要做任何事情,它会自己注册(检查日志,它会在你第一次访问 json 休息服务时注册)。这现在对我有用,不优雅,但我放弃了。

    编辑:谨慎使用 - 我遇到了一个可能与此 hack 相关的错误:Android volley 无法发送带有请求正文的 POST/PUT 请求,总是从框架中获得 400,我将调查并报告我的发现。

    EDIT2:每当带有 volley 和 OKHTTP 客户端的 Android 应用程序尝试执行 POST 或 PUT 请求时,这个 hack 确实会导致通用 400,所以不要使用它 - 在我的测试球衣 2.6 中似乎解决了这个问题所以你可以使用@Provide 方法

    【讨论】:

    • 您使用的是什么“ObjectMapperManager”类?是你自己的吗?
    • 只是一个自定义工厂类,您可以将其替换为例如。 new ObjectMapper()
    • 只是一个自定义工厂类,您可以将其替换为例如。 new ObjectMapper()
    • 新方法不适用于球衣 1.18。如何在 web.xml 文件中注册?
    【解决方案2】:

    不幸的是,每个人都比它需要的更难。 Jersey 团队在他们的智慧中决定集成 Jackson 1.9,所以他们的东西不会帮助你。

    但这对我来说很容易。只需这样做:

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

    现在摆脱这个:

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

    然后在您的 web.xml 中更改此行:

    <param-value>ie.cit.nimbus.sample</param-value>
    

    成为:

    <param-value>ie.cit.nimbus.sample,com.fasterxml.jackson.jaxrs.json</param-value>
    

    应该可以的。

    【讨论】:

    • 谢谢。我试过这个,但似乎没有任何区别。它仍然会忽略 fastxml 注释并产生错误的 JSON 输出。还有什么我需要改变的吗?
    • 你能给我你想使用的具体注释吗?我将设置一个测试用例。
    • 你的意思是JsonIgnore注解吗?尝试将它放在 getter 和/或 setter 而不是私有字段上。
    • 如果你不喜欢包扫描或者想自定义ObjectMapper,你可以在ResourceConfigregister(JacksonJsonProvider.class)register(new JacksonJsonProvider(myObjectMapper))
    【解决方案3】:

    使用Jersey 2.13,你可以强制@Provider使用相同的ObjectMapper应该只创建一个ObjectMapper

    package com.example.api.environment.configs;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.datatype.joda.JodaModule;
    
    import javax.ws.rs.ext.ContextResolver;
    import javax.ws.rs.ext.Provider;
    
    @Provider
    public class JacksonConfig implements ContextResolver<ObjectMapper> {
    
      private final ObjectMapper objectMapper;
    
      public JacksonConfig() {
        objectMapper = new ObjectMapper()
              .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
              .registerModule(new JodaModule());
      };
    
      @Override
      public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
      }
    }
    

    我在ObjectMapper 中使用@Autowire 即时生成json-schema 文档。

    【讨论】:

    • 你的解决方案可以使用,没有spring感染所有其他类吗?
    【解决方案4】:

    有很多方法可以将 jacksonJax-rs Jersey 实现集成。

    如果你看看 Mkyong 教程:http://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/ 看来您还应该在 web.xml 的 init 参数中传递“POJOMappingFeature”-> true。 我认为这适用于 Jersey 1.8

    如果您改为查看 Jersey 官方文档: https://jersey.java.net/nonav/documentation/latest/user-guide.html#json.jackson 看来您应该实现一个 Jax-rs 提供程序并将该提供程序添加到您的应用程序资源中

    @Provider
    public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>
    

    他们为您提供了如何执行此操作的示例 https://github.com/jersey/jersey/blob/2.4.1/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java

    我使用这种方式解决了我的问题,Jackson 提供者正确扫描了 Jackson 注释。


    题外话我建议你在你的bean中使用这个语法来初始化地图:

    import static java.util.Arrays.asList;
    
    public static class Foobar {
            @JsonIgnore
            private String foo = "foo";
            private String baa = "baa";
            private Map<String, List<? extends Number>> map = new HashMap<>(){{
                put("even", asList(2, 4, 6, 8, 10));
                put("odd", asList(1, 3, 5, 7, 9));
                put("float", asList(1.1F, 2.2F, 3.3F));
            }};
    
            public Foobar() {
            }
    
            public String getFoo() {
                return foo;
            }
    
            public void setFoo(String foo) {
                this.foo = foo;
            }
    
            public String getBaa() {
                return baa;
            }
    
            public void setBaa(String baa) {
                this.baa = baa;
            }
    
            @JsonAnyGetter
            public Map<String, List<? extends Number>> getMap() {
                return map;
            }
    
            public void setMap(Map<String, List<? extends Number>> map) {
                this.map = map;
            }
        }
    

    【讨论】:

      【解决方案5】:

      像这样添加每个模型类

      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      public class Event {
        -----
        --
      }
      

      它对我有用,但我使用了 codehus jackson jars

      【讨论】:

        猜你喜欢
        • 2013-05-21
        • 1970-01-01
        • 2014-10-30
        • 2019-11-09
        • 1970-01-01
        • 2017-02-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多