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 Views

有時過濾序列化到 HTTP 回應主體的物件會很有用。為了提供這種功能,Spring MVC 現在內建支援 Jackson 的序列化 Views(從 Spring Framework 4.2 開始,JSON Views 也支援在 @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 View 時,不會序列化未註釋的欄位或屬性,例如 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 時,也可以指定 JSON Views,方法是將要序列化的值包裝在 MappingJacksonValue 中,如這個 程式碼範例所示。

JSONP

參考文件中所述,您可以透過宣告一個擴展 AbstractJsonpResponseBodyAdvice@ControllerAdvice bean,為 @ResponseBodyResponseEntity 方法啟用 JSONP,如下所示。

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

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

註冊了這樣一個 @ControllerAdvice bean 後,就可以使用 <script /> 標籤從另一個網域請求 JSON webservice。

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

在本範例中,收到的 payload 將是

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

使用帶有查詢參數 jsonp 或 callback 的請求,在使用 MappingJackson2JsonView 時也支援並自動啟用 JSONP。JSONP 查詢參數名稱可以透過 jsonpParameterNames 屬性進行自訂。

XML 支援

自 2.0 版本以來,Jackson 為 JSON 以外的一些其他資料格式提供了一流的支援。Spring Framework 和 Spring Boot 提供內建支援,可基於 Jackson 進行 XML 序列化/反序列化。

只要將 jackson-dataformat-xml 依賴項包含到您的專案中,它就會自動取代 JAXB2 使用。

與 JAXB2 相比,使用 Jackson XML 擴充具有多個優點:

  • Jackson 和 JAXB 註釋都可以識別。
  • 支援 JSON View,允許您輕鬆建構 REST Webservices,為 XML 和 JSON 資料格式提供相同的過濾輸出。
  • 無需使用 @XmlRootElement 註釋您的類別,每個可在 JSON 中序列化的類別都可以在 XML 中序列化。

您通常還需要確保使用的 XML 函式庫是 Woodstox,因為:

  • 它比 JDK 提供的 Stax 實現更快。
  • 它可以避免一些已知問題,例如添加不必要的命名空間前綴。
  • 如果沒有它,某些功能(如美化列印)將無法使用。

為了使用它,只需將最新的 woodstox-core-asl 依賴項添加到您的專案中即可。

自訂 Jackson ObjectMapper

在 Spring Framework 4.1.1 之前,Jackson HttpMessageConverter 使用的是 ObjectMapper 預設配置。為了提供更好且易於自訂的預設配置,引入了一個新的 Jackson2ObjectMapperBuilder。它是 XML 配置中廣為人知的 Jackson2ObjectMapperFactoryBean 的 JavaConfig 等效項。

Jackson2ObjectMapperBuilder 提供了一個不錯的 API 來定制各種 Jackson 設定,同時保留 Spring Framework 提供的預設設定。它還允許基於相同的配置建立 ObjectMapperXmlMapper 實例。

Jackson2ObjectMapperBuilderJackson2ObjectMapperFactoryBean 都定義了更好的 Jackson 預設配置。例如,將 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 屬性設置為 false,以允許反序列化具有未映射屬性的 JSON 物件。

這些類別還允許您輕鬆註冊 Jackson mixins模組序列化器,甚至屬性命名策略(如 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 模組在類別路徑 (classpath) 上被偵測到,它們會自動註冊。

某些其他模組預設不會註冊 (主要是因為它們需要額外的設定),因此您必須明確註冊它們,例如使用 Jackson2ObjectMapperBuilder#modulesToInstall(),或者如果您使用 Spring Boot,則宣告一個 Jackson Module @Bean

進階功能

自 Spring Framework 4.1.3 起,由於新增了 Spring 上下文感知的 HandlerInstantiator (更多詳細資訊請參閱 SPR-10768),您可以自動注入 (autowire) Jackson 處理器 (序列化器、反序列化器、類型和類型 ID 解析器)。

例如,這允許您建立一個自訂的反序列化器,它會將 JSON Payload 中只包含參考的欄位,替換為從資料庫檢索的完整 Entity

取得 Spring 電子報

隨時關注 Spring 電子報

訂閱

領先一步

VMware 提供訓練和認證,加速您的進度。

了解更多

獲得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部