Spring Boot(一)REST
Spring Boot(一)REST
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)
@GetMapping("/v1/{user_id}")
public User user(@PathVariable("user_id") String userId) {
return new User(userId, "binarylei", "123456");
}
默認返回一個 json,如果需要返回 xml 或者自定義返回類型時怎麽辦呢?
一、自定義媒體類型
1.1 引入 application/xml 解析器
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
1.2 自定義解析器
(1) PropertiesHttpMessageConverter
public class PropertiesHttpMessageConverter extends AbstractHttpMessageConverter<User> { public PropertiesHttpMessageConverter() { super(Charset.forName("utf-8"), MediaType.valueOf("application/properties")); } @Override protected boolean supports(Class clazz) { return clazz == User.class; } @Override protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { Properties properties = new Properties(); properties.load(inputMessage.getBody()); User user = new User(); user.setUserId(properties.getProperty("user.id")); user.setUsername(properties.getProperty("user.name")); return user; } @Override protected void writeInternal(User user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { Properties properties = new Properties(); properties.setProperty("user.id", user.getUserId()); properties.setProperty("user.name", user.getUsername()); properties.setProperty("user.password", user.getPassword()); properties.store(outputMessage.getBody(), "write"); } }
(2) 配置類 PropertiesWebMvcConfigurer
@Configuration public class PropertiesWebMvcConfigurer implements WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new PropertiesHttpMessageConverter()); } }
(3) rest 接口定義
@GetMapping(value = "/v3/properties/to/json",
consumes = "application/properties",
produces = "application/json")
public User propertiesToHJson(@RequestBody User user) {
return new User("1", "binarylei", "123456");
}
@GetMapping(value = "/v3/json/to/properties",
consumes = "application/json",
produces = "application/properties")
public User jsonToProperties(@RequestBody User user) {
return new User("1", "binarylei", "123456");
}
(4) 測試
- 測試1:
請求地址:localhost:8080//v3/properties/to/json
請求頭:Accept: application/properties, Content-Type: application/json
請求參數:user.id=1 user.name=binarylei
- 測試2:
請求地址:localhost:8080/v3/json/to/properties
請求頭:Accept: application/json, Content-Type: application/properties
請求參數:{}
二、源碼分析
@EnableWebMvc 註入了 DelegatingWebMvcConfiguration 組件,其類圖結構如下:
2.1 默認 HttpMessageConverter 加載
在 WebMvcConfigurationSupport 類中定義了許多默認的 HttpMessageConverter,根據是否有相應的類加載來判斷是否啟動對應的 HttpMessageConverter。
// 類型轉換器
private List<HttpMessageConverter<?>> messageConverters;
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters); // (1)
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters); // (2)
}
extendMessageConverters(this.messageConverters); // (3)
}
return this.messageConverters;
}
(1) 由子類 DelegatingWebMvcConfiguration 重寫了 configureMessageConverters 方法,實際上是委托給了 WebMvcConfigurer 完成。
(2) 加載默認的 HttpMessageConverter
(3) 同 (1),也是由子類重寫 extendMessageConverters
下面我們看一下 Spring Boot 默認加載了那些 HttpMessageConverter
boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
try {
messageConverters.add(new SourceHttpMessageConverter<>());
} catch (Throwable ex) {
// Ignore when no TransformerFactory implementation is available...
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
// 省略...
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
// 省略...
}
可以看到除了 ByteArrayHttpMessageConverter 等是固定加載外,其余的都是通過判斷是否有相應的類來決定是否啟用。如果需要使用相應的解析器,只需要到相應的 jar 包添加到 pom.xml 中即可。
最終容器中加載了如下的 HttpMessageConverter 解析器:
0 = {ByteArrayHttpMessageConverter@5783}
1 = {StringHttpMessageConverter@5784}
2 = {ResourceHttpMessageConverter@5785}
3 = {ResourceRegionHttpMessageConverter@5786}
4 = {SourceHttpMessageConverter@5787}
5 = {AllEncompassingFormHttpMessageConverter@5788}
6 = {MappingJackson2XmlHttpMessageConverter@5789}
7 = {MappingJackson2HttpMessageConverter@5790}
2.2 HttpMessageConverter 執行過程
上文中提到 Spring Boot 啟動時會在 messageConverters 集合中加載多個 HttpMessageConverter,到底執行那個呢?毫無疑問,執行肯定有三個過程:一是匹配對應的 HttpMessageConverter;二是執行 Handler;三是執行 HttpMessageConverter 響應結果。
HttpMessageConverter 的執行是在 AbstractMessageConverterMethodProcessor#writeWithMessageConverters 中執行的,這個方法很長,我們一點點來看。
2.2.1 匹配 HttpMessageConverter
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
// 1. 獲取客戶端可接受的類型 Accept: application/jsion
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 2. 服務端可以生成的所有 MediaType 類型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// 3. acceptableTypes 和 producibleTypes 比較,找出可用的 MediaType
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// 4. 如果有多個 MediaType 可用,選擇一個可用的返回
for (MediaType mediaType : mediaTypesToUse) {
// 只要是非 */* 就直接返回
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
}
客戶端可以傳兩個請求頭過來:
Accept: application/xml // 客戶端可接收的媒體類型
Content-Type: application/json // 客戶端請求的媒體類型
2.2.2 執行 HttpMessageConverter
// 遍歷 messageConverters,如果 converter 支持 selectedMediaType 則直接返回
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 1. canWrite 返回 true 則直接執行並結束循環
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
// 2. 拿到 handler 的執行結果
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage);
// 3. 執行對應的 genericConverter
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
return;
}
}
核心的步驟 converter.write(body, selectedMediaType, outputMessage) 將 POJO 轉換為 json 或 xml 後返回。
2.2.3 HttpMessageConverter
如果需要自定義 HttpMessageConverter,可以直接繼承 AbstractHttpMessageConverter 類,重寫 supports、readInternal、writeInternal 方法。
每天用心記錄一點點。內容也許不重要,但習慣很重要!
Spring Boot(一)REST