最全面闡述WebDataBinder理解Spring的數據綁定

每篇一句

不要總問低級的問題,這樣的人要麼懶,不肯意上網搜索,要麼笨,一點獨立思考的能力都沒有java

相關閱讀

【小家Spring】聊聊Spring中的數據綁定 --- DataBinder本尊(源碼分析)web

【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用spring

【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor編程


對Spring感興趣可掃碼加入wx羣:`Java高工、架構師3羣`(文末有二維碼)

前言

上篇文章聊了DataBinder,這篇文章繼續聊聊實際應用中的數據綁定主菜WebDataBinderjson

在上文的基礎上,咱們先來看看DataBinder它的繼承樹:
在這裏插入圖片描述
從繼承樹中能夠看到,web環境統一對數據綁定DataBinder進行了加強。數組

畢竟數據綁定的實際應用場景:不誇張的說99%狀況都是web環境~架構

WebDataBinder

它的做用就是從web request裏(注意:這裏指的web請求,並不必定就是ServletRequest請求喲~)把web請求的parameters綁定到JavaBean上~app

Controller方法的參數類型能夠是基本類型,也能夠是封裝後的普通Java類型。若這個普通Java類型沒有聲明任何註解,則意味着它的每個屬性都須要到Request中去查找對應的請求參數。編輯器

// @since 1.2
public class WebDataBinder extends DataBinder {

    // 此字段意思是:字段標記  好比name -> _name
    // 這對於HTML複選框和選擇選項特別有用。
    public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
    // !符號是處理默認值的,提供一個默認值代替空值~~~
    public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
    
    @Nullable
    private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
    @Nullable
    private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
    // 默認也會綁定空的文件流~
    private boolean bindEmptyMultipartFiles = true;

    // 徹底沿用父類的兩個構造~~~
    public WebDataBinder(@Nullable Object target) {
        super(target);
    }
    public WebDataBinder(@Nullable Object target, String objectName) {
        super(target, objectName);
    }

    ... //  省略get/set
    // 在父類的基礎上,增長了對_和!的處理~~~
    @Override
    protected void doBind(MutablePropertyValues mpvs) {
        checkFieldDefaults(mpvs);
        checkFieldMarkers(mpvs);
        super.doBind(mpvs);
    }

    protected void checkFieldDefaults(MutablePropertyValues mpvs) {
        String fieldDefaultPrefix = getFieldDefaultPrefix();
        if (fieldDefaultPrefix != null) {
            PropertyValue[] pvArray = mpvs.getPropertyValues();
            for (PropertyValue pv : pvArray) {

                // 若你給定的PropertyValue的屬性名確實是以!打頭的  那就作處理以下:
                // 若是JavaBean的該屬性可寫 && mpvs不存在去掉!後的同名屬性,那就添加進來表示後續可使用了(畢竟是默認值,沒有精確匹配的高的)
                // 而後把帶!的給移除掉(由於默認值以已經轉正了~~~)
                // 其實這裏就是說你可使用!來給個默認值。好比!name表示若找不到name這個屬性的時,就取它的值~~~
                // 也就是說你request裏如有穿!name保底,也就不怕出現null值啦~
                if (pv.getName().startsWith(fieldDefaultPrefix)) {
                    String field = pv.getName().substring(fieldDefaultPrefix.length());
                    if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
                        mpvs.add(field, pv.getValue());
                    }
                    mpvs.removePropertyValue(pv);
                }
            }
        }
    }

    // 處理_的步驟
    // 若傳入的字段以_打頭
    // JavaBean的這個屬性可寫 && mpvs木有去掉_後的屬性名字
    // getEmptyValue(field, fieldType)就是根據Type類型給定默認值。
    // 好比Boolean類型默認給false,數組給空數組[],集合給空集合,Map給空map  能夠參考此類:CollectionFactory
    // 固然,這一切都是創建在你傳的屬性值是以_打頭的基礎上的,Spring纔會默認幫你處理這些默認值
    protected void checkFieldMarkers(MutablePropertyValues mpvs) {
        String fieldMarkerPrefix = getFieldMarkerPrefix();
        if (fieldMarkerPrefix != null) {
            PropertyValue[] pvArray = mpvs.getPropertyValues();
            for (PropertyValue pv : pvArray) {
                if (pv.getName().startsWith(fieldMarkerPrefix)) {
                    String field = pv.getName().substring(fieldMarkerPrefix.length());
                    if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
                        Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
                        mpvs.add(field, getEmptyValue(field, fieldType));
                    }
                    mpvs.removePropertyValue(pv);
                }
            }
        }
    }

    // @since 5.0
    @Nullable
    public Object getEmptyValue(Class<?> fieldType) {
        try {
            if (boolean.class == fieldType || Boolean.class == fieldType) {
                // Special handling of boolean property.
                return Boolean.FALSE;
            } else if (fieldType.isArray()) {
                // Special handling of array property.
                return Array.newInstance(fieldType.getComponentType(), 0);
            } else if (Collection.class.isAssignableFrom(fieldType)) {
                return CollectionFactory.createCollection(fieldType, 0);
            } else if (Map.class.isAssignableFrom(fieldType)) {
                return CollectionFactory.createMap(fieldType, 0);
            }
        } catch (IllegalArgumentException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
            }
        }
        // 若不在這幾大類型內,就返回默認值null唄~~~
        // 但須要說明的是,若你是簡單類型好比int,
        // Default value: null. 
        return null;
    }

    // 單獨提供的方法,用於綁定org.springframework.web.multipart.MultipartFile類型的數據到JavaBean屬性上~
    // 顯然默認是容許MultipartFile做爲Bean一個屬性  參與綁定的
    // Map<String, List<MultipartFile>>它的key,通常來講就是文件們啦~
    protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
        multipartFiles.forEach((key, values) -> {
            if (values.size() == 1) {
                MultipartFile value = values.get(0);
                if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
                    mpvs.add(key, value);
                }
            }
            else {
                mpvs.add(key, values);
            }
        });
    }
}

單從WebDataBinder來講,它對父類進行了加強,提供的加強能力以下:ide

  1. 支持對屬性名以_打頭的默認值處理(自動擋,可以自動處理全部的Bool、Collection、Map等)
  2. 支持對屬性名以!打頭的默認值處理(手動檔,須要手動給某個屬性賦默認值,本身控制的靈活性很高)
  3. 提供方法,支持把MultipartFile綁定到JavaBean的屬性上~

Demo示例

下面以一個示例來演示使用它加強的這些功能:

@Getter
@Setter
@ToString
public class Person {

    public String name;
    public Integer age;

    // 基本數據類型
    public Boolean flag;
    public int index;
    public List<String> list;
    public Map<String, String> map;

}

演示使用!手動精確控制字段的默認值:

public static void main(String[] args) {
        Person person = new Person();
        WebDataBinder binder = new WebDataBinder(person, "person");

        // 設置屬性(此處演示一下默認值)
        MutablePropertyValues pvs = new MutablePropertyValues();

        // 使用!來模擬各個字段手動指定默認值
        //pvs.add("name", "fsx");
        pvs.add("!name", "不知火舞");
        pvs.add("age", 18);
        pvs.add("!age", 10); // 上面有確切的值了,默認值不會再生效

        binder.bind(pvs);
        System.out.println(person);
    }

打印輸出(符合預期):

Person(name=null, age=null, flag=false, index=0, list=[], map={})

請用此打印結果對比一下上面的結果,你是會有不少發現,好比可以發現基本類型的默認值就是它本身
另外一個很顯然的道理:若你啥都不作特殊處理,包裝類型默認值那鐵定都是null了~

瞭解了WebDataBinder後,繼續看看它的一個重要子類ServletRequestDataBinder

ServletRequestDataBinder

前面說了這麼多,親有沒有發現還木有聊到過咱們最爲常見的Web場景API:javax.servlet.ServletRequest。本類從命名上就知道,它就是爲此而生。

它的目標就是:data binding from servlet request parameters to JavaBeans, including support for multipart files.從Servlet Request裏把參數綁定到JavaBean裏,支持multipart。

備註:到此類爲止就已經把web請求限定爲了Servlet Request,和Servlet規範強綁定了。

public class ServletRequestDataBinder extends WebDataBinder {
    ... // 沿用父類構造
    // 注意這個可不是父類的方法,是本類加強的~~~~意思就是kv都從request裏來~~固然內部仍是適配成了一個MutablePropertyValues
    public void bind(ServletRequest request) {
        // 內部最核心方法是它:WebUtils.getParametersStartingWith()  把request參數轉換成一個Map
        // request.getParameterNames()
        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
    
        // 調用父類的bindMultipart方法,把MultipartFile都放進MutablePropertyValues裏去~~~
        if (multipartRequest != null) {
            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
        }
        // 這個方法是本類流出來的一個擴展點~~~子類能夠複寫此方法本身往裏繼續添加
        // 好比ExtendedServletRequestDataBinder它就複寫了這個方法,進行了加強(下面會說)  支持到了uriTemplateVariables的綁定
        addBindValues(mpvs, request);
        doBind(mpvs);
    }

    // 這個方法和父類的close方法相似,不多直接調用
    public void closeNoCatch() throws ServletRequestBindingException {
        if (getBindingResult().hasErrors()) {
            throw new ServletRequestBindingException("Errors binding onto object '" + getBindingResult().getObjectName() + "'", new BindException(getBindingResult()));
        }
    }
}

下面就以MockHttpServletRequest爲例做爲Web 請求實體,演示一個使用的小Demo。說明:MockHttpServletRequest它是HttpServletRequest的實現類~

Demo示例

public static void main(String[] args) {
        Person person = new Person();
        ServletRequestDataBinder binder = new ServletRequestDataBinder(person, "person");

        // 構造參數,此處就不用MutablePropertyValues,以HttpServletRequest的實現類MockHttpServletRequest爲例吧
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模擬請求參數
        request.addParameter("name", "fsx");
        request.addParameter("age", "18");

        // flag不只僅能夠用true/false  用0和1也是能夠的?
        request.addParameter("flag", "1");

        // 設置多值的
        request.addParameter("list", "4", "2", "3", "1");
        // 給map賦值(Json串)
        // request.addParameter("map", "{'key1':'value1','key2':'value2'}"); // 這樣可不行
        request.addParameter("map['key1']", "value1");
        request.addParameter("map['key2']", "value2");

        //// 一次性設置多個值(傳入Map)
        //request.setParameters(new HashMap<String, Object>() {{
        //    put("name", "fsx");
        //    put("age", "18");
        //}});

        binder.bind(request);
        System.out.println(person);
    }

打印輸出:

Person(name=fsx, age=18, flag=true, index=0, list=[4, 2, 3, 1], map={key1=value1, key2=value2})

完美。

思考題:小夥伴能夠思考爲什麼給Map屬性傳值是如上,而不是value寫個json就行呢?

ExtendedServletRequestDataBinder

此類代碼很少但也不容小覷,它是對ServletRequestDataBinder的一個加強,它用於把URI template variables參數添加進來用於綁定。它會去從request的HandlerMapping.class.getName() + ".uriTemplateVariables";這個屬性裏查找到值出來用於綁定~~~

好比咱們熟悉的@PathVariable它就和這相關:它負責把參數從url模版中解析出來,而後放在attr上,最後交給ExtendedServletRequestDataBinder進行綁定~~~

介於此:我以爲它還有一個做用,就是定製咱們全局屬性變量用於綁定~

向此屬性放置值的地方是:AbstractUrlHandlerMapping.lookupHandler() --> chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); --> preHandle()方法 -> exposeUriTemplateVariables(this.uriTemplateVariables, request); -> request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);

// @since 3.1
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
    ... // 沿用父類構造

    //本類的惟一方法
    @Override
    @SuppressWarnings("unchecked")
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        // 它的值是:HandlerMapping.class.getName() + ".uriTemplateVariables";
        String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;

        // 注意:此處是attr,而不是parameter
        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
        if (uriVars != null) {
            uriVars.forEach((name, value) -> {
                
                // 若已經存在確切的key了,不會覆蓋~~~~
                if (mpvs.contains(name)) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Skipping URI variable '" + name + "' because request contains bind value with same name.");
                    }
                } else {
                    mpvs.addPropertyValue(name, value);
                }
            });
        }
    }
}

可見,經過它咱們亦能夠很方便的作到在每一個ServletRequest提供一份共用的模版屬性們,供以綁定~

此類基本都沿用父類的功能,比較簡單,此處就不寫Demo了(Demo請參照父類)~

說明:ServletRequestDataBinder通常不會直接使用,而是使用更強的子類ExtendedServletRequestDataBinder

WebExchangeDataBinder

它是Spring5.0後提供的,對Reactive編程的Mono數據綁定提供支持,所以暫略~

data binding from URL query params or form data in the request data to Java objects

MapDataBinder

它位於org.springframework.data.web是和Spring-Data相關,專門用於處理targetMap<String, Object>類型的目標對象的綁定,它並不是一個public類~

它用的屬性訪問器是MapPropertyAccessor:一個繼承自AbstractPropertyAccessor的私有靜態內部類~(也支持到了SpEL哦)

WebRequestDataBinder

它是用於處理Spring本身定義的org.springframework.web.context.request.WebRequest的,旨在處理和容器無關的web請求數據綁定,有機會詳述到這塊的時候,再詳細說~


如何註冊本身的PropertyEditor來實現自定義類型數據綁定?

經過前面的分析咱們知道了,數據綁定這一塊最終會依託於PropertyEditor來實現具體屬性值的轉換(畢竟request傳進來的都是字符串嘛~)

通常來講,像String, int, long會自動綁定到參數都是可以自動完成綁定的,由於前面有說,默認狀況下Spring是給咱們註冊了N多個解析器的:

public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {

    @Nullable
    private Map<Class<?>, PropertyEditor> defaultEditors;

    private void createDefaultEditors() {
        this.defaultEditors = new HashMap<>(64);

        // Simple editors, without parameterization capabilities.
        // The JDK does not contain a default editor for any of these target types.
        this.defaultEditors.put(Charset.class, new CharsetEditor());
        this.defaultEditors.put(Class.class, new ClassEditor());
        ...
        // Default instances of collection editors.
        // Can be overridden by registering custom instances of those as custom editors.
        this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
        this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
        this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
        this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
        this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
        ...
        // 這裏就部所有枚舉出來了
    }
}

雖然默認註冊支持的Editor衆多,可是依舊發現它並無對Date類型、以及Jsr310提供的各類事件、日期類型的轉換(固然也包括咱們的自定義類型)。
所以我相信小夥伴都遇到過這樣的痛點:Date、LocalDate等類型使用自動綁定老不方便了,而且還常常傻傻搞不清楚。因此最終不少都無奈選擇了語義不是很是清晰的時間戳來傳遞

演示Date類型的數據綁定Demo:

@Getter
@Setter
@ToString
public class Person {

    public String name;
    public Integer age;

    // 以Date類型爲示例
    private Date start;
    private Date end;
    private Date endTest;

}

    public static void main(String[] args) {
        Person person = new Person();
        DataBinder binder = new DataBinder(person, "person");

        // 設置屬性
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // 事件類型綁定
        pvs.add("start", new Date());
        pvs.add("end", "2019-07-20");
        // 試用試用標準的事件日期字符串形式~
        pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");


        binder.bind(pvs);
        System.out.println(person);
    }

打印輸出:

Person(name=fsx, age=null, start=Sat Jul 20 11:05:29 CST 2019, end=null, endTest=Sun Jul 21 01:00:22 CST 2019)

結果是符合我預期的:start有值,end沒有,endTest卻有值。
可能小夥伴對start、end均可以理解,最詫異的是endTest爲什麼會有值呢???
此處我簡單解釋一下處理步驟:

  1. BeanWrapper調用setPropertyValue()給屬性賦值,傳入的value值都會交給convertForProperty()方法根據get方法的返回值類型進行轉換~(好比此處爲Date類型)
  2. 委託給this.typeConverterDelegate.convertIfNecessary進行類型轉換(好比此處爲string->Date類型)
  3. this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);找到一個合適的PropertyEditor(顯然此處咱們沒有自定義Custom處理Date的PropertyEditor,返回null)
  4. 回退到使用ConversionService,顯然此處咱們也沒有設置,返回null
  5. 回退到使用默認的editor = findDefaultEditor(requiredType);(注意:此處只根據類型去找了,由於上面說了默認不處理了Date,因此也是返回null)
  6. 最終的最終,回退到Spring對Array、Collection、Map的默認值處理問題,最終如果String類型,都會調用BeanUtils.instantiateClass(strCtor, convertedValue)也就是有參構造進行初始化~~~(請注意這必須是String類型纔有的權利)
    1. 因此本例中,到最後一步就至關於new Date("Sat Jul 20 11:00:22 CST 2019")由於該字符串是標準的時間日期串,因此是闊儀的,也就是endTest是能被正常賦值的~

經過這個簡單的步驟分析,解釋了爲什麼end沒值,endTest有值了。
其實經過回退到的最後一步處理,咱們還能夠對此作巧妙的應用。好比我給出以下的一個巧用例子:

@Getter
@Setter
@ToString
public class Person {
    private String name;
    // 備註:child是有有一個入參的構造器的
    private Child child;
}

@Getter
@Setter
@ToString
public class Child {
    private String name;
    private Integer age;
    public Child() {
    }
    public Child(String name) {
        this.name = name;
    }
}

    public static void main(String[] args) {
        Person person = new Person();
        DataBinder binder = new DataBinder(person, "person");

        // 設置屬性
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // 給child賦值,其實也能夠傳一個字符串就好了 很是的方便   Spring會自動給咱們new對象
        pvs.add("child", "fsx-son");
        
        binder.bind(pvs);
        System.out.println(person);
    }

打印輸出:

Person(name=fsx, child=Child(name=fsx-son, age=null))

完美。


廢話很少說,下面我經過自定義屬性編輯器的手段,來讓可以支持處理上面咱們傳入2019-07-20這種非標準的時間字符串

咱們知道DataBinder自己就是個PropertyEditorRegistry,所以我只須要本身註冊一個自定義的PropertyEditor便可:

一、經過繼承PropertyEditorSupport實現一個本身的處理Date的編輯器:

public class MyDatePropertyEditor extends PropertyEditorSupport {

    private static final String PATTERN = "yyyy-MM-dd";

    @Override
    public String getAsText() {
        Date date = (Date) super.getValue();
        return new SimpleDateFormat(PATTERN).format(date);
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        try {
            super.setValue(new SimpleDateFormat(PATTERN).parse(text));
        } catch (ParseException e) {
            System.out.println("ParseException....................");
        }
    }
}

二、註冊進DataBinder並運行

public static void main(String[] args) {
        Person person = new Person();
        DataBinder binder = new DataBinder(person, "person");
        binder.registerCustomEditor(Date.class, new MyDatePropertyEditor());
        //binder.registerCustomEditor(Date.class, "end", new MyDatePropertyEditor());

        // 設置屬性
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // 事件類型綁定
        pvs.add("start", new Date());
        pvs.add("end", "2019-07-20");
        // 試用試用標準的事件日期字符串形式~
        pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");


        binder.bind(pvs);
        System.out.println(person);
    }

運行打印以下:

ParseException....................
Person(name=fsx, age=null, start=Sat Jul 20 11:41:49 CST 2019, end=Sat Jul 20 00:00:00 CST 2019, endTest=null)

結果符合預期。不過對此結果我仍舊拋出以下兩個問題供小夥伴自行思考:
一、輸出了ParseException....................
二、start有值,endTest值卻爲null了

理解這塊最後我想說:經過自定義編輯器,咱們能夠很是自由、高度定製化的完成自定義類型的封裝,可使得咱們的Controller更加容錯、更加智能、更加簡潔。有興趣的能夠運用此塊知識,自行實踐~

WebBindingInitializer和WebDataBinderFactory

WebBindingInitializer

WebBindingInitializer:實現此接口重寫initBinder方法註冊的屬性編輯器是全局的屬性編輯器,對全部的Controller都有效。

能夠簡單粗暴的理解爲:WebBindingInitializer爲編碼方式,@InitBinder爲註解方式(固然註解方式還能控制到只對當前Controller有效,實現更細粒度的控制)

觀察發現,Spring對這個接口的命名頗有意思:它用的Binding正在進行時態~

// @since 2.5   Spring在初始化WebDataBinder時候的回調接口,給調用者自定義~
public interface WebBindingInitializer {

    // @since 5.0
    void initBinder(WebDataBinder binder);

    // @deprecated as of 5.0 in favor of {@link #initBinder(WebDataBinder)}
    @Deprecated
    default void initBinder(WebDataBinder binder, WebRequest request) {
        initBinder(binder);
    }

}

此接口它的內建惟一實現類爲:ConfigurableWebBindingInitializer,若你本身想要擴展,建議繼承它~

public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
    private boolean autoGrowNestedPaths = true;
    private boolean directFieldAccess = false; // 顯然這裏是false

    // 下面這些參數,不就是WebDataBinder那些能夠配置的屬性們嗎?
    @Nullable
    private MessageCodesResolver messageCodesResolver;
    @Nullable
    private BindingErrorProcessor bindingErrorProcessor;
    @Nullable
    private Validator validator;
    @Nullable
    private ConversionService conversionService;
    // 此處使用的PropertyEditorRegistrar來管理的,最終都會被註冊進PropertyEditorRegistry嘛
    @Nullable
    private PropertyEditorRegistrar[] propertyEditorRegistrars;

    ... //  省略全部get/set
    
    // 它作的事無非就是把配置的值都放進去而已~~
    @Override
    public void initBinder(WebDataBinder binder) {
        binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
        if (this.directFieldAccess) {
            binder.initDirectFieldAccess();
        }
        if (this.messageCodesResolver != null) {
            binder.setMessageCodesResolver(this.messageCodesResolver);
        }
        if (this.bindingErrorProcessor != null) {
            binder.setBindingErrorProcessor(this.bindingErrorProcessor);
        }
        // 能夠看到對校驗器這塊  內部仍是作了容錯的
        if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
            binder.setValidator(this.validator);
        }
        if (this.conversionService != null) {
            binder.setConversionService(this.conversionService);
        }
        if (this.propertyEditorRegistrars != null) {
            for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
                propertyEditorRegistrar.registerCustomEditors(binder);
            }
        }
    }
}

此實現類主要是提供了一些可配置項,方便使用。注意:此接口通常不直接使用,而是結合InitBinderDataBinderFactoryWebDataBinderFactory等一塊兒使用~

WebDataBinderFactory

顧名思義它就是來創造一個WebDataBinder的工廠。

// @since 3.1   注意:WebDataBinder 但是1.2就有了~
public interface WebDataBinderFactory {
    // 此處使用的是Spring本身的NativeWebRequest   後面兩個參數就不解釋了
    WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}

它的繼承樹以下:
在這裏插入圖片描述

DefaultDataBinderFactory

public class DefaultDataBinderFactory implements WebDataBinderFactory {
    @Nullable
    private final WebBindingInitializer initializer;
    // 注意:這是惟一構造函數
    public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
        this.initializer = initializer;
    }

    // 實現接口的方法
    @Override
    @SuppressWarnings("deprecation")
    public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {

        WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
        
        // 可見WebDataBinder 建立好後,此處就會回調(只有一個)
        if (this.initializer != null) {
            this.initializer.initBinder(dataBinder, webRequest);
        }
        // 空方法 子類去實現,好比InitBinderDataBinderFactory實現了詞方法
        initBinder(dataBinder, webRequest);
        return dataBinder;
    }

    //  子類能夠複寫,默認實現是WebRequestDataBinder
    // 好比子類ServletRequestDataBinderFactory就複寫了,使用的new ExtendedServletRequestDataBinder(target, objectName)
    protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception 
        return new WebRequestDataBinder(target, objectName);
    }
}

按照Spring一向的設計,本方法實現了模板動做,子類只須要複寫對應的動做便可達到效果。

InitBinderDataBinderFactory

它繼承自DefaultDataBinderFactory,主要用於處理標註有@InitBinder的方法作初始綁定~

// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
    
    // 須要注意的是:`@InitBinder`能夠標註N多個方法~  因此此處是List
    private final List<InvocableHandlerMethod> binderMethods;

    // 此子類的惟一構造函數
    public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
        super(initializer);
        this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
    }

    // 上面知道此方法的調用方法生initializer.initBinder以後
    // 因此使用註解它生效的時機是在直接實現接口的後面的~
    @Override
    public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
        for (InvocableHandlerMethod binderMethod : this.binderMethods) {
            // 判斷@InitBinder是否對dataBinder持有的target對象生效~~~(根據name來匹配的)
            if (isBinderMethodApplicable(binderMethod, dataBinder)) {
                // 關於目標方法執行這塊,能夠參考另一篇@InitBinder的原理說明~
                Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);

                // 標註@InitBinder的方法不能有返回值
                if (returnValue != null) {
                    throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);
                }
            }
        }
    }

    //@InitBinder有個Value值,它是個數組。它是用來匹配dataBinder.getObjectName()是否匹配的   若匹配上了,如今此註解方法就會生效
    // 若value爲空,那就對全部生效~~~
    protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
        InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
        Assert.state(ann != null, "No InitBinder annotation");
        String[] names = ann.value();
        return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
    }
}
ServletRequestDataBinderFactory

它繼承自InitBinderDataBinderFactory,做用就更明顯了。既可以處理@InitBinder,並且它使用的是更爲強大的數據綁定器:ExtendedServletRequestDataBinder

// @since 3.1
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
    public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
        super(binderMethods, initializer);
    }
    @Override
    protected ServletRequestDataBinder createBinderInstance(
            @Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {
        return new ExtendedServletRequestDataBinder(target, objectName);
    }
}

此工廠是RequestMappingHandlerAdapter這個適配器默認使用的一個數據綁定器工廠,而RequestMappingHandlerAdapter卻又是當下使用得最頻繁、功能最強大的一個適配器

總結

WebDataBinderSpringMVC中使用,它不須要咱們本身去建立,咱們只須要向它註冊參數類型對應的屬性編輯器PropertyEditorPropertyEditor能夠將字符串轉換成其真正的數據類型,它的void setAsText(String text)方法實現數據轉換的過程。

好好掌握這部份內容,這在Spring MVC中結合@InitBinder註解一塊兒使用將有很是大的威力,能必定程度上簡化你的開發,提升效率

知識交流

若文章格式混亂,可點擊原文連接-原文連接-原文連接-原文連接-原文連接

==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~==

若對技術內容感興趣能夠加入wx羣交流:Java高工、架構師3羣
若羣二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。而且備註:"java入羣" 字樣,會手動邀請入羣

在這裏插入圖片描述

相關文章
相關標籤/搜索