Spring 中最新的 Jackson 集成改进

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

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

Spring 对 Jackson 的支持最近得到了改进,使其更加灵活和强大。这篇博文将为您提供有关 Spring Framework 4.x 和 Spring Boot 中可用的最有用的 Jackson 相关功能的更新。所有代码示例均来自此 spring-jackson-demo 示例应用程序,欢迎查看代码。

JSON 视图

有时需要对序列化到 HTTP 响应主体中的对象进行上下文过滤。为了提供此类功能,Spring MVC 现在内置支持 Jackson 的序列化视图(从 Spring Framework 4.2 开始,JSON 视图也支持 @MessageMapping 处理程序方法)。

以下示例说明了如何使用 @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);
	}
}

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

[ {
  "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() 处理程序方法(未指定 JSON 视图)检索特定 Message 时,将按预期序列化所有字段。

{
  "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 视图层次结构(如果某个字段是 JSON 视图的一部分,它也将是父视图的一部分)。例如,此处理程序方法将序列化使用 @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 时,也可以指定 JSON 视图,方法是将要序列化的值包装在 MappingJacksonValue 中,如本 代码示例 所示。

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",
  ...
});

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

XML 支持

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

jackson-dataformat-xml 依赖项包含到项目中后,它将自动代替 JAXB2 使用。

使用 Jackson XML 扩展比 JAXB2 有几个优势。

  • Jackson 和 JAXB 注释均会被识别。
  • 支持 JSON 视图,允许您轻松构建 REST Web 服务,并为 XML 和 JSON 数据格式提供相同的过滤输出。
  • 无需使用 @XmlRootElement 注释类,每个可在 JSON 中序列化的类都可以在 XML 中序列化。

通常还需要确保使用的 XML 库是 Woodstox,因为

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

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

自定义 Jackson ObjectMapper

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

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

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

这些类还允许您轻松注册 Jackson mixin模块序列化器,甚至属性命名策略(如 PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES),如果您希望将 userName java 属性转换为 JSON 中的 user_name

使用 Spring Boot

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

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

或者,Spring Boot 还允许通过声明 Jackson2ObjectMapperBuilder @Bean 来自定义 Spring MVC HttpMessageConverter 使用的 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 的 HttpMessageConverter,如下所示

@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 模块

如果在类路径上检测到一些知名的 Jackson 模块,它们会自动注册。

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

高级功能

从 Spring Framework 4.1.3 开始,由于添加了 Spring 上下文感知的 HandlerInstantiator(有关更多详细信息,请参阅 SPR-10768),您能够自动装配 Jackson 处理程序(序列化器、反序列化器、类型和类型 ID 解析器)。

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

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部