成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

最全面闡述WebDataBinder理解Spring的數(shù)據(jù)綁定

cgspine / 730人閱讀

摘要:每篇一句不要總問低級(jí)的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點(diǎn)獨(dú)立思考的能力都沒有相關(guān)閱讀小家聊聊中的數(shù)據(jù)綁定本尊源碼分析小家聊聊中的數(shù)據(jù)綁定屬性訪問器和實(shí)現(xiàn)類的使用小家聊聊中的數(shù)據(jù)綁定以及內(nèi)省和對(duì)感興趣可掃碼加

每篇一句
不要總問低級(jí)的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點(diǎn)獨(dú)立思考的能力都沒有
相關(guān)閱讀

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

【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- 屬性訪問器PropertyAccessor和實(shí)現(xiàn)類DirectFieldAccessor的使用

【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- BeanWrapper以及Java內(nèi)省Introspector和PropertyDescriptor

對(duì)Spring感興趣可掃碼加入wx群:Java高工、架構(gòu)師3群(文末有二維碼)

前言

上篇文章聊了DataBinder,這篇文章繼續(xù)聊聊實(shí)際應(yīng)用中的數(shù)據(jù)綁定主菜WebDataBinder。

在上文的基礎(chǔ)上,我們先來看看DataBinder它的繼承樹:

從繼承樹中可以看到,web環(huán)境統(tǒng)一對(duì)數(shù)據(jù)綁定DataBinder進(jìn)行了增強(qiáng)。

畢竟數(shù)據(jù)綁定的實(shí)際應(yīng)用場(chǎng)景:不夸張的說99%情況都是web環(huán)境~
WebDataBinder

它的作用就是從web request 里(注意:這里指的web請(qǐng)求,并不一定就是ServletRequest請(qǐng)求喲~)把web請(qǐng)求的parameters綁定到JavaBean上~

Controller方法的參數(shù)類型可以是基本類型,也可以是封裝后的普通Java類型。若這個(gè)普通Java類型沒有聲明任何注解,則意味著它的每一個(gè)屬性都需要到Request中去查找對(duì)應(yīng)的請(qǐng)求參數(shù)。

// @since 1.2
public class WebDataBinder extends DataBinder {

    // 此字段意思是:字段標(biāo)記  比如name -> _name
    // 這對(duì)于HTML復(fù)選框和選擇選項(xiàng)特別有用。
    public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
    // !符號(hào)是處理默認(rèn)值的,提供一個(gè)默認(rèn)值代替空值~~~
    public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
    
    @Nullable
    private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
    @Nullable
    private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
    // 默認(rèn)也會(huì)綁定空的文件流~
    private boolean bindEmptyMultipartFiles = true;

    // 完全沿用父類的兩個(gè)構(gòu)造~~~
    public WebDataBinder(@Nullable Object target) {
        super(target);
    }
    public WebDataBinder(@Nullable Object target, String objectName) {
        super(target, objectName);
    }

    ... //  省略get/set
    // 在父類的基礎(chǔ)上,增加了對(duì)_和!的處理~~~
    @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的屬性名確實(shí)是以!打頭的  那就做處理如下:
                // 如果JavaBean的該屬性可寫 && mpvs不存在去掉!后的同名屬性,那就添加進(jìn)來表示后續(xù)可以使用了(畢竟是默認(rèn)值,沒有精確匹配的高的)
                // 然后把帶!的給移除掉(因?yàn)槟J(rèn)值以已經(jīng)轉(zhuǎn)正了~~~)
                // 其實(shí)這里就是說你可以使用!來給個(gè)默認(rèn)值。比如!name表示若找不到name這個(gè)屬性的時(shí),就取它的值~~~
                // 也就是說你request里若有穿!name保底,也就不怕出現(xiàn)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的這個(gè)屬性可寫 && mpvs木有去掉_后的屬性名字
    // getEmptyValue(field, fieldType)就是根據(jù)Type類型給定默認(rèn)值。
    // 比如Boolean類型默認(rèn)給false,數(shù)組給空數(shù)組[],集合給空集合,Map給空map  可以參考此類:CollectionFactory
    // 當(dāng)然,這一切都是建立在你傳的屬性值是以_打頭的基礎(chǔ)上的,Spring才會(huì)默認(rèn)幫你處理這些默認(rèn)值
    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());
            }
        }
        // 若不在這幾大類型內(nèi),就返回默認(rèn)值null唄~~~
        // 但需要說明的是,若你是簡(jiǎn)單類型比如int,
        // Default value: null. 
        return null;
    }

    // 多帶帶提供的方法,用于綁定org.springframework.web.multipart.MultipartFile類型的數(shù)據(jù)到JavaBean屬性上~
    // 顯然默認(rèn)是允許MultipartFile作為Bean一個(gè)屬性  參與綁定的
    // Map>它的key,一般來說就是文件們啦~
    protected void bindMultipart(Map> 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來說,它對(duì)父類進(jìn)行了增強(qiáng),提供的增強(qiáng)能力如下:

支持對(duì)屬性名以_打頭的默認(rèn)值處理(自動(dòng)擋,能夠自動(dòng)處理所有的Bool、Collection、Map等)

支持對(duì)屬性名以!打頭的默認(rèn)值處理(手動(dòng)檔,需要手動(dòng)給某個(gè)屬性賦默認(rèn)值,自己控制的靈活性很高)

提供方法,支持把MultipartFile綁定到JavaBean的屬性上~

Demo示例

下面以一個(gè)示例來演示使用它增強(qiáng)的這些功能:

@Getter
@Setter
@ToString
public class Person {

    public String name;
    public Integer age;

    // 基本數(shù)據(jù)類型
    public Boolean flag;
    public int index;
    public List list;
    public Map map;

}

演示使用!手動(dòng)精確控制字段的默認(rèn)值:

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

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

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

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

打印輸出(符合預(yù)期):

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

請(qǐng)用此打印結(jié)果對(duì)比一下上面的結(jié)果,你是會(huì)有很多發(fā)現(xiàn),比如能夠發(fā)現(xiàn)基本類型的默認(rèn)值就是它自己。
另一個(gè)很顯然的道理:若你啥都不做特殊處理,包裝類型默認(rèn)值那鐵定都是null了~

了解了WebDataBinder后,繼續(xù)看看它的一個(gè)重要子類ServletRequestDataBinder

ServletRequestDataBinder

前面說了這么多,親有沒有發(fā)現(xiàn)還木有聊到過我們最為常見的Web場(chǎng)景API:javax.servlet.ServletRequest。本類從命名上就知道,它就是為此而生。

它的目標(biāo)就是:data binding from servlet request parameters to JavaBeans, including support for multipart files.從Servlet Request里把參數(shù)綁定到JavaBean里,支持multipart。

備注:到此類為止就已經(jīng)把web請(qǐng)求限定為了Servlet Request,和Servlet規(guī)范強(qiáng)綁定了。
public class ServletRequestDataBinder extends WebDataBinder {
    ... // 沿用父類構(gòu)造
    // 注意這個(gè)可不是父類的方法,是本類增強(qiáng)的~~~~意思就是kv都從request里來~~當(dāng)然內(nèi)部還是適配成了一個(gè)MutablePropertyValues
    public void bind(ServletRequest request) {
        // 內(nèi)部最核心方法是它:WebUtils.getParametersStartingWith()  把request參數(shù)轉(zhuǎn)換成一個(gè)Map
        // request.getParameterNames()
        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
    
        // 調(diào)用父類的bindMultipart方法,把MultipartFile都放進(jìn)MutablePropertyValues里去~~~
        if (multipartRequest != null) {
            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
        }
        // 這個(gè)方法是本類流出來的一個(gè)擴(kuò)展點(diǎn)~~~子類可以復(fù)寫此方法自己往里繼續(xù)添加
        // 比如ExtendedServletRequestDataBinder它就復(fù)寫了這個(gè)方法,進(jìn)行了增強(qiáng)(下面會(huì)說)  支持到了uriTemplateVariables的綁定
        addBindValues(mpvs, request);
        doBind(mpvs);
    }

    // 這個(gè)方法和父類的close方法類似,很少直接調(diào)用
    public void closeNoCatch() throws ServletRequestBindingException {
        if (getBindingResult().hasErrors()) {
            throw new ServletRequestBindingException("Errors binding onto object "" + getBindingResult().getObjectName() + """, new BindException(getBindingResult()));
        }
    }
}

下面就以MockHttpServletRequest為例作為Web 請(qǐng)求實(shí)體,演示一個(gè)使用的小Demo。說明:MockHttpServletRequest它是HttpServletRequest的實(shí)現(xiàn)類~

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

        // 構(gòu)造參數(shù),此處就不用MutablePropertyValues,以HttpServletRequest的實(shí)現(xiàn)類MockHttpServletRequest為例吧
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模擬請(qǐng)求參數(shù)
        request.addParameter("name", "fsx");
        request.addParameter("age", "18");

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

        // 設(shè)置多值的
        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");

        //// 一次性設(shè)置多個(gè)值(傳入Map)
        //request.setParameters(new HashMap() {{
        //    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寫個(gè)json就行呢?
ExtendedServletRequestDataBinder

此類代碼不多但也不容小覷,它是對(duì)ServletRequestDataBinder的一個(gè)增強(qiáng),它用于把URI template variables參數(shù)添加進(jìn)來用于綁定。它會(huì)去從request的HandlerMapping.class.getName() + ".uriTemplateVariables";這個(gè)屬性里查找到值出來用于綁定~~~

比如我們熟悉的@PathVariable它就和這相關(guān):它負(fù)責(zé)把參數(shù)從url模版中解析出來,然后放在attr上,最后交給ExtendedServletRequestDataBinder進(jìn)行綁定~~~

介于此:我覺得它還有一個(gè)作用,就是定制我們?nèi)謱傩宰兞坑糜诮壎▇

向此屬性放置值的地方是: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 {
    ... // 沿用父類構(gòu)造

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

        // 注意:此處是attr,而不是parameter
        Map uriVars = (Map) request.getAttribute(attr);
        if (uriVars != null) {
            uriVars.forEach((name, value) -> {
                
                // 若已經(jīng)存在確切的key了,不會(huì)覆蓋~~~~
                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);
                }
            });
        }
    }
}

可見,通過它我們亦可以很方便的做到在每個(gè)ServletRequest提供一份共用的模版屬性們,供以綁定~

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

說明:ServletRequestDataBinder一般不會(huì)直接使用,而是使用更強(qiáng)的子類ExtendedServletRequestDataBinder
WebExchangeDataBinder

它是Spring5.0后提供的,對(duì)Reactive編程的Mono數(shù)據(jù)綁定提供支持,因此暫略~

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

它位于org.springframework.data.web是和Spring-Data相關(guān),專門用于處理targetMap類型的目標(biāo)對(duì)象的綁定,它并非一個(gè)public類~

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

它是用于處理Spring自己定義的org.springframework.web.context.request.WebRequest的,旨在處理和容器無關(guān)的web請(qǐng)求數(shù)據(jù)綁定,有機(jī)會(huì)詳述到這塊的時(shí)候,再詳細(xì)說~

如何注冊(cè)自己的PropertyEditor來實(shí)現(xiàn)自定義類型數(shù)據(jù)綁定?

通過前面的分析我們知道了,數(shù)據(jù)綁定這一塊最終會(huì)依托于PropertyEditor來實(shí)現(xiàn)具體屬性值的轉(zhuǎn)換(畢竟request傳進(jìn)來的都是字符串嘛~)

一般來說,像String, int, long會(huì)自動(dòng)綁定到參數(shù)都是能夠自動(dòng)完成綁定的,因?yàn)榍懊嬗姓f,默認(rèn)情況下Spring是給我們注冊(cè)了N多個(gè)解析器的:

public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {

    @Nullable
    private Map, 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));
        ...
        // 這里就部全部枚舉出來了
    }
}

雖然默認(rèn)注冊(cè)支持的Editor眾多,但是依舊發(fā)現(xiàn)它并沒有對(duì)Date類型、以及Jsr310提供的各種事件、日期類型的轉(zhuǎn)換(當(dāng)然也包括我們的自定義類型)。
因此我相信小伙伴都遇到過這樣的痛點(diǎn):Date、LocalDate等類型使用自動(dòng)綁定老不方便了,并且還經(jīng)常傻傻搞不清楚。所以最終很多都無奈選擇了語(yǔ)義不是非常清晰的時(shí)間戳來傳遞

演示Date類型的數(shù)據(jù)綁定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");

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

        // 事件類型綁定
        pvs.add("start", new Date());
        pvs.add("end", "2019-07-20");
        // 試用試用標(biāo)準(zhǔn)的事件日期字符串形式~
        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)

結(jié)果是符合我預(yù)期的:start有值,end沒有,endTest卻有值。
可能小伙伴對(duì)start、end都可以理解,最詫異的是endTest為何會(huì)有值呢???
此處我簡(jiǎn)單解釋一下處理步驟:

BeanWrapper調(diào)用setPropertyValue()給屬性賦值,傳入的value值都會(huì)交給convertForProperty()方法根據(jù)get方法的返回值類型進(jìn)行轉(zhuǎn)換~(比如此處為Date類型)

委托給this.typeConverterDelegate.convertIfNecessary進(jìn)行類型轉(zhuǎn)換(比如此處為string->Date類型)

this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);找到一個(gè)合適的PropertyEditor(顯然此處我們沒有自定義Custom處理Date的PropertyEditor,返回null)

回退到使用ConversionService,顯然此處我們也沒有設(shè)置,返回null

回退到使用默認(rèn)的editor = findDefaultEditor(requiredType);(注意:此處只根據(jù)類型去找了,因?yàn)樯厦嬲f了默認(rèn)不處理了Date,所以也是返回null)

最終的最終,回退到Spring對(duì)Array、Collection、Map的默認(rèn)值處理問題,最終若是String類型,都會(huì)調(diào)用BeanUtils.instantiateClass(strCtor, convertedValue)也就是有參構(gòu)造進(jìn)行初始化~~~(請(qǐng)注意這必須是String類型才有的權(quán)利)

    1. 所以本例中,到最后一步就相當(dāng)于`new Date("Sat Jul 20 11:00:22 CST 2019") `,**因?yàn)樵撟址菢?biāo)準(zhǔn)的時(shí)間日期串,所以是闊儀的,也就是endTest是能被正常賦值的~**

通過這個(gè)簡(jiǎn)單的步驟分析,解釋了為何end沒值,endTest有值了。
其實(shí)通過回退到的最后一步處理,我們還可以對(duì)此做巧妙的應(yīng)用。比如我給出如下的一個(gè)巧用例子:

@Getter
@Setter
@ToString
public class Person {
    private String name;
    // 備注:child是有有一個(gè)入?yún)⒌臉?gòu)造器的
    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");

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

        // 給child賦值,其實(shí)也可以傳一個(gè)字符串就行了 非常的方便   Spring會(huì)自動(dòng)給我們new對(duì)象
        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這種非標(biāo)準(zhǔn)的時(shí)間字符串。

我們知道DataBinder本身就是個(gè)PropertyEditorRegistry,因此我只需要自己注冊(cè)一個(gè)自定義的PropertyEditor即可:

1、通過繼承PropertyEditorSupport實(shí)現(xiàn)一個(gè)自己的處理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....................");
        }
    }
}

2、注冊(cè)進(jìn)DataBinder并運(yùn)行

    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());

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

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


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

運(yùn)行打印如下:

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)

結(jié)果符合預(yù)期。不過對(duì)此結(jié)果我仍舊拋出如下兩個(gè)問題供小伙伴自行思考:
1、輸出了ParseException....................
2、start有值,endTest值卻為null了

理解這塊最后我想說:通過自定義編輯器,我們可以非常自由、高度定制化的完成自定義類型的封裝,可以使得我們的Controller更加容錯(cuò)、更加智能、更加簡(jiǎn)潔。有興趣的可以運(yùn)用此塊知識(shí),自行實(shí)踐~

WebBindingInitializer和WebDataBinderFactory WebBindingInitializer

WebBindingInitializer:實(shí)現(xiàn)此接口重寫initBinder方法注冊(cè)的屬性編輯器是全局的屬性編輯器,對(duì)所有的Controller都有效。

可以簡(jiǎn)單粗暴的理解為:WebBindingInitializer為編碼方式,@InitBinder為注解方式(當(dāng)然注解方式還能控制到只對(duì)當(dāng)前Controller有效,實(shí)現(xiàn)更細(xì)粒度的控制)

觀察發(fā)現(xiàn),Spring對(duì)這個(gè)接口的命名很有意思:它用的Binding正在進(jìn)行時(shí)態(tài)~
// @since 2.5   Spring在初始化WebDataBinder時(shí)候的回調(diào)接口,給調(diào)用者自定義~
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);
    }

}

此接口它的內(nèi)建唯一實(shí)現(xiàn)類為:ConfigurableWebBindingInitializer,若你自己想要擴(kuò)展,建議繼承它~

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

    // 下面這些參數(shù),不就是WebDataBinder那些可以配置的屬性們嗎?
    @Nullable
    private MessageCodesResolver messageCodesResolver;
    @Nullable
    private BindingErrorProcessor bindingErrorProcessor;
    @Nullable
    private Validator validator;
    @Nullable
    private ConversionService conversionService;
    // 此處使用的PropertyEditorRegistrar來管理的,最終都會(huì)被注冊(cè)進(jìn)PropertyEditorRegistry嘛
    @Nullable
    private PropertyEditorRegistrar[] propertyEditorRegistrars;

    ... //  省略所有g(shù)et/set
    
    // 它做的事無非就是把配置的值都放進(jìn)去而已~~
    @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);
        }
        // 可以看到對(duì)校驗(yàn)器這塊  內(nèi)部還是做了容錯(cuò)的
        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);
            }
        }
    }
}

此實(shí)現(xiàn)類主要是提供了一些可配置項(xiàng),方便使用。注意:此接口一般不直接使用,而是結(jié)合InitBinderDataBinderFactory、WebDataBinderFactory等一起使用~

WebDataBinderFactory

顧名思義它就是來創(chuàng)造一個(gè)WebDataBinder的工廠。

// @since 3.1   注意:WebDataBinder 可是1.2就有了~
public interface WebDataBinderFactory {
    // 此處使用的是Spring自己的NativeWebRequest   后面兩個(gè)參數(shù)就不解釋了
    WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}

它的繼承樹如下:

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

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

        WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
        
        // 可見WebDataBinder 創(chuàng)建好后,此處就會(huì)回調(diào)(只有一個(gè))
        if (this.initializer != null) {
            this.initializer.initBinder(dataBinder, webRequest);
        }
        // 空方法 子類去實(shí)現(xiàn),比如InitBinderDataBinderFactory實(shí)現(xiàn)了詞方法
        initBinder(dataBinder, webRequest);
        return dataBinder;
    }

    //  子類可以復(fù)寫,默認(rèn)實(shí)現(xiàn)是WebRequestDataBinder
    // 比如子類ServletRequestDataBinderFactory就復(fù)寫了,使用的new ExtendedServletRequestDataBinder(target, objectName)
    protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception 
        return new WebRequestDataBinder(target, objectName);
    }
}

按照Spring一貫的設(shè)計(jì),本方法實(shí)現(xiàn)了模板動(dòng)作,子類只需要復(fù)寫對(duì)應(yīng)的動(dòng)作即可達(dá)到效果。

InitBinderDataBinderFactory

它繼承自DefaultDataBinderFactory,主要用于處理標(biāo)注有@InitBinder的方法做初始綁定~

// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
    
    // 需要注意的是:`@InitBinder`可以標(biāo)注N多個(gè)方法~  所以此處是List
    private final List binderMethods;

    // 此子類的唯一構(gòu)造函數(shù)
    public InitBinderDataBinderFactory(@Nullable List binderMethods, @Nullable WebBindingInitializer initializer) {
        super(initializer);
        this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
    }

    // 上面知道此方法的調(diào)用方法生initializer.initBinder之后
    // 所以使用注解它生效的時(shí)機(jī)是在直接實(shí)現(xiàn)接口的后面的~
    @Override
    public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
        for (InvocableHandlerMethod binderMethod : this.binderMethods) {
            // 判斷@InitBinder是否對(duì)dataBinder持有的target對(duì)象生效~~~(根據(jù)name來匹配的)
            if (isBinderMethodApplicable(binderMethod, dataBinder)) {
                // 關(guān)于目標(biāo)方法執(zhí)行這塊,可以參考另外一篇@InitBinder的原理說明~
                Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);

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

    //@InitBinder有個(gè)Value值,它是個(gè)數(shù)組。它是用來匹配dataBinder.getObjectName()是否匹配的   若匹配上了,現(xiàn)在此注解方法就會(huì)生效
    // 若value為空,那就對(duì)所有生效~~~
    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,而且它使用的是更為強(qiáng)大的數(shù)據(jù)綁定器:ExtendedServletRequestDataBinder

// @since 3.1
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
    public ServletRequestDataBinderFactory(@Nullable List 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這個(gè)適配器默認(rèn)使用的一個(gè)數(shù)據(jù)綁定器工廠,而RequestMappingHandlerAdapter卻又是當(dāng)下使用得最頻繁、功能最強(qiáng)大的一個(gè)適配器

總結(jié)

WebDataBinderSpringMVC中使用,它不需要我們自己去創(chuàng)建,我們只需要向它注冊(cè)參數(shù)類型對(duì)應(yīng)的屬性編輯器PropertyEditor。PropertyEditor可以將字符串轉(zhuǎn)換成其真正的數(shù)據(jù)類型,它的void setAsText(String text)方法實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換的過程。

好好掌握這部分內(nèi)容,這在Spring MVC中結(jié)合@InitBinder注解一起使用將有非常大的威力,能一定程度上簡(jiǎn)化你的開發(fā),提高效率

知識(shí)交流
若文章格式混亂,可點(diǎn)擊:原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接

==The last:如果覺得本文對(duì)你有幫助,不妨點(diǎn)個(gè)贊唄。當(dāng)然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==

**若對(duì)技術(shù)內(nèi)容感興趣可以加入wx群交流:Java高工、架構(gòu)師3群。
若群二維碼失效,請(qǐng)加wx號(hào):fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會(huì)手動(dòng)邀請(qǐng)入群**

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75512.html

相關(guān)文章

  • SpringMVC學(xué)習(xí)筆記

    摘要:是目前最流行的一種互聯(lián)網(wǎng)軟件架構(gòu)。協(xié)議,是一個(gè)無狀態(tài)協(xié)議,即所有的狀態(tài)都保存在服務(wù)器端。而這種轉(zhuǎn)化是建立在表現(xiàn)層之上的,所以就是表現(xiàn)層狀態(tài)轉(zhuǎn)化。具體說,就是協(xié)議里面,四個(gè)表示操作方式的動(dòng)詞。 一、SpringMVC概述 Spring為展現(xiàn)層提供的基于MVC設(shè)計(jì)理念的優(yōu)秀的Web框架,是目前最主流的MVC框架之一 Spring3.0之后全面超越Struts2,成為最優(yōu)秀的MVC框架 S...

    roundstones 評(píng)論0 收藏0
  • @Validated和@Valid區(qū)別?校驗(yàn)級(jí)聯(lián)屬性(內(nèi)部類)

    摘要:畢竟永遠(yuǎn)相信本文能給你帶來意想不到的收獲使用示例關(guān)于數(shù)據(jù)校驗(yàn)這一塊在中的使用案例,我相信但凡有點(diǎn)經(jīng)驗(yàn)的程序員應(yīng)該沒有不會(huì)使用的,并且還不乏熟練的選手。 每篇一句 NBA里有兩大笑話:一是科比沒天賦,二是詹姆斯沒技術(shù) 相關(guān)閱讀 【小家Java】深入了解數(shù)據(jù)校驗(yàn):Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validati...

    Winer 評(píng)論0 收藏0
  • 【小家Spring】聊聊Spring數(shù)據(jù)綁定 --- DataBinder本尊(源碼分析)

    摘要:對(duì)中的數(shù)據(jù)綁定場(chǎng)景,小伙伴們就再熟悉不過了。比如包下大名鼎鼎的源碼分析的源碼相對(duì)來說還是頗為復(fù)雜的,它提供的能力非常強(qiáng)大,也注定了它的方法非常多屬性也非常多。并且備注入群字樣,會(huì)手動(dòng)邀請(qǐng)入群 每篇一句 唯有熱愛和堅(jiān)持,才能讓你在程序人生中屹立不倒,切忌跟風(fēng)什么語(yǔ)言或就學(xué)什么去~ 相關(guān)閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- 屬性訪問器PropertyAccesso...

    charles_paul 評(píng)論0 收藏0
  • 3.13、@InitBinder 和 WebDataBinder

    摘要:標(biāo)記初始化的方法,被用于填充被注解的處理方法的命令和表單對(duì)象參數(shù)。初始化綁定器方法必須不帶返回值,所以它們通常被聲明為的。典型的參數(shù)包括和或者,允許用代碼方式注冊(cè)特定上下文的編輯器。詳情見使用和通知控制器一節(jié)。 ??這一部分示例見這個(gè)項(xiàng)目的 mvc 分支下的 WebDataBinderController.java ① 用@InitBinder自定義數(shù)據(jù)綁定 ??用@InitBind...

    dreamGong 評(píng)論0 收藏0
  • ControllerAdvice攔截器

    摘要:看成提供的一個(gè)特殊的攔截器。是一個(gè),用于定義最主要用途,和方法,適用于所有使用方法攔截。為所有封裝統(tǒng)一異常處理代碼。為所有設(shè)置全局變量。用于為所有設(shè)置某個(gè)類型的數(shù)據(jù)轉(zhuǎn)換器。 Spring3.2開始提供的新注解,控制器增強(qiáng)(AOP),最主要的應(yīng)用是做統(tǒng)一的異常處理。@ControllerAdvice(看成spring mvc提供的一個(gè)特殊的攔截器)。@ControllerAdvice是一...

    caohaoyu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<