【问题标题】:Spring and JacksonJson, serialising different fields with viewsSpring和Jackson Json,用视图序列化不同的字段
【发布时间】:2011-10-17 12:24:34
【问题描述】:

previous similar question 中,我询问了如何使用 JacksonJson 和 Spring 序列化两组不同的字段。

我的用例是带有 @ResponseBody 注释的典型 Controller 映射,直接返回特定对象或对象集合,然后每当客户端在 Accept 标头中添加 application/json 时使用 JacksonJson 呈现这些对象。

我有两个答案,第一个建议使用不同的 getter 列表返回不同的接口,第二个建议使用 Json Views。

我理解第一种方式没有问题,但是第二种方式,在阅读了JacksonJsonViews上的文档后,我不知道如何用Spring实现它。

为了继续举例,我将在 Views 类中声明三个存根类:

// View definitions:
public class Views {
    public static class Public { }
    public static class ExtendedPublic extends PublicView { }
    public static class Internal extends ExtendedPublicView { }
}

然后我要声明提到的类:

public class PublicView { }   
public class ExtendedPublicView { }

他们到底为什么要声明空静态类和外部空类,我不知道。我知道他们需要一个“标签”,但是 Views 的静态成员就足够了。并不是ExtendedPublic 扩展Public,因为这是合乎逻辑的,但它们实际上完全不相关。

最后,bean 将通过注解指定视图或视图列表:

//changed other classes to String for simplicity and fixed typo 
//in classname, the values are hardcoded, just for testing
public class Bean {
    // Name is public
    @JsonView(Views.Public.class)
    String name = "just testing";

    // Address semi-public
    @JsonView(Views.ExtendedPublic.class)
    String address = "address";

    // SSN only for internal usage
    @JsonView(Views.Internal.class)
    String ssn = "32342342";
}

最后在 Spring Controller 中,我要考虑如何更改我的测试 bean 的原始映射:

@RequestMapping(value = "/bean") 
@ResponseBody
public final Bean getBean() {
    return new Bean();
}

它说调用:

//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);

所以我有一个不知从何而来的ObjectMapper 实例和一个out,它不是典型的servlet PrintWriter out = response.getWriter();,而是JsonGenerator 的一个实例,并且无法通过new 运算符获得。所以不知道怎么修改方法,这里是一个不完整的尝试:

@RequestMapping(value = "/bean")
@ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    JsonGenerator out; //how to create?
    objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
    return ??; //what should I return?
}

所以我想知道是否有人成功使用 JsonView 和 Spring 以及他/她是如何做到的。整个概念看起来很有趣,但文档似乎缺乏,示例代码也不见了。

如果不可能,我将只使用相互扩展的接口。抱歉问了这么长的问题。

【问题讨论】:

  • 你为什么要自己做 JSON?设置为使用 Jackson 的 HttpMessageConverter 应该自动转换它。
  • @chrislovecnm,在我的简单示例中,我只是返回带有 ResponseBody 注释的对象或集合,Spring 会在“幕后”完成所有工作。这就是问题所在,因为它是“秘密地”进行的,所以我不知道如何向它传递参数(在我的例子中是 Views.Public.class)来改变它的行为。

标签: json spring serialization spring-mvc jackson


【解决方案1】:

根据@igbopie 和@chrislovecnm 的回答,我整理了一个注释驱动的解决方案:

@Controller
public class BookService
{
    @RequestMapping("/books")
    @ResponseView(SummaryView.class)
    public @ResponseBody List<Book> getBookSummaries() {}

    @RequestMapping("/books/{bookId}")
    public @ResponseBody Book getBook(@PathVariable("bookId") Long BookId) {}
}

SummaryViewBook 模型上的注释如下:

@Data
class Book extends BaseEntity
{
    @JsonView(SummaryView.class)
    private String title;
    @JsonView(SummaryView.class)
    private String author;
    private String review;

    public static interface SummaryView extends BaseView {}
}

@Data
public class BaseEntity
{
    @JsonView(BaseView.class)
    private Long id;    
}

public interface BaseView {}

然后将自定义 HandlerMethodReturnValueHandler 连接到 Spring MVC 的上下文中以检测 @ResponseView 注释,并相应地应用 Jackson 视图。

我提供了完整的代码over on my blog

【讨论】:

  • 不错的解决方案!注释驱动的解决方案看起来非常整洁。
  • 这是一个很好的解决方案,但是我不想注释每个类中的每个字段来排除几个字段。因此,我使用 @ResponseMixinFilters 扩展了您的注释方法,它提供了 2 个数组(实体和过滤器类),然后在 ViewAndFilterAwareJsonMessageConverter 中执行 mapper.getSerializationConfig().addMixInAnnotations(entity,filter)。
【解决方案2】:

您需要手动连接 MappingJacksonHttpMessageConverter。在 spring 3.1 中,您可以使用 mvc xml 标签,如下所示:

<mvc:annotation-driven >
    <mvc:message-converter>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>

不使用 spring 3.1 是相当难看的,它会为你节省大约 20 行 xml。 mvc:annotation 标签做的很多。

您需要使用正确的视图编写器连接对象映射器。我最近注意到使用@Configuration 类可以使这样的复杂接线变得容易得多。使用 @Configuration 类并使用您的 MappingJacksonHttpMessageConverter 创建一个 @Bean,并将引用连接到该 bean 而不是上面的 MappingJacksonHttpMessageConverter。

【讨论】:

  • 感谢克里斯的回答。但是,我远未理解我应该做什么。我正在使用 Spring 3.0.6,并且我已经在使用 mvc:annotation-driven,我已按照建议添加了 mvc:message-converter。但是,我得到“元素 'mvc:annotation-driven' 必须没有字符或元素信息项 [children],因为类型的内容类型为空。”然后你说“用正确的视图编写器连接对象映射器”,这可能意味着类似于这样的调用:objectMapper.viewWriter(Views.Public.class),我不知道在哪里以及如何获取对 objectMapper 的引用。
  • 那么你所说的“我注意到了......”是另一种解决方案吗?在实践中,bean 将由 AOP @Configuration 标记自动装配,带有 MappingJacksonHttpMessageConverter,并且我将变量命名为我喜欢的 Spring 会找到它吗?那我得说说看法,怎么办?以及如何区分它们?我必须在每个对象中设置一些东西?
  • @stivlo 您需要使用 Spring 3.1 才能使用 mvc:message-converter 标签。我也不熟悉 JacksonJsonViews ...
  • 谢谢你,克里斯,所以我稍后会再次尝试在这个区域进行实验......现在我将简单地退回所有属性,因为我还有一些其他紧急服务要交付。万一我以后发现了什么,我会分享。
  • 非常感谢克里斯!我有一个非常相似的问题,你的建议解决了它。不是春天盛大...
【解决方案3】:

我已经设法通过这种方式解决了这个问题:

  • 创建自定义抽象类以包含 json 响应对象:
public abstract AbstractJson<E>{
    @JsonView(Views.Public.class)
    private E responseObject;

    public E getResponseObject() {
        return responseObject;
    }
    public void setResponseObject(E responseObject) {
        this.responseObject = responseObject;
    }
}
  • 为每个可见性创建一个类(只是为了标记响应):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
  • 更改您的方法声明:
    @RequestMapping(value = "/bean")
    @ResponseBody
    public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
         return new PublicJson(new Bean());
    }
  • 创建自定义MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public PublicApiResponseMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(PublicJson.class)){
             return true;
         }
         return false;
     }

}

public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public ExtendedPublicJsonMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(ExtendedPublicJson.class)){
             return true;
         }
         return false;
     }

}

public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public InternalJsonMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(Internal.class)){
             return true;
         }
         return false;
     }

}
  • 将以下内容添加到您的 xml:
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="PublicJsonMessageConverter"></bean>
        <bean class="ExtendedPublicJsonMessageConverter"></bean>
        <bean class="InternalJsonMessageConverter"></bean>
    </mvc:message-converters>
</mvc:annotation-driven>

就是这样!我不得不更新到 spring 3.1 但仅此而已。我使用 responseObject 发送有关 json 调用的更多信息,但您可以覆盖 MessageConverter 的更多方法以使其完全透明。我希望有一天春天会为此添加一个注释。

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    • 2012-07-04
    • 2011-07-30
    • 2019-10-21
    • 1970-01-01
    • 2020-07-27
    相关资源
    最近更新 更多