Spring 中 Jackson 集成最新改进

工程 | Sébastien Deleuze | 2014 年 12 月 02 日 | ...

于 2015/08/31 更新,新增 Jackson 模块部分

Spring 对 Jackson 的支持最近得到了改进,变得更加灵活和强大。这篇博文向您介绍 Spring Framework 4.x 和 Spring Boot 中最实用的 Jackson 相关特性。所有代码示例均来自这个 spring-jackson-demo 示例应用,欢迎查看代码。

JSON 视图

有时,对序列化到 HTTP 响应体的对象进行上下文过滤非常有用。为了提供这样的能力,Spring MVC 现在内置支持 Jackson 的序列化视图 (Serialization Views)(自 Spring Framework 4.2 起,@MessageMapping 处理器方法也支持 JSON Views)。

以下示例演示了如何使用 @JsonView 根据序列化上下文过滤字段——例如,处理集合时获取“摘要”视图,处理单个资源时获取完整表示。

public class View {
	interface Summary {}
}

public class User {

	@JsonView(View.Summary.class)
	private Long id;

	@JsonView(View.Summary.class)
	private String firstname;

	@JsonView(View.Summary.class)
	private String lastname;

	private String email;
	private String address;
	private String postalCode;
	private String city;
	private String country;
}

public class Message {

	@JsonView(View.Summary.class)
	private Long id;

	@JsonView(View.Summary.class)
	private LocalDate created;

	@JsonView(View.Summary.class)
	private String title;

	@JsonView(View.Summary.class)
	private User author;

	private List<User> recipients;
  
	private String body;
}

借助 Spring MVC @JsonView 支持,可以按每个处理器方法选择哪些字段应该被序列化。

@RestController
public class MessageController {

	@Autowired
	private MessageService messageService;

	@JsonView(View.Summary.class)
	@RequestMapping("/")
	public List<Message> getAllMessages() {
		return messageService.getAll();
	}

	@RequestMapping("/{id}")
	public Message getMessage(@PathVariable Long id) {
		return messageService.get(id);
	}
}

在此示例中,如果检索所有消息,由于 getAllMessages() 方法使用 @JsonView(View.Summary.class) 进行注解,因此只会序列化最重要的字段。

[ {
  "id" : 1,
  "created" : "2014-11-14",
  "title" : "Info",
  "author" : {
    "id" : 1,
    "firstname" : "Brian",
    "lastname" : "Clozel"
  }
}, {
  "id" : 2,
  "created" : "2014-11-14",
  "title" : "Warning",
  "author" : {
    "id" : 2,
    "firstname" : "Stéphane",
    "lastname" : "Nicoll"
  }
}, {
  "id" : 3,
  "created" : "2014-11-14",
  "title" : "Alert",
  "author" : {
    "id" : 3,
    "firstname" : "Rossen",
    "lastname" : "Stoyanchev"
  }
} ]

在 Spring MVC 的默认配置中,MapperFeature.DEFAULT_VIEW_INCLUSION 设置为 false。这意味着启用 JSON 视图时,未注解的字段或属性(例如 bodyrecipients)不会被序列化。

当使用 getMessage() 处理器方法检索特定 Message(未指定 JSON View)时,所有字段都会按预期序列化。

{
  "id" : 1,
  "created" : "2014-11-14",
  "title" : "Info",
  "body" : "This is an information message",
  "author" : {
    "id" : 1,
    "firstname" : "Brian",
    "lastname" : "Clozel",
    "email" : "[email protected]",
    "address" : "1 Jaures street",
    "postalCode" : "69003",
    "city" : "Lyon",
    "country" : "France"
  },
  "recipients" : [ {
    "id" : 2,
    "firstname" : "Stéphane",
    "lastname" : "Nicoll",
    "email" : "[email protected]",
    "address" : "42 Obama street",
    "postalCode" : "1000",
    "city" : "Brussel",
    "country" : "Belgium"
  }, {
    "id" : 3,
    "firstname" : "Rossen",
    "lastname" : "Stoyanchev",
    "email" : "[email protected]",
    "address" : "3 Warren street",
    "postalCode" : "10011",
    "city" : "New York",
    "country" : "USA"
  } ]
}

@JsonView 注解只能指定一个类或接口,但您可以使用继承来表示 JSON View 的层级结构(如果一个字段是某个 JSON View 的一部分,它也将是其父视图的一部分)。例如,此处理器方法将序列化使用 @JsonView(View.Summary.class) @JsonView(View.SummaryWithRecipients.class) 进行注解的字段。

public class View {
	interface Summary {}
	interface SummaryWithRecipients extends Summary {}
}

public class Message {

	@JsonView(View.Summary.class)
	private Long id;

	@JsonView(View.Summary.class)
	private LocalDate created;

	@JsonView(View.Summary.class)
	private String title;

	@JsonView(View.Summary.class)
	private User author;

	@JsonView(View.SummaryWithRecipients.class)
	private List<User> recipients;
  
	private String body;
}

@RestController
public class MessageController {

	@Autowired
	private MessageService messageService;

	@JsonView(View.SummaryWithRecipients.class)
	@RequestMapping("/with-recipients")
	public List<Message> getAllMessagesWithRecipients() {
		return messageService.getAll();
	}
}

使用 RestTemplate HTTP 客户端或 MappingJackson2JsonView 时,也可以通过将要序列化的值包装在 MappingJacksonValue 中来指定 JSON Views,如本代码示例所示。

JSONP

参考文档所述,您可以通过声明一个扩展 AbstractJsonpResponseBodyAdvice@ControllerAdvice bean 来为 @ResponseBodyResponseEntity 方法启用 JSONP,如下所示。

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

注册此类 @ControllerAdvice bean 后,就可以使用 <script /> 标签从另一个域请求 JSON Web 服务。

<script type="application/javascript"
            src="http://mydomain.com/1.json?jsonp=parseResponse">
</script>

在此示例中,接收到的负载将是

parseResponse({
  "id" : 1,
  "created" : "2014-11-14",
  ...
});

使用 MappingJackson2JsonView 并且请求包含名为 jsonp 或 callback 的查询参数时,JSONP 也受支持并自动启用。JSONP 查询参数名称可以通过 jsonpParameterNames 属性进行自定义。

XML 支持

自 2.0 版本发布以来,Jackson 为除 JSON 外的其他数据格式提供了第一类支持。Spring Framework 和 Spring Boot 内置支持基于 Jackson 的 XML 序列化/反序列化。

一旦您在项目中包含 jackson-dataformat-xml 依赖,它将自动取代 JAXB2 使用。

使用 Jackson XML 扩展相对于 JAXB2 有几个优势

  • Jackson 和 JAXB 注解都可识别
  • 支持 JSON View,让您可以轻松构建针对 XML 和 JSON 数据格式提供相同过滤输出的 REST Web 服务
  • 无需使用 @XmlRootElement 注解类,每个可在 JSON 中序列化的类都可在 XML 中序列化

您通常还希望确保正在使用的 XML 库是 Woodstox,因为

  • 它比 JDK 提供的 Stax 实现更快
  • 它避免了一些已知问题,例如添加不必要的命名空间前缀
  • 某些功能(例如 pretty print)没有它将无法工作

要使用它,只需将最新的 woodstox-core-asl 依赖添加到您的项目中。

自定义 Jackson ObjectMapper

在 Spring Framework 4.1.1 之前,Jackson HttpMessageConverters 使用 ObjectMapper 的默认配置。为了提供更好且易于自定义的默认配置,引入了新的 Jackson2ObjectMapperBuilder。它是 XML 配置中众所周知的 Jackson2ObjectMapperFactoryBean 的 JavaConfig 等效项。

Jackson2ObjectMapperBuilder 提供了一个优秀的 API,用于自定义各种 Jackson 设置,同时保留 Spring Framework 提供的默认设置。它还允许基于相同的配置创建 ObjectMapperXmlMapper 实例。

Jackson2ObjectMapperBuilderJackson2ObjectMapperFactoryBean 都定义了更好的 Jackson 默认配置。例如,将 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 属性设置为 false,以便允许反序列化包含未映射属性的 JSON 对象。

这些类还允许您轻松注册 Jackson mixins模块序列化器,甚至属性命名策略,例如如果您希望将 Java 属性 userName 在 JSON 中转换为 user_name,可以使用 PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES

使用 Spring Boot

如 Spring Boot 参考文档所述,有多种方法可以自定义 Jackson ObjectMapper

例如,您可以通过将 spring.jackson.serialization.indent_output=true 等属性添加到 application.properties 来轻松启用/禁用 Jackson 特性。

作为替代方案,Spring Boot 还允许通过声明 Jackson2ObjectMapperBuilder @Bean 来自定义 Spring MVC HttpMessageConverters 使用的 Jackson 配置(JSON 和 XML)。

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
	Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
	b.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
	return b;
}

如果您想使用未通过常规配置键公开的高级 Jackson 配置,这将很有用。

如果您只需要注册一个额外的 Jackson 模块,请注意 Spring Boot 会自动检测所有 Module @Bean。例如,要注册 jackson-module-parameter-names

@Bean
public Module parameterNamesModule() {
  return new ParameterNamesModule(JsonCreator.Mode.PROPERTIES);
}

不使用 Spring Boot

在纯粹的 Spring Framework 应用中,您也可以使用 Jackson2ObjectMapperBuilder 来自定义 XML 和 JSON HttpMessageConverters,如下所示。

@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
		builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
		converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
	}
}

Jackson 模块

如果在 classpath 中检测到某些知名的 Jackson 模块,它们会自动注册

默认情况下不会注册一些其他模块(主要是因为它们需要额外的配置),因此您需要显式注册它们,例如使用 Jackson2ObjectMapperBuilder#modulesToInstall(),或者如果您使用 Spring Boot,可以声明一个 Jackson Module @Bean

高级特性

自 Spring Framework 4.1.3 起,由于添加了 Spring context感知的 HandlerInstantiator(详情请参阅 SPR-10768),您能够自动注入 Jackson 处理器(序列化器、反序列化器、类型和类型 ID 解析器)。

例如,这可以允许您构建一个自定义反序列化器,用从数据库检索的完整 Entity 替换 JSON 负载中仅包含引用的字段。

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

保持领先

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 通过一项简单的订阅提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举办的活动

查看 Spring 社区所有即将举办的活动。

查看全部