Springboot強(qiáng)大的類型轉(zhuǎn)換功能,你必須要掌握
環(huán)境:Springboot2.4.11
Spring3引入了一個(gè)core.convert包,它提供了一個(gè)通用類型轉(zhuǎn)換系統(tǒng)。系統(tǒng)定義一個(gè)SPI來(lái)實(shí)現(xiàn)類型轉(zhuǎn)換邏輯,定義一個(gè)API來(lái)在運(yùn)行時(shí)執(zhí)行類型轉(zhuǎn)換。在Spring容器中,你可以使用此系統(tǒng)作為PropertyEditor實(shí)現(xiàn)的替代方案,將外部化的bean屬性值字符串轉(zhuǎn)換為所需的屬性類型。你還可以在應(yīng)用程序中需要進(jìn)行類型轉(zhuǎn)換的任何位置使用公共API。
Converter SPI
實(shí)現(xiàn)類型轉(zhuǎn)換邏輯的SPI是簡(jiǎn)單且強(qiáng)類型的,如以下接口定義所示:
- package org.springframework.core.convert.converter;
 - public interface Converter<S, T> {
 - T convert(S source);
 - }
 
要?jiǎng)?chuàng)建自己的轉(zhuǎn)換器,需要實(shí)現(xiàn)converter接口,并將S參數(shù)化為要轉(zhuǎn)換的類型,將T參數(shù)化為要轉(zhuǎn)換的類型。如果需要將S的集合或數(shù)組轉(zhuǎn)換為T的集合或集合,還可以透明地應(yīng)用這樣的轉(zhuǎn)換器,前提是同時(shí)注冊(cè)了委托數(shù)組或集合轉(zhuǎn)換器(默認(rèn)情況下,DefaultConversionService會(huì)這樣做)。
對(duì)于每個(gè)轉(zhuǎn)換調(diào)用,保證源參數(shù)source不為null。如果轉(zhuǎn)換失敗,轉(zhuǎn)換器可能會(huì)拋出任何未檢查的異常。具體來(lái)說(shuō),它應(yīng)該拋出IllegalArgumentException以報(bào)告無(wú)效的源值。注意確保轉(zhuǎn)換器實(shí)現(xiàn)是線程安全的。
為了方便起見(jiàn),core.convert.support包中提供了幾種轉(zhuǎn)換器實(shí)現(xiàn)。其中包括從字符串到數(shù)字和其他常見(jiàn)類型的轉(zhuǎn)換器。下表顯示了StringToInteger類,它是典型的轉(zhuǎn)換器實(shí)現(xiàn):
- package org.springframework.core.convert.support;
 - final class StringToInteger implements Converter<String, Integer> {
 - public Integer convert(String source) {
 - return Integer.valueOf(source);
 - }
 - }
 
使用ConverterFactory
當(dāng)需要集中整個(gè)類層次結(jié)構(gòu)的轉(zhuǎn)換邏輯時(shí)(例如,從字符串轉(zhuǎn)換為枚舉對(duì)象時(shí)),可以實(shí)現(xiàn)ConverterFactory,如下例所示:
- package org.springframework.core.convert.converter;
 - public interface ConverterFactory<S, R> {
 - <T extends R> Converter<S, T> getConverter(Class<T> targetType);
 - }
 
將S參數(shù)化為要轉(zhuǎn)換的類型,將R參數(shù)化為定義可轉(zhuǎn)換為的類范圍的基類型。然后實(shí)現(xiàn)getConverter(類
以StringToEnumConverterFactory為例:
- package org.springframework.core.convert.support;
 - final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
 - public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
 - return new StringToEnumConverter(targetType);
 - }
 - private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
 - private Class<T> enumType;
 - public StringToEnumConverter(Class<T> enumType) {
 - this.enumType = enumType;
 - }
 - public T convert(String source) {
 - return (T) Enum.valueOf(this.enumType, source.trim());
 - }
 - }
 - }
 
自定義類型轉(zhuǎn)換
現(xiàn)在需要將接受的字符串轉(zhuǎn)換為Users對(duì)象
- public class Users {
 - private String name ;
 - private Integer age ;
 - }
 
接口
- @GetMapping("/convert2")
 - public Object convert2(Users users) {
 - return users ;
 - }
 
調(diào)用接口

如上,通過(guò)get方式users的參數(shù)通過(guò)逗號(hào)分割。接下來(lái)就是寫類型轉(zhuǎn)換器了。
- @SuppressWarnings({"rawtypes", "unchecked"})
 - public class UsersConverterFactory implements ConverterFactory<String, Users> {
 - @Override
 - public <T extends Users> Converter<String, T> getConverter(Class<T> targetType) {
 - return new StringToUsersConverter() ;
 - }
 - private final class StringToUsersConverter<T extends Users> implements Converter<String, Users> {
 - public Users convert(String source) {
 - if (source == null || source.trim().length() == 0) {
 - return null ;
 - }
 - Users user = new Users() ;
 - // 下面做簡(jiǎn)單處理,不做校驗(yàn)
 - String[] values = source.split(",") ;
 - user.setName(values[0]) ;
 - user.setAge(Integer.parseInt(values[1]));
 - return user ;
 - }
 - }
 - }
 
注冊(cè)類型轉(zhuǎn)換器
- @Configuration
 - public class WebConfig implements WebMvcConfigurer {
 - @Override
 - public void addFormatters(FormatterRegistry registry) {
 - registry.addConverterFactory(new UsersConverterFactory()) ;
 - }
 - }
 
編程方式使用類型轉(zhuǎn)換器
要以編程方式使用ConversionService實(shí)例,可以像對(duì)任何其他bean一樣向其注入引用。以下示例顯示了如何執(zhí)行此操作:
我們使用系統(tǒng)內(nèi)置的類型轉(zhuǎn)換器:字符串類型轉(zhuǎn)枚舉類型
- public enum PayStatus {
 - START, PROCESS, COMPLETE
 - }
 
- @RestController
 - @RequestMapping("/users")
 - public class UsersController {
 - @Resource
 - private ConversionService cs ;
 - @GetMapping("/convert")
 - public Object convert(String status) {
 - boolean canConvert = cs.canConvert(String.class, PayStatus.class) ;
 - return canConvert ? cs.convert(status, PayStatus.class) : "UNKNOW";
 - }
 - }
 
先判斷是否能夠轉(zhuǎn)換,其實(shí)就是判斷有沒(méi)有從source到target的類型轉(zhuǎn)換器存在。
類型轉(zhuǎn)換的實(shí)現(xiàn)原理
以自定義類型轉(zhuǎn)換器為例
SpringMVC在進(jìn)行接口調(diào)用是會(huì)執(zhí)行相應(yīng)的參數(shù)解析,確定了參數(shù)解析器后會(huì)執(zhí)行轉(zhuǎn)換服務(wù)。
查找參數(shù)解析器
查找合適的HandlerMethodArgumentResolver
- public class InvocableHandlerMethod extends HandlerMethod {
 - protected Object[] getMethodArgumentValues(...) throws Exception {
 - // 查找合適的參數(shù)解析器(本例應(yīng)用的是ServletModelAttributeMethodProcessor)
 - if (!this.resolvers.supportsParameter(parameter)) {
 - throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
 - }
 - try {
 - args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
 - }
 - }
 - }
 
解析參數(shù)
執(zhí)行
- public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver {
 - public final Object resolveArgument(...) {
 - attribute = createAttribute(name, parameter, binderFactory, webRequest);
 - }
 - }
 - public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
 - protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
 - // 這里得到的是原始值
 - String value = getRequestValueForAttribute(attributeName, request);
 - if (value != null) {
 - Object attribute = createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);
 - if (attribute != null) {
 - return attribute;
 - }
 - }
 - return super.createAttribute(attributeName, parameter, binderFactory, request);
 - }
 - protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
 - DataBinder binder = binderFactory.createBinder(request, null, attributeName);
 - // ConversionService對(duì)象是在容器啟動(dòng)的時(shí)候就初始化好的
 - // 在WebMvcAutoConfiguration#mvcConversionService方法中初始化。
 - ConversionService conversionService = binder.getConversionService();
 - if (conversionService != null) {
 - TypeDescriptor source = TypeDescriptor.valueOf(String.class);
 - TypeDescriptor target = new TypeDescriptor(parameter);
 - // 判斷是否有合適的類型轉(zhuǎn)換器
 - if (conversionService.canConvert(source, target)) {
 - // 此方法中進(jìn)行類型的轉(zhuǎn)換
 - return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
 - }
 - }
 - return null;
 - }
 - }
 















 
 
 










 
 
 
 