SpringMVC源码分析(七)--数据绑定工厂

1.数据绑定工厂的使用

数据绑定工厂能够创建数据绑定器,将数据绑定到对象中,比如说当接收到请求时,经过http协议解析后数据刚开始都是字符串,此时我们希望将这些属性进行类型转换,并为对象赋值,示例如下:

1)先创建两个实体类Student和Teacher

@Getter
@Setter
@ToString
public class Student {
    private String name;
    private int age;
    private Date birthday;
    private Teacher teacher;
}

@Getter
@Setter
@ToString
public class Teacher {
    private String name;
}

2)使用ServletRequestDataBinderFactory工厂创建数据绑定器,进行数据绑定

public class Test01 {
    public static void main(String[] args) throws Exception {
        // 模拟一个Web请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 简单字符串属性
        request.setParameter("name", "lm");
        // 需要将字符串转换为整型
        request.setParameter("age", "28");
        // 需要能够解析自定义格式
        request.setParameter("birthday", "1999-01-02");
        // 需要能够绑定到对象中的属性
        request.setParameter("teacher.name", "lhm");

        Student student = new Student();
        // 使用数据绑定工厂创建数据绑定器,此时没有传入bindMethod或initializer
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        System.out.println(student);
    }
}

执行Test01测试,绑定失败,会报如下错误:

这是因为我们的数据绑定工厂中没有传入bindMethod或者initializer,无法自定义解析日期格式,创建Date对象时没有使用默认的格式导致解析错误,此时我们有如下2种方式完成自定义数据格式的绑定

1.1 向数据绑定工厂中传入bindMethod

通过@InitBinder注解向数据绑定工厂中添加bindMethod

1)首先创建自定义的转换器类

public class DateFormatter implements Formatter<Date> {
    private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd");

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        return simpleDateFormat.parse(text);
    }

    @Override
    public String print(Date date, Locale locale) {
        return simpleDateFormat.format(date);
    }
}

2)创建一个类并添加@InitBinder修饰的转换方法

public class MyBinder {
    @InitBinder
    public void bind(WebDataBinder webDataBinder) {
        // 添加自定义的转换器
        webDataBinder.addCustomFormatter(new DateFormatter());
    }
}

3)修改测试代码,在数据转换工厂中添加bindMethod,即可绑定成功

public class Test02 {
    public static void main(String[] args) throws Exception {
        // 模拟一个Web请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 简单字符串属性
        request.setParameter("name", "lm");
        // 需要将字符串转换为整型
        request.setParameter("age", "28");
        // 需要能够解析自定义格式
        request.setParameter("birthday", "1999-01-02");
        // 需要能够绑定到对象中的属性
        request.setParameter("teacher.name", "lhm");

        Student student = new Student();
        // 添加bindMethod
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyBinder(), MyBinder.class.getMethod("bind", WebDataBinder.class));
        // 使用数据绑定工厂创建数据绑定器,此时没有传入bindMethod或initializer
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Arrays.asList(method), null);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        // 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
        System.out.println(student);
    }
}

1.2 向数据绑定工厂中传入initializer

修改测试代码,在数据转换工厂中添加initializer,使用ConversionService接口进行转换,即可绑定成功

public class Test03 {
    public static void main(String[] args) throws Exception {
        // 模拟一个Web请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 简单字符串属性
        request.setParameter("name", "lm");
        // 需要将字符串转换为整型
        request.setParameter("age", "28");
        // 需要能够解析自定义格式
        request.setParameter("birthday", "1999-01-02");
        // 需要能够绑定到对象中的属性
        request.setParameter("teacher.name", "lhm");

        Student student = new Student();
        // 创建ConversionService,添加自定义的转换器
        FormattingConversionService conversionService = new FormattingConversionService();
        conversionService.addFormatter(new DateFormatter());
        // 创建initializer对象,添加ConversionService
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);
        // 使用数据绑定工厂创建数据绑定器,此时传入initializer
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        // 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
        System.out.println(student);
    }
}

如果同时传入了bindMethod和initializer,会优先使用bindMethod

另外ApplicationConversionService中添加了许多默认的转换器,可以直接使用

但是在使用前,需要在字段上添加相应的注解,例如date类型上添加@DateTimeFormat注解即可完成绑定

@Getter
@Setter
@ToString
public class Student {
    private String name;
    private int age;
    @DateTimeFormat(pattern = "yyyy-mm-dd")
    private Date birthday;
    private Teacher teacher;
}
public class Test04 {
    public static void main(String[] args) throws Exception {
        // 模拟一个Web请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 简单字符串属性
        request.setParameter("name", "lm");
        // 需要将字符串转换为整型
        request.setParameter("age", "28");
        // 需要能够解析自定义格式
        request.setParameter("birthday", "1999-01-02");
        // 需要能够绑定到对象中的属性
        request.setParameter("teacher.name", "lhm");

        Student student = new Student();
        // 创建ConversionService,使用默认的转换器
        FormattingConversionService conversionService = new ApplicationConversionService();
        // 创建initializer对象,添加ConversionService
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);
        // 使用数据绑定工厂创建数据绑定器,此时传入initializer
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        // 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
        System.out.println(student);
    }
}

2.数据绑定原理

2.1 创建数据绑定器

示例代码使用factory.createBinder通过ServletRequestDataBinderFactory创建dataBinder,源码如下:

public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
    // 1、创建dataBinder
    WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
    if (this.initializer != null) {
        // 2.如果构造工厂时传入了initializer,则初始化ConversionService方式扩展的绑定器
        this.initializer.initBinder(dataBinder, webRequest);
    }
    // 3.初始化@InitBinder方式扩展的绑定器
    initBinder(dataBinder, webRequest);
    return dataBinder;
}

// ServletRequestDataBinderFactory#createBinderInstance
protected ServletRequestDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {
    // 返回ExtendedServletRequestDataBinder
    return new ExtendedServletRequestDataBinder(target, objectName);
}

// ConfigurableWebBindingInitializer#initBinder
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) {
        // 设置conversionService
        binder.setConversionService(this.conversionService);
    }
    if (this.propertyEditorRegistrars != null) {
        for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
            propertyEditorRegistrar.registerCustomEditors(binder);
        }
    }
}

// InitBinderDataBinderFactory#initBinder
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
    // 遍历传入的binderMethods
    for (InvocableHandlerMethod binderMethod : this.binderMethods) {
        // 判断方法上是否有InitBinder注解且符合条件
        if (isBinderMethodApplicable(binderMethod, dataBinder)) {
            // 反射调用binderMethod方法
            Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
            if (returnValue != null) {
                throw new IllegalStateException(
                    "@InitBinder methods must not return a value (should be void): " + binderMethod);
            }
        }
    }
}

protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
    InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
    Assert.state(ann != null, "No InitBinder annotation");
    String[] names = ann.value();
    // value为空或者value包含数据绑定器名称
    return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
}

2.2 执行数据转换

使用WebDataBinder#bind方法转换数据类型,并设置到对象中

public void bind(PropertyValues pvs) {
    MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
                                  (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
    // 执行绑定
    doBind(mpvs);
}

protected void doBind(MutablePropertyValues mpvs) {
    checkFieldDefaults(mpvs);
    checkFieldMarkers(mpvs);
    super.doBind(mpvs);
}

// DataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) {
    checkAllowedFields(mpvs);
    checkRequiredFields(mpvs);
    applyPropertyValues(mpvs);
}

// DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // 获取属性访问器设置属性
        getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    }
    catch (PropertyBatchUpdateException ex) {
        for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
            getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
        }
    }
}

// AbstractPropertyAccessor#setPropertyValues
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
    // 省略其他代码...

    // 循环处理每个名值对
    for (PropertyValue pv : propertyValues) {
        try {
            setPropertyValue(pv);
        }

        // 省略其他代码...
    }

    // 省略其他代码...
}

public void setPropertyValue(PropertyValue pv) throws BeansException {
    // 省略中间调用链,会调用到TypeConverterDelegate#convertIfNecessary
    setPropertyValue(pv.getName(), pv.getValue());
}

通过setPropertyValue会调用到TypeConverterDelegate#convertIfNecessary

public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                                @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
    // 1.首先检查是否有自定义的PropertyEditor,即我们通过@InitBinder扩展的转换方法
    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    
    ConversionFailedException conversionAttemptEx = null;
    
    // 2.如果没有自定义的PropertyEditor,再看有没有ConversionService转换器
    ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
    if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        // 判断该conversionService能否将原始类型sourceTypeDesc转换为实际类型typeDescriptor
        if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
            try {
                // 如果可以转换则通过此ConversionService转换类型
                return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            }
            catch (ConversionFailedException ex) {
                conversionAttemptEx = ex;
            }
        }
    }
    
    Object convertedValue = newValue;
    
    // 如果没有ConversionService转换器,或者ConversionService转换器无法处理
    if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
        if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
            convertedValue instanceof String) {
            TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
            if (elementTypeDesc != null) {
                Class<?> elementType = elementTypeDesc.getType();
                if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
            }
        }
        if (editor == null) {
            // 获取默认的PropertyEditor
            editor = findDefaultEditor(requiredType);
        }
        // 3.通过默认的PropertyEditor执行类型转换
        convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
    }
    
    boolean standardConversion = false;
    
    // 省略其他代码...
    
    return (T) convertedValue;
}

从源码可以看出,在转换过程中会调用到TypeConverterDelegate委派给ConversionService或PropertyEditor真正执行类型转换,转换的优先级如下:

1)首先判断是否有通过@InitBinder扩展的自定义转换器,通过适配器模式将Formatter转换为PropertyEditor,源码见DataBinder#addCustomFormatter

public void addCustomFormatter(Formatter<?> formatter) {
    FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
    getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}

2) 其次再判断能否通过ConversionService进行转换

public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
        return handleResult(null, targetType, convertNullSource(null, targetType));
    }
    if (source != null && !sourceType.getObjectType().isInstance(source)) {
        throw new IllegalArgumentException("Source to convert from must be an instance of [" +
                                           sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
    }
    // 根据原始类型和目标类型获取GenericConverter转换器
    GenericConverter converter = getConverter(sourceType, targetType);
    if (converter != null) {
        // 调用转换器执行数据转换,得到转换结果
        Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
        return handleResult(sourceType, targetType, result);
    }
    return handleConverterNotFound(source, sourceType, targetType);
}

protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
    // 先从缓存中查找
    GenericConverter converter = this.converterCache.get(key);
    if (converter != null) {
        return (converter != NO_MATCH ? converter : null);
    }
    // 在Converters成员变量中查找用户添加的转换器
    converter = this.converters.find(sourceType, targetType);
    if (converter == null) {
        // 如果原始类型是目标类型或其子类,获取默认的转换器NoOpConverter,否则返回null
        converter = getDefaultConverter(sourceType, targetType);
    }

    if (converter != null) {
        this.converterCache.put(key, converter);
        return converter;
    }

    this.converterCache.put(key, NO_MATCH);
    return null;
}

protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
}

3)最后再使用默认的PropertyEditor进行转换,查找内置的PropertyEditor逻辑如下:

private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
    PropertyEditor editor = null;
    if (requiredType != null) {
        // 查找默认的PropertyEditor
        editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
        if (editor == null && String.class != requiredType) {
            editor = BeanUtils.findEditorByConvention(requiredType);
        }
    }
    return editor;
}

public PropertyEditor getDefaultEditor(Class<?> requiredType) {
    if (!this.defaultEditorsActive) {
        return null;
    }
    if (this.overriddenDefaultEditors != null) {
        PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
        if (editor != null) {
            return editor;
        }
    }
    if (this.defaultEditors == null) {
        // 如果defaultEditors为空,创建默认的PropertyEditor
        createDefaultEditors();
    }
    // 获取默认的PropertyEditor
    return this.defaultEditors.get(requiredType);
}

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

    // 创建Java中常见类型的转换器
    this.defaultEditors.put(Charset.class, new CharsetEditor());
    this.defaultEditors.put(Class.class, new ClassEditor());
    this.defaultEditors.put(Class[].class, new ClassArrayEditor());
    this.defaultEditors.put(Currency.class, new CurrencyEditor());
    this.defaultEditors.put(File.class, new FileEditor());
    this.defaultEditors.put(InputStream.class, new InputStreamEditor());
    this.defaultEditors.put(InputSource.class, new InputSourceEditor());
    this.defaultEditors.put(Locale.class, new LocaleEditor());
    this.defaultEditors.put(Path.class, new PathEditor());
    this.defaultEditors.put(Pattern.class, new PatternEditor());
    this.defaultEditors.put(Properties.class, new PropertiesEditor());
    this.defaultEditors.put(Reader.class, new ReaderEditor());
    this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
    this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
    this.defaultEditors.put(URI.class, new URIEditor());
    this.defaultEditors.put(URL.class, new URLEditor());
    this.defaultEditors.put(UUID.class, new UUIDEditor());
    this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());

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

    this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
    this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

    this.defaultEditors.put(char.class, new CharacterEditor(false));
    this.defaultEditors.put(Character.class, new CharacterEditor(true));

    this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
    this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
    
    this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
    this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
    this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
    this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
    this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
    this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
    this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
    this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
    this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
    this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
    this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
    this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
    this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
    this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
    
    if (this.configValueEditorsActive) {
        StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
        this.defaultEditors.put(String[].class, sae);
        this.defaultEditors.put(short[].class, sae);
        this.defaultEditors.put(int[].class, sae);
        this.defaultEditors.put(long[].class, sae);
    }
}
  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lm_ylj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值