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

Keywords: PHP Spring Attribute Java JSON

Every sentence

Don't always ask low-level questions. Such people are either lazy, unwilling to search on the Internet, or dumb, and have no ability to think independently.

Relevant Reading

[Little Spring] Talk about Data Binder in Spring (Source Code Analysis)

[Little Spring] Talk about Data Binding in Spring - the Use of Property Accessor and DirectField Accessor

[Little Spring] Talk about data binding in Spring - - BeanWrapper and Java Introspector and Property Descriptor

Interested in Spring, Scannable Code joins the wx group: `Java Senior Engineer, Architect 3 groups'(there are two-dimensional codes at the end of the article)

Preface

Last article talked about DataBinder, and this article goes on to talk about the main course of data binding in practical applications: WebDataBinder.

On the basis of the above, let's first look at the inheritance tree of DataBinder:

As you can see from the inheritance tree, the unified web environment enhances the data binding DataBinder.

After all, the actual application scenario of data binding: 99% of the cases are web environments, not to be exaggerated.~

WebDataBinder

Its function is to bind the parameters of web requests to JavaBean s from web requests (note: the web requests here are not necessarily ServletRequest requests yo ~)~

The parameter type of the Controller method can be either the basic type or the encapsulated normal Java type. If this common Java type does not declare any annotations, it means that each of its attributes needs to go to Request to find the corresponding request parameters.

// @since 1.2
public class WebDataBinder extends DataBinder {

    // This field means: field tags such as name->_name
    // This is particularly useful for HTML checkboxes and selection options.
    public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
    // Symbols handle default values, providing a default value instead of a null value~~~
    public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
    
    @Nullable
    private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
    @Nullable
    private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
    // By default, empty file streams will also be bound~
    private boolean bindEmptyMultipartFiles = true;

    // Completely following the two constructs of the parent class~~~
    public WebDataBinder(@Nullable Object target) {
        super(target);
    }
    public WebDataBinder(@Nullable Object target, String objectName) {
        super(target, objectName);
    }

    ... //  Omit get/set
    // On the basis of the parent class, the processing of _and!~~~
    @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) {

                // If the property name you gave to PropertyValue really is! Start by doing the following:
                // If this attribute of JavaBean is writable & MPVS does not exist without removing! The same name attribute after that is added to indicate that it can be used later (after all, it is the default value, no exact match is high)
                // Then remove the band! Because the default value has been corrected.
                // Actually, this means you can use it! To give a default value. For example, name means that if you can't find the attribute name, you take its value.~~~
                // That is to say, if you wear it in your request! name is guaranteed, you will not be afraid of null value.~
                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);
                }
            }
        }
    }

    // Steps for handling ____________
    // If the incoming field begins with _
    // This property of JavaBean is writable & MPVS has the name of the property after deleting ____________.
    // GetEmptyValue (field, field Type) is the default value given according to the type.
    // For example, Boolean type defaults to false, array to empty array [], set to empty collection, Map to empty map can refer to this type: Collection Factory
    // Of course, all of this is based on the fact that the attribute values you pass on start with _and Spring will default to help you deal with these defaults.
    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());
            }
        }
        // If not in these types, return the default value null choke~~~
        // But it's important to note that if you're a simple type like int,
        // Default value: null. 
        return null;
    }

    // A separate method is provided to bind data of type org. spring framework. web. multipart. MultipartFile to JavaBean properties~
    // Apparently the default is to allow MultipartFile to be bound as an attribute of a Bean
    // Map < String, List < MultipartFile > Its key, in general, is the files.~
    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);
            }
        });
    }
}

For WebDataBinder alone, it enhances the parent class by providing the following enhancements:

  1. Supports default value processing for attribute names starting with (automatic block, automatic handling of all Bool s, Collection s, Map s, etc.)
  2. Supports default value processing starting with! For attribute names (manual file, need to assign default values to an attribute manually, self-control flexibility is very high)
  3. Provides methods to support binding MultipartFile to JavaBean properties~

Demo example

Here's an example to illustrate the capabilities that it enhances:

@Getter
@Setter
@ToString
public class Person {

    public String name;
    public Integer age;

    // Basic data types
    public Boolean flag;
    public int index;
    public List<String> list;
    public Map<String, String> map;

}

Demonstration Use! Manual Accurate Control of Default Values for Fields:

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

        // Setting properties (the default values are shown here)
        MutablePropertyValues pvs = new MutablePropertyValues();

        // Use! To simulate fields to manually specify default values
        //pvs.add("name", "fsx");
        pvs.add("!name", "Mai Shiranui");
        pvs.add("age", 18);
        pvs.add("!age", 10); // There are exact values above. The default values will not be renewed.

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

Print out (as expected):

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

Comparing the printed results with the above results, you will find many discoveries, such as that the default value of the basic type is itself.
Another obvious truth: if you don't do anything special, the default value of packing type must be null.~

Once you understand WebDataBinder, go ahead and look at its important subclass, ServletRequestDataBinder

ServletRequestDataBinder

Having said so much, have you ever found out that you have talked about our most common Web scenario API: javax.servlet.ServletRequest? This class is named for it.

Its goal is: data binding from servlet request parameters to JavaBeans, including support for multipart files. Binding parameters to JavaBeans from Servlet Request to support multipart files.

Note: Up to this point, web requests have been restricted to Servlet Request, which is strongly bound to the Servlet specification.

public class ServletRequestDataBinder extends WebDataBinder {
    ... // Following the parent construct
    // Note that this is not a parent method, but an enhanced version of this class, which means that kv comes from the request ~ ~ internally or adapts to a Mutable Property Values, of course.
    public void bind(ServletRequest request) {
        // The core internal approach is this: WebUtils. getParameters StartingWith () converts the request parameter into a Map
        // request.getParameterNames()
        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
    
        // Call the bindMultipart method of the parent class and put the MultipartFile in Mutable Property Values~~~
        if (multipartRequest != null) {
            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
        }
        // This method is an extension point ~~subclass from this class, which can override the method itself and continue to add it to it.
        // Extended Servlet Request Data Binder, for example, overrides this approach and enhances (as we will see below) support for uriTemplate Variables binding
        addBindValues(mpvs, request);
        doBind(mpvs);
    }

    // This method is similar to the close method of the parent class and rarely calls directly.
    public void closeNoCatch() throws ServletRequestBindingException {
        if (getBindingResult().hasErrors()) {
            throw new ServletRequestBindingException("Errors binding onto object '" + getBindingResult().getObjectName() + "'", new BindException(getBindingResult()));
        }
    }
}

Let's take MockHttpServletRequest as an example to demonstrate a small Demo used as a Web request entity. Description: MockHttpServletRequest is the implementation class of HttpServletRequest~

Demo example

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

        // Construct parameters without Mutable Property Values. Take MockHttpServletRequest, an implementation class of HttpServletRequest, for example.
        MockHttpServletRequest request = new MockHttpServletRequest();
        // Analog request parameters
        request.addParameter("name", "fsx");
        request.addParameter("age", "18");

        // flag can be used not only with true/false but also with 0 and 1?
        request.addParameter("flag", "1");

        // Setting multivalued
        request.addParameter("list", "4", "2", "3", "1");
        // map assignment (Json string)
        // Request. addParameter ("map", "{key1':'value1','key2':'value2'});// This is not possible
        request.addParameter("map['key1']", "value1");
        request.addParameter("map['key2']", "value2");

        //// Set multiple values at once (pass in Map)
        //request.setParameters(new HashMap<String, Object>() {{
        //    put("name", "fsx");
        //    put("age", "18");
        //}});

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

Print Output:

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

Perfect.

Question to Consider: Why can a buddy think about passing values to Map attributes as above instead of writing a json for value?

ExtendedServletRequestDataBinder

This kind of code is not much, but it can not be underestimated. It is an enhancement of ServletRequestDataBinder, which is used to add URI template variables parameters to bind. It will go from the request's Handler Mapping. class. getName ()+". uriTemplateVariables"; this property finds the value for binding.~~~

For example, the familiar @PathVariable is related to this: it parses the parameters from the url template, puts them on the attr, and finally gives them to the Extended Servlet Request Data Binder for binding.~~~

In between: I think it also has a role in customizing our global attribute variables for binding.~

The places to place values on this property are: AbstractUrlHandlerMapping. lookupHandler ()--> chain. addInterceptor (new UriTemplate Variables Handler Interceptor (uriTemplate Variables); --> PreHandle () method - > exposeUriTemplate Variables (this. Template Variables, request); - > Attribute (URI_TEMPLATE_VAR) IABLES_ATTRIBUTE, uriTemplate Variables;

// @since 3.1
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
    ... // Following the parent construct

    //The Unique Method of this Class
    @Override
    @SuppressWarnings("unchecked")
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        // Its value is: HandlerMapping. class. getName ()+". uriTemplate Variables";
        String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;

        // Note: Here is attr, not parameter
        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
        if (uriVars != null) {
            uriVars.forEach((name, value) -> {
                
                // If the exact key already exists, it will not be overwritten.~~~~
                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);
                }
            });
        }
    }
}

Obviously, it is also very convenient for us to provide a common template attributes for binding in each ServletRequest.~

This class basically follows the function of the parent class, which is relatively simple. Demo is not written here (see the parent class for Demo).~

Note: ServletRequestDataBinder is not normally used directly, but instead uses a stronger subclass Extended ServletRequestDataBinder

WebExchangeDataBinder

It is provided after Spring 5.0 and supports Mono data binding for Reactive programming, so it is temporary.~

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

MapDataBinder

It is located at org. spring framework. data. web, which is related to Spring-Data. It is specifically used to handle binding of target objects of Map < String, Object > type. It is not a public class.~

The property accessor it uses is MapProperty Accessor: a private static internal class inherited from AbstractProperty Accessor ~ (also supported by SpEL)

WebRequestDataBinder

It is used to handle Spring's own definition of org. Spring framework. web. context. request. WebRequest. It is designed to handle container-independent web request data binding. When you have the opportunity to elaborate on this, let's go into details.~

How to register your Property Editor to implement custom type data binding?

From the previous analysis, we know that data binding will ultimately rely on Property Editor to achieve the conversion of specific attribute values (after all, request s come in strings ~)

Generally speaking, like String, int, long will automatically bind to parameters and can automatically complete the binding, because as mentioned earlier, Spring registers more than N parsers for us by default:

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));
        ...
        // Here are all the enumerations.
    }
}

Although there are many Editor s supported by default registration, it still finds that it does not convert Date types, events and date types provided by Jsr310 (including our custom types, of course).
Therefore, I believe that small partners have encountered such pain points: Date, LocalDate and other types of automatic binding is not convenient, and often silly confused. So in the end, many people have no choice but to choose a time stamp whose semantics are not very clear.

Demo for data binding of type Date:

@Getter
@Setter
@ToString
public class Person {

    public String name;
    public Integer age;

    // Take the Date type as an example
    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");

        // set a property
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // Event type binding
        pvs.add("start", new Date());
        pvs.add("end", "2019-07-20");
        // Trial the standard event date string format~
        pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");


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

Print Output:

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 results are in line with my expectations: start has value, end has no value, and endTest has value.
Perhaps small partners can understand the start and end, the most surprising thing is why endTest is valuable???
Here I will briefly explain the processing steps:

  1. BeanWrapper calls setPropertyValue() to assign values to attributes, and the incoming value is given to the convertForProperty() method to convert ~ (for example, Date) according to the return value type of the get method.
  2. Delegate type conversion to this. type Converter Delegate. convertIfNecessary (for example, string - > Date type here)
  3. First this. Property Editor Registry. find Custom Editor (requiredType, propertyName); find a suitable Property Editor (obviously we don't have Custom to handle Date's Property Editor here, return null)
  4. Back to using Conversion Service, obviously we haven't set it up here, back to null
  5. Back to using the default editor = find Default Editor (requiredType); (Note: This is only based on the type, because the default does not handle Date, so it also returns null)
  6. Ultimately, back to Spring's default value handling of Array, Collection, Map, and ultimately, if String type, BeanUtils. instantiateClass (strCtor, converted Value) is called, which means initialization with reference constructs. ~ (Note that this must be the right of String type)
    1. So in this case, the last step is equivalent to the new Date ("Sat Jul 2011:00:22 CST 2019"), because the string is a standard time and date string, so it is broad, that is, endTest can be assigned normally.~

Through this simple step analysis, it explains why end is not worth, and endTest is worth.
In fact, through the last step back to deal with, we can also do clever application of this. For example, I give the following example of clever use:

@Getter
@Setter
@ToString
public class Person {
    private String name;
    // Note: child has a constructor with a parameter
    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");

        // set a property
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // Assigning a value to a child, in fact, can also pass a string. It's very convenient that Spring will automatically give us a new object.
        pvs.add("child", "fsx-son");
        
        binder.bind(pvs);
        System.out.println(person);
    }

Print Output:

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

Perfect.

Next, I use the custom property editor to support the processing of non-standard time strings we passed in from 2019 to 07-20.

We know that DataBinder itself is a Property Editor Registry, so I just need to register a custom Property Editor myself:

1. Implement an editor for Date processing by inheriting Property Editor Support:

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. Register into DataBinder and run it

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

        // set a property
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("name", "fsx");

        // Event type binding
        pvs.add("start", new Date());
        pvs.add("end", "2019-07-20");
        // Trial the standard event date string format~
        pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");


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

The running prints are 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 are in line with expectations. However, I still put forward the following two questions for small partners to think for themselves:
1. Output ParseException...
2. start has a value, but endTest has a value of null.

Understanding this last piece, I would like to say: through the custom editor, we can complete the custom type encapsulation very freely and highly customized, which can make our Controller more fault-tolerant, intelligent and concise. Interested people can use this knowledge and practice by themselves.~

Web Binding Initializer and Web Data BinderFactory

WebBindingInitializer

WebBindingInitializer: The property editor that implements this interface to rewrite the initBinder method registration is a global property editor that works for all Contentrollers.

It can be understood roughly as follows: Web Binding Initializer is the encoding mode, and @InitBinder is the annotation mode (of course, the annotation mode can also be controlled to only be effective for the current Controller to achieve finer-grained control).

Observations show that Spring's naming of this interface is interesting: it uses Binding in the ongoing tense~

// @ Since 2.5 Spring callback interface when initializing WebDataBinder to customize the caller~
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);
    }

}

The only built-in implementation class of this interface is: Configurable Web Binding Initializer. If you want to extend it yourself, it is recommended to inherit it.~

public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
    private boolean autoGrowNestedPaths = true;
    private boolean directFieldAccess = false; // Obviously this is false.

    // Aren't these parameters the configurable attributes of WebDataBinder?
    @Nullable
    private MessageCodesResolver messageCodesResolver;
    @Nullable
    private BindingErrorProcessor bindingErrorProcessor;
    @Nullable
    private Validator validator;
    @Nullable
    private ConversionService conversionService;
    // The Property Editor Registrar used here will eventually be registered in the Property Editor Registry.
    @Nullable
    private PropertyEditorRegistrar[] propertyEditorRegistrars;

    ... //  Omit all get/set
    
    // All it does is put in the configuration values.~~
    @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);
        }
        // You can see that the internal part of the checker is still fault-tolerant.
        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 for easy use. Note: This interface is generally not used directly, but in conjunction with InitBinderDataBinderFactory, WebDataBinderFactory, etc.~

WebDataBinderFactory

As the name implies, it is to create a factory for WebDataBinder.

// @ since 3.1 Note: WebDataBinder is available in 1.2~
public interface WebDataBinderFactory {
    // The latter two parameters of Spring's own Native Web Request are not explained here.
    WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}

Its succession tree is as follows:

DefaultDataBinderFactory

public class DefaultDataBinderFactory implements WebDataBinderFactory {
    @Nullable
    private final WebBindingInitializer initializer;
    // Note: This is the only constructor
    public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
        this.initializer = initializer;
    }

    // The Method of Implementing Interface
    @Override
    @SuppressWarnings("deprecation")
    public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {

        WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
        
        // You can see that once the WebDataBinder is created, there will be a callback (only one)
        if (this.initializer != null) {
            this.initializer.initBinder(dataBinder, webRequest);
        }
        // Implementing empty method subclasses, such as InitBinderDataBinderFactory, implements word methods
        initBinder(dataBinder, webRequest);
        return dataBinder;
    }

    //  Subclasses can be overridden, and the default implementation is WebRequestDataBinder
    // For example, the subclass ServletRequestDataBinderFactory is rewritten using the new Extended ServletRequestDataBinder (target, objectName)
    protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception 
        return new WebRequestDataBinder(target, objectName);
    }
}

According to Spring's consistent design, this method realizes template action, and sub-classes only need to copy the corresponding action to achieve the effect.

InitBinderDataBinderFactory

It inherits from DefaultDataBinderFactory and is mainly used to process methods labeled @InitBinder for initial binding.~

// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
    
    // Note that `@InitBinder'can annotate more than N methods ~so here is List
    private final List<InvocableHandlerMethod> binderMethods;

    // Unique constructor for this subclass
    public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
        super(initializer);
        this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
    }

    // After knowing that the calling method of this method generates initializer.initBinder
    // So the time to use annotations to take effect is behind the direct implementation of the interface.~
    @Override
    public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
        for (InvocableHandlerMethod binderMethod : this.binderMethods) {
            // Determine whether @InitBinder takes effect on the target object held by dataBinder ~ (matched by name)
            if (isBinderMethodApplicable(binderMethod, dataBinder)) {
                // Refer to another @InitBinder Principle Note on Target Method Execution~
                Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);

                // The method labeled @InitBinder cannot have a return value
                if (returnValue != null) {
                    throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);
                }
            }
        }
    }

    //@ InitBinder has a Value value, which is an array. It is used to match whether dataBinder.getObjectName() matches or not. If matched, this annotation method will now take effect.
    // If the value is empty, it is valid for all.~~~
    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 InitBinderDataBinderFactory, and its role is even more obvious. It can handle @InitBinder, and it uses a more powerful data binder: Extended Servlet Request Data Binder

// @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 factory is the default data binding factory used by the Request Mapping Handler Adapter adapter, while the Request Mapping Handler Adapter is currently the most frequently used and powerful adapter.

summary

WebDataBinder is used in Spring MVC. It does not need to be created by ourselves. We just need to register the property editor Property Editor corresponding to the parameter type with WebDataBinder. Property Editor can convert strings into their real data types, and its void setAsText(String text) method implements the process of data conversion.

Grasp this part of the content well, which in Spring MVC with the @InitBinder annotation will be very powerful, can simplify your development to a certain extent, improve efficiency.

Knowledge exchange

If the format of the article is confused, click: Text Link-Text Link-Text Link-Text Link-Text Link-Text Link

== The last: If you think this is helpful to you, you might as well give a compliment. Of course, sharing your circle of friends so that more small partners can see it is also authorized by the author himself.~==

If you are interested in technical content, you can join the wx group: Java Senior Engineer and Architect Group.
If the group two-dimensional code fails, Please add wx number: fsx641385712 (or scan the wx two-dimensional code below). And note: "java into the group" words, will be manually invited to join the group

Posted by phpisawesome on Sat, 20 Jul 2019 03:52:28 -0700