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 社群所有即將舉辦的活動。

檢視全部