SpringMVC(四)自定義引數轉換規則
SpringMVC(四)自定義引數轉換規則
處理器獲取引數邏輯
當一個請求到來時,在處理器執行的過程中,它首先會從HTTP請求和上下文環境來得到引數,如果是簡易的引數它會以簡單的轉換器進行轉換,而這些簡單的轉換器是SpringMVC自身已經提供了的。但是如果轉換HTTP請求體(Body),它就會呼叫HttpMessageConverter
介面的方法對請求體的資訊進行轉換,首先它會判斷能否對請求體進行轉換,如果可以就會將其轉換為Java型別。
HttpMessageConverter介面原始碼
package org.springframework.http.converter;
public interface HttpMessageConverter<T> {
//是否可讀,其中clazz為Java型別,mediaType為http請求型別
boolean canRead(Class<?> var1, @Nullable MediaType var2);
//判斷clazz型別是否能夠轉換為mediaType媒體型別
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
//可支援的媒體型別列表
List<MediaType> getSupportedMediaTypes();
//當canRead()驗證通過後,讀入http請求資訊
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
//當canWrite()方法驗證通過後,寫入響應
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
上面的HttpMessageConverter
介面只是將HTTP請求體轉換為對應的Java物件,而對於HTTP引數和其他內容,還沒有討論。例如,以性別引數來說,前端可能傳遞給控制器的是一個整數,而控制器引數卻是一個列舉,這樣就需要提供自定義的引數轉換規則。
在SpringMVC中,是通過WebDataBinder
機制來獲取引數的,它的主要作用是解析http請求的上下文,然後再控制器的呼叫之前轉換引數並且提供驗證的功能,為呼叫控制器的方法做準備。處理器會從HTTP請求中讀取資料,然後通過三種介面來進行各類引數轉換,者三種介面是Converter
,Fomatter
,GenericConverter
。在SpringMVC的機制中這三種介面的實現類都採用了註冊機的機制,預設的情況下SpringMVC已經在註冊機內註冊了許多的轉換器,這樣就可以實現大部分的資料型別的轉換,所以在大部分的情況下下無需開發者再提供轉換器。當下需要自定義轉換規則時,只需要在註冊機上註冊自己的轉換器就可以了。
實際上,WebDataBinder
機制還有一個重要的功能,那就是驗證轉換結果。
可以看到控制器的引數是處理器通過Converter
、Formatter
和GenericConverter
這三個介面轉換出來的。
- Converter:普通的轉換器,例如有一個Integer型別的控制器引數,而從HTTP對應的為字串,對應的Convert就會將字串轉換為Integer型別。
- Formatter:格式化轉換器,類似日期字串就是通過它按照約定的格式轉換為日期。
- GenericConverter:將HTTP引數轉換為陣列。
轉換器註冊
對於資料型別轉換,SpringMVC提供了一個服務機制去管理,它就是ConversionService
介面。在預設情況下下,會使用這個介面的子類DefaultFormattingConversionService
物件來管理這些轉換器類。
可以看出,Converter、Formatter和GenericConverter可以通過註冊機介面進行註冊,這樣處理器就可以獲取對應的轉換器來實現引數的轉換。
上面討論的是普通的SpringMVC的引數轉換規則,而在spring boot中還提供了特殊的機制來管理這些轉換器。Spring Boot的自動配置類WebMvcAutoConfiguration
還定義了一個內部類WebMvcAutoConfigurationAdapter
,程式碼如下
WebMvcAutoConfigurationAdapter原始碼
public void addFormatters(FormatterRegistry registry) {
//在IoC容器中獲取Converter型別的Bean,然後獲得迭代器
Iterator var2 = this.getBeansOfType(Converter.class).iterator();
//遍歷迭代器,然後註冊到服務類中
while(var2.hasNext()) {
Converter<?, ?> converter = (Converter)var2.next();
registry.addConverter(converter);
}
//在IoC容器中獲取GenericConverter型別的Bean,然後獲得迭代器
var2 = this.getBeansOfType(GenericConverter.class).iterator();
//遍歷迭代器,然後註冊到服務類中
while(var2.hasNext()) {
GenericConverter converter = (GenericConverter)var2.next();
registry.addConverter(converter);
}
//在IoC容器中獲取Formatter型別的Bean,然後獲得迭代器
var2 = this.getBeansOfType(Formatter.class).iterator();
//遍歷迭代器,然後註冊到服務類中
while(var2.hasNext()) {
Formatter<?> formatter = (Formatter)var2.next();
registry.addFormatter(formatter);
}
}
可以看到,在spring boot的初始化中,會將對應使用者自定義的Convert
、Formatter
和GenericConverter
的實現類所傳就的spring bean自動地註冊到DefaultFormattingConversionService
物件中。這樣對於開發者,只需要自定義Convert
、Formatter
和GenericConverter
的介面Bean,spring boot就通過這個方法將它們註冊到ConversionService
物件中。其中,格式化Formatter
介面在實際開發中使用率較低。
一對一轉換器(Converter)
Converter是一對一轉換器,也就是從一種型別轉換為另外一種型別,其介面定義十分簡單。如下
Converter介面原始碼
package org.springframework.core.convert.converter;
import org.springframework.lang.Nullable;
@FunctionalInterface
public interface Converter<S, T> {
//轉換方法,S代表原型別,T代表目標型別
@Nullable
T convert(S var1);
}
這個介面型別有原型別(S)和目標型別(T)兩種,它們通過convert方法進行轉換。
例如,http的型別為字串(String)型,而控制器引數為Long型,那麼就可以通過Spring內部提供的StringToNumber進行轉換。
示例:假設前端要傳遞一個使用者資訊,這個使用者資訊的格式是{id}-{personName}-{note}
,而控制器的引數是Person物件。這裡需要一個從String轉換為Person的轉換器。
package com.lay.mvc.converter;
import com.lay.mvc.entity.Person;
import org.springframework.core.convert.converter.Converter;
/**
* @Description:自定義字串使用者轉換器
* @Author: lay
* @Date: Created in 16:26 2018/11/13
* @Modified By:IntelliJ IDEA
*/
@Component
public class StringToPersonConverter implements Converter<String, Person> {
//轉換方法
@Override
public Person convert(String s) {
Person person=new Person();
String[] strArr=s.split("-");
Long id=Long.parseLong(strArr[0]);
String personName=strArr[1];
String note=strArr[2];
person.setId(id);
person.setPersonName(personName);
person.setNote(note);
return person;
}
}
這裡類標註了註解@Component
,並且實現了Converter
介面,這樣Spring就會將這個類掃描並且裝配到IoC容器中。
控制器驗證
/**
*
* @Description: 測試轉換器
* @param: [person]
* @return: com.lay.mvc.entity.Person
* @auther: lay
* @date: 16:37 2018/11/13
*/
@GetMapping("/converter")
@ResponseBody
public Person getPersonByConverter(Person person){
return person;
}
測試http://localhost:8080/my/converter?person=1-xiaohuahua-beautiful
GenericConverter集合和陣列轉換
GenericConverter
是陣列轉換器。因為SpringMVC自身提供了一些陣列轉換器,需要自定義的並不多,所以這裡只介紹SpringMVC自定義的陣列轉換器。
假設需要同時新增多個使用者,這樣便需要傳遞一個使用者列表(List)給控制器。此時SpringMVC會使用StringToCellectionConverter
轉換它,這個類實現了GenericConverter
介面,並且是SpringMVC內部已經註冊的資料轉換器。它首先會把字串用逗號分隔稱為一個個的子字串,然後根據原型別為String
,目標型別泛型為Person
類,找到對應的Converter
進行轉換,將字串轉換為Person物件。
/**
*
* @Description: 測試陣列轉換器
* @param: List
* @return: java.util.List<com.lay.mvc.entity.Person>
* @auther: lay
* @date: 16:47 2018/11/13
*/
@GetMapping("/converterList")
@ResponseBody
public List<Person> personList(List<Person> personList){
return personList;
}
這裡引數使用了一個個逗號分隔,StringToCollectionConverter在處理時就通過逗號分隔,然後通過之前自定義的轉換器StringToPerson將其變為使用者物件,在組成一個列表List傳遞給控制器。