1. 程式人生 > >SpringMVC(四)自定義引數轉換規則

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請求中讀取資料,然後通過三種介面來進行各類引數轉換,者三種介面是ConverterFomatterGenericConverter。在SpringMVC的機制中這三種介面的實現類都採用了註冊機的機制,預設的情況下SpringMVC已經在註冊機內註冊了許多的轉換器,這樣就可以實現大部分的資料型別的轉換,所以在大部分的情況下下無需開發者再提供轉換器。當下需要自定義轉換規則時,只需要在註冊機上註冊自己的轉換器就可以了。

實際上,WebDataBinder機制還有一個重要的功能,那就是驗證轉換結果。

SpringMVC\SpringMVC處理器HTTP請求體轉換流程圖
可以看到控制器的引數是處理器通過ConverterFormatterGenericConverter這三個介面轉換出來的。

  • Converter:普通的轉換器,例如有一個Integer型別的控制器引數,而從HTTP對應的為字串,對應的Convert就會將字串轉換為Integer型別。
  • Formatter:格式化轉換器,類似日期字串就是通過它按照約定的格式轉換為日期。
  • GenericConverter:將HTTP引數轉換為陣列。

轉換器註冊

對於資料型別轉換,SpringMVC提供了一個服務機制去管理,它就是ConversionService介面。在預設情況下下,會使用這個介面的子類DefaultFormattingConversionService物件來管理這些轉換器類。

ConversionService轉換機制設計
可以看出,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的初始化中,會將對應使用者自定義的ConvertFormatterGenericConverter的實現類所傳就的spring bean自動地註冊到DefaultFormattingConversionService物件中。這樣對於開發者,只需要自定義ConvertFormatterGenericConverter的介面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;
    }

訪問:http://localhost:8080/my/converterList?personList=1-xiaohuahua-beautiful,2-xiaoming-shuaige,3-daxiong-jinshi

這裡引數使用了一個個逗號分隔,StringToCollectionConverter在處理時就通過逗號分隔,然後通過之前自定義的轉換器StringToPerson將其變為使用者物件,在組成一個列表List傳遞給控制器。