自定義轉換器&處理JSON&內容協商
1.自定義轉換器
1.1基本介紹
- SpringBoot 在回應客戶端請求時,將提交的資料封裝成物件時,使用了內置的轉換器,也就是自動幫我們封裝物件,springboot 自帶了124個轉換器,可以實作大部分的型別間的轉換,
- SpringBoot 也支持自定義轉換器,但當前臺發送請求傳遞的引數使用內置的轉換器不能轉換時,這時就需要寫一個自定義的資料型別轉換器,我們只需要實作 Converter 介面的 convert 方法即可,
1.2應用案例
演示自定義轉換器的使用,
(1)save.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>save</title>
</head>
<body>
<form action="/saveMonster" method="post">
編號:<input name="id" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/10001"/><br/>
姓名:<input name="name" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/齊天大圣"/><br/>
年齡:<input name="age" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/888"/><br/>
婚否:<input name="isMarried" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/false"/><br/>
生日:<input name="birth" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/1456/12/12"/><br/>
<!--使用自定義轉換器關聯car,value字串整體添加,使用逗號間隔-->
坐騎:<input name="car" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/避水金晶獸,666.6"/><br/>
<input type="submit" value="https://www.cnblogs.com/liyuelian/archive/2023/03/20/保存"/>
</form>
</body>
</html>
(2)自定義轉換器(String-->Car)
package com.li.config;
import com.li.bean.Car;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author 李
* @version 1.0
* (proxyBeanMethods = false)
* 1.表示啟用Lite模式,保證修飾的配置類中,每個@Bean方法被呼叫多少次回傳的組件都是新創建的,
* 是多例物件,是非代理方式,
* 2.proxyBeanMethods 在呼叫 @Bean 方法時才生效,因此需要先獲取BeanConfig 組件,再呼叫方法
*/
@Configuration(proxyBeanMethods = false)//設定為設定類
public class WebConfig {
//注入bean-WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 1.在 addFormatters()方法中添加一個自定義的轉換器
* 2.自定義轉換器要完成的功能是 String -> Car
* 3.增加的轉換器會注冊到 converters 容器中
* 4.converters 底層結構是 ConcurrentHashMap,默認內置了124個轉換器
*/
registry.addConverter(new Converter<String, Car>() {
//<sourceType,targetType>
//匿名內部類
@Override
public Car convert(String source) {//source即傳入的字串
//加入轉換的業務代碼
if (!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] strings = source.split(",");
car.setName(strings[0]);
car.setPrice(Double.parseDouble(strings[1]));
return car;
}
return null;
}
});
//還可以增加更多的轉換器
}
};
}
}
(3)控制器
Monster和Car是級聯物件,Car為Monster物件的屬性,
//處理添加Monster的方法
@PostMapping("/saveMonster")
@ResponseBody
public String saveMonster(Monster monster) {
System.out.println("monster=" + monster);
return "success";
}
(4)瀏覽器提交表單,后臺輸出如下:
monster=Monster(id=10001, name=齊天大圣, age=888, isMarried=false, birth=Sun Dec 12 00:00:00 CST 1456, car=Car(name=避水金晶獸, price=666.6))
可以看到服務器成功獲取到表單資料,并將car表單項的value值轉換為Car物件的屬性值,
1.3注意事項
不同轉換器通過key值區分,key=[源型別->目標型別]

如果實作的兩個自定義轉換器的 key 值相同(即源型別和目標型別相同),則在注入容器時,根據注入的順序,后一個轉換器會覆寫前一個轉換器!
2.處理JSON
SpringBoot 支持回傳 JSON 格式的資料,在啟用 WEB 開發場景時,已經引入了相關的依賴:spring-boot-starter-json,
例子-使用@ResponseBody處理回傳 json
package com.li.controller;
import com.li.bean.Car;
import com.li.bean.Monster;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
/**
* @author 李
* @version 1.0
*/
@Controller
public class ResponseController {
//撰寫方法,以json格式回傳資料
@GetMapping("/get/monster")
@ResponseBody
public Monster getMonster() {
Monster monster = new Monster();
monster.setId(199);
monster.setName("孫悟空");
monster.setAge(23);
monster.setIsMarried(false);
monster.setBirth(new Date());
Car car = new Car();
car.setName("奔馳");
car.setPrice(20000.0);
monster.setCar(car);
return monster;
}
}
瀏覽器訪問該方法,回傳如下:
為什么SpringBoot可以將Monster物件以 JSON格式回傳呢?
它的底層仍然用到了一個轉換器:AbstractJackson2HttpMessageConverter
其中一個重要的方法如下:
回傳資料的格式是按照你設定的contentType型別,如果沒有指定,默認為json格式,
//引數Object object就是控制器方法回傳的物件型別,如Monster
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//當控制器方法回傳的時候,獲取回傳物件的contentType,一般為application/json
MediaType contentType = outputMessage.getHeaders().getContentType();
//獲取編碼,一般為utf-8
JsonEncoding encoding = this.getJsonEncoding(contentType);
Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
try {//生成一個UTF8JsonGenerator物件
JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);
Throwable var10 = null;
try {//對回傳的資料型別進行一系列處理
this.writePrefix(generator, object);
Object value = https://www.cnblogs.com/liyuelian/archive/2023/03/20/object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue)object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = this.getJavaType(type, (Class)null);
}
ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
//UTF8JsonGenerator物件處理回傳的資料
objectWriter.writeValue(generator, value);
this.writeSuffix(generator, object);
generator.flush();
} catch (Throwable var26) {
var10 = var26;
throw var26;
} finally {
if (generator != null) {
if (var10 != null) {
try {
generator.close();
} catch (Throwable var25) {
var10.addSuppressed(var25);
}
} else {
generator.close();
}
}
}
} catch (InvalidDefinitionException var28) {
throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
} catch (JsonProcessingException var29) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
}
}
3.內容協商
3.1基本說明
內容協商:服務端和請求端協商決定最侄訓傳什么格式的內容,客戶端發送請求的時候可以告知服務器,自己希望對方回傳的資料格式串列,而服務器的介面也有能支持回應的格式串列,最侄訓傳的結果會根據這兩個型別串列,找到一種兩邊都能支持的型別回傳,如果找不到合適的型別,則報錯,
簡單來說就是:根據客戶端接收能力不同,SpringBoot 回傳不同媒體型別的資料
比如:
- 客戶端 Http 請求
Accept: application/xml則回傳 xml 資料 - 客戶端 Http 請求
Accept: application/json則回傳 json 資料
例子1:使用postman測驗
(1)使用postman發送Http請求,在此期間服務器的代碼不變,根據請求頭不同,回傳的資料格式也會不同
回傳json格式:
回傳xml格式:
SpringBoot 默認支持回傳 Json資料,不支持回傳xml資料,所以需要匯入jackson-dataformat-xml
<!--引入處理xml的依賴-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
例子2:使用瀏覽器測驗
瀏覽器不能指定Accept屬性,我們在瀏覽器發出請求,發現回傳的資料為xml格式:
這是因為瀏覽器的Accept中指定了多種媒體型別,如下:
- 支持接收html,xhtml+xml,xml格式型別的權重為0.9
- 支持接收image和
*/*[所有型別] 格式的權重為0.8 - 所以服務器會優先回傳xhtml+xml格式
3.2問題
如上,客戶端通過Http請求的Accept來指定能接收的媒體型別,那么服務端的介面是怎么回應這個型別的呢?
內容協商原理
內容協商原理:
- 判斷當前回應頭中是否已經有確定的媒體型別
- 獲取客戶端Accept請求頭欄位
- 遍歷回圈所有當前系統的 MessageConverter,看誰支持操作這個物件
- 找到支持操作當前操作物件的converter,把converter支持的媒體型別統計出來
- 進行內容協商得到最佳匹配媒體型別
3.3注意事項
Postman可以通過修改Accept的值來回傳不同的資料格式,對于瀏覽器來說,我們無法修改其Accept的值,這時如果要指定回傳json格式,怎么辦呢?
解決方案:開啟支持基于請求引數的內容協商功能
(1)修改application.yml
spring:
mvc:
contentnegotiation:
favor-parameter: true #開啟基于請求引數的內容協商,默認不開啟
(2)在瀏覽器請求的時候帶上format引數,此時回傳的就是指定的格式了
注意:引數format的值是規定好的,在開啟請求引數的內容協商功能后,SpringBoot底層ParameterContentNegotiationStrategy會通過format來接收引數,然后回傳對應的媒體型別/資料格式,因此format的值也要是SpringBoot能處理的才行,
當然format這個屬性名本身也可以通過組態檔修改:
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/547473.html
標籤:其他
上一篇:Java內部類筆記整理
