The most comprehensive exposition of WebDataBinder understanding of Spring's data binding

Each one

Do not always ask low-level questions, such people are either lazy, do not want to search the Internet, or stupid, little ability to think independently are not

Related Reading

[Small talk] home Spring data binding in Spring --- DataBinder deity (source code analysis)

Use [small house] Spring data binding talk in Spring --- PropertyAccessor property accessor and the implementation class DirectFieldAccessor

[Spring] data binding small family talk in Spring --- BeanWrapper and Java introspection and PropertyDescriptor Introspector


<center> interested Spring scannable code added wx group: Java高工、架构师3群(the end the two-dimensional code) </ center>


Foreword

Last article talked DataBinder, the article continues to talk about the practical application of data binding main course : WebDataBinder.

On the basis of the above, we take a look at DataBinderits inheritance tree:
Here Insert Picture Description
you can see from the inheritance tree, web environment unified data binding DataBinderhas been enhanced.

After all, the actual data binding scenarios: no exaggeration to say that 99% of the cases are web environment -

WebDataBinder

Its role is from web requestinside ( Note: This refers to a web request, the request is not necessarily ServletRequest ~ yo ) requests the web parametersis bound to JavaBeanthe -

ControllerParameter type method may be basic types, normal Java type may be encapsulated. If this type does not declare any common Java annotation, it means that it 每一个属性needs to Request to go to find the corresponding request parameters.

// @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);
            }
        });
    }
}

Single from WebDataBinderit, its parent class has been enhanced to provide enhanced capabilities as follows:

  1. Support for property names to _the default value of the processing starts with (automatic, can automatically handle all of Bool, Collection, Map, etc.)
  2. Support for attribute names !beginning with the default value handling (manual transmission, need to manually assign a default value to a property, highly flexible control of their own)
  3. Provide methods to support MultipartFilebind to JavaBeanon the property -

Demo sample

Below an example to demonstrate the use of its enhanced these features:

@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;

}

Demonstrate the use of !default values manually precise control field:

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

Printout (as expected):

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

Please use this print results compare the results of the above, you are there will be a lot to discover, for example, can find basic types of defaults is its own .
Another reason is clear: if you consequently no special handling, packaging type default values that are null definitely a ~

Understand WebDataBinderafter, we continue to see it as an important subclassServletRequestDataBinder

ServletRequestDataBinder

Having said that earlier, there is no pro also find wood we had talked to the most common Web scene API: javax.servlet.ServletRequest. This class will know from the name, it was born to do this.

Its goal is to: the Data from the servlet the Binding Request to the Parameters JavaBeans, Including Support for multipart Files from Servlet Request in the bind parameters to the JavaBean, the support multipart..

Note: up to such a request has been defined for the web Servlet Request, and bound strongly Servlet specification.


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示例
```java
    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);
    }

Printout:

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

perfect.

Questions: Why is the junior partner may think to Map Crossing values ​​are above, not the value json write it on the line?

ExtendedServletRequestDataBinder

Such code is small but can not be underestimated, it is ServletRequestDataBinderan enhanced, which is used to URI template variablesadd parameters to come for binding. It will go from the request HandlerMapping.class.getName() + ".uriTemplateVariables";to find out the value of this property to be used in binding ~~~

For example, we are familiar with @PathVariableit and it related: it is responsible for parsing the url parameter template out, and then put on attr, and finally to ExtendedServletRequestDataBinderbind ~~~

Between this: I think there is a role it is to customize our global variable for binding ~

This property to place the value of the place is: 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数据绑定提供支持,因此暂略~
```java
data binding from URL query params or form data in the request data to Java objects

MapDataBinder

It is located in org.springframework.data.weba Spring-Data correlation and, specifically for the treatment targetis Map&lt;String, Object&gt;to bind the target object type, it is not a public class ~

It is used in property accessor MapPropertyAccessor: inherited from a AbstractPropertyAccessorprivate static inner classes ~ (also to support the SpEL oh)

WebRequestDataBinder

It is used to treat Spring own definition of org.springframework.web.context.request.WebRequestthe purpose of processing and container-independent web request data binding, have the opportunity to detail this time, and then detail ~


How to register your own PropertyEditor to implement 自定义类型data binding?

We know by previous analysis, and data binding this one will eventually rely on PropertyEditorto achieve specific attribute conversion value (after all request are passed in a string Well ~)

In general, like String, int, long parameters are automatically bound to the binding can be done automatically, because the front there is that, by default, Spring is registered to us a number of parser 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));
        ...
        // 这里就部全部枚举出来了
    }
}

While many support the default registration Editor, but still found it a variety of events and no Date type, and Jsr310 provided, type the date of conversion (of course, including our custom type).
So I believe that small partners have experienced such pain points: a Date, LocalDate other types of use are automatically bound old inconvenient, and often innocently confused. So in the end many of them reluctantly chose semantics is not very clear timestamp to pass

Date demonstrate the type of data binding 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);
    }

Printout:

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)

The result is in line with my expectations: Start has a value, end no, endTest there is value.
Possible partner for small start, end can understand, the most surprising is endTestwhy there is value it? ? ?
Here I briefly explain the process steps:

  1. BeanWrapperCall setPropertyValue()assigns them a value passed in value will be given to convertForProperty()the method return value type conversion method according to get to (such as Date type here)
  2. Entrusted to this.typeConverterDelegate.convertIfNecessarytype conversion (here, for example string-> Date Type)
  3. First this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);find a suitable PropertyEditor(apparently here we do not deal with custom PropertyEditor Custom Date returns null)
  4. Fall back ConversionService, obviously we have here is not set, or null
  5. Fall back to using the default editor = findDefaultEditor(requiredType);(note: here only to find Depending on the type, because it says the default is not dealt with Date, it is also returns null)
  6. The final final, Spring fall back on Array、Collection、Mapthe default value of the deal with the problem, ultimately if the String type will be called BeanUtils.instantiateClass(strCtor, convertedValue)is, there is argument constructor initializes ~~~ (Note that this must be the right only of type String)
    1. Therefore, in this embodiment, it is equivalent to the last step new Date("Sat Jul 20 11:00:22 CST 2019"), because the string is the standard, the date string is so wide instrument, i.e. can normally be assigned a endTest ~

Through this simple step analysis explains why the end no value, endTest has a value of.
In fact, by the fallback of the last step of processing , we can also do this ingenious application . For example, I give a clever use of the following example:

@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);
    }

Printout:

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

perfect.


Ado, here I through the custom property editors means to allow us to support the above process incoming 2019-07-20this time string nonstandard .

We know that DataBinderitself is PropertyEditorRegistry, so I just need to own a custom registration PropertyEditorto:

1, through inheritance PropertyEditorSupportto achieve its own processing Date Editor:

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, into registered DataBinderand run

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

Print run as follows:

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)

The results in line with expectations . But this result I still throw the following two questions for small partners themselves thinking:
1, output .................... ParseException
2, Start has a value, endTest but the value is null

Finally, I would like to understand this: The custom editor, we can be very free, highly customized complete custom type of package, we can make the Controller even more fault tolerant, more intelligent, more simple. Interested parties can use this piece of knowledge, practice self ~

WebBindingInitializer和WebDataBinderFactory

WebBindingInitializer

WebBindingInitializer: This interface is implemented rewrite initBinder method of registering property editor is a global property editor, valid for all of the Controller.

It can be understood as simple and crude: WebBindingInitializeras encoding, @InitBinderto comment the way (of course also notes ways to control the current Controller only effective to achieve a more fine-grained control)

Observed, Spring naming of this interface is very interesting: it uses the Binding in progress state ~

// @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);
    }

}

This interface is its built-in implementation class is unique: ConfigurableWebBindingInitializerIf you want to expand, it is recommended to inherit it ~

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

This implementation class mainly provides some configurable items, easy to use. Note: This interface is generally not used directly, but in combination InitBinderDataBinderFactory, WebDataBinderFactoryand the like - for use with

WebDataBinderFactory

As the name suggests it is to create a WebDataBinderfactory.

// @since 3.1   注意:WebDataBinder 可是1.2就有了~
public interface WebDataBinderFactory {
    // 此处使用的是Spring自己的NativeWebRequest   后面两个参数就不解释了
    WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}

It inheritance tree as follows:
Here Insert Picture Description

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 according to the consistent design, the present method enables the operation of the template, only the subclass replication effect can be achieved corresponding to the operation.

InitBinderDataBinderFactory

It inherits from DefaultDataBinderFactory, mainly for processing labeled with @InitBindera way to do the initial binding ~

// @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

It inherits from InitBinderDataBinderFactorythe role even more apparent. Both can handle @InitBinder, and it uses a more powerful data-bound control: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);
    }
}

This plant is RequestMappingHandlerAdapterthe default adapter uses a data binding factory, and RequestMappingHandlerAdapteryet the moment is the most frequently used, the most powerful one adapter

to sum up

WebDataBinderIn SpringMVCuse, it does not need to create our own, we only need to register it with parameters corresponding to the type of property editor PropertyEditor. PropertyEditorString can be converted into its true data type , its void setAsText(String text)method of realization of the data conversion process.

A good grasp this part, which in Spring MVCconjunction with the @InitBindernotes used together will have a very great power, simplify your development to a certain extent, improve efficiency

Knowledge Exchange

若文章格式混乱,可点击: Description link - text link - text link - text link - text link

== The last: If you think this article helpful to you, may wish to point a praise chant. Of course, your circle of friends to share so that more small partners also are seeing 作者本人许可的~==

If interested in technical content can be added wx ××× stream: Java高工、架构师3群.
If the group fails two-dimensional code, please add wx number: fsx641385712(or two-dimensional code is scanned beneath wx). And Note: "java入群"the word, will be invited into the group manually

[insert here the picture description] (! Https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI =, size_16, color_FFFFFF, t_70, pic_center = 300X)

Guess you like

Origin blog.51cto.com/3631118/2422034