第二十三讲-数据绑定与类型转换

第二十三讲-对象绑定与类型转换

1. 底层第一套转换接口实现(Spring提供)

底层第一套接口与实现

image-20240808152704296

  • Printer: 把其它类型转为String类型
  • ParserL 把字符串类型转为其它类型
  • Formatter: 综合Printer和Parser功能
  • Converter: 把类型S转为类型T
  • 我们可以将这些类型转换器收集起来形成一个转换器集合,就是Converter集合
  • 由FormattingConversionService利用其它们实现转换

2. 第二套底层转换接口实现(JDK自带)

image-20240808153437432

  • PropertyEditor 把 String类型与其它类型相互转换
  • PropertyEditorRegistry可以注册多个ProeprtyEditor对象
  • 与第一套接口直接通过FormatterPropertyEditorAdapter来进行适配

为什么Spring要使用这两套接口呢?这可能是历史遗留问题吧:)

3. 高层接口转换与实现

image-20240808153938589

  • 它们都实现了TypeConverter这个高层转换接口,在转换时,会用到TypeConverter Delegate委派ConversionService与PropertyEditorRegistry真正执行转换(Facade门面模式)
    • 首先看是否有自定义转换器,@InitBinder添加的即属于这种(用了适配器模式把Formatter转为需要的PropertyEditor)
    • 再看有没有ConversionService转换
    • 再利用默认的PropertyEditor转换
    • 最后有一些特殊处理
  • SimpleTypeConverter 仅仅做类型转换
  • BeanWrapperImpl为bean的属性赋值,当需要时做类型转换,走Property
  • DirectFieldAccessor为bean的属性赋值,当需要时做类型转换,走Field
  • ServletRequestDataBinder为bean的属性执行绑定,当需要时做类型转换,根据directFieldAccess选择走Property还是Field,具备校验与获取校验结果功能

4. 类型绑定和数据转换的示例

下面我们来演示一下类型绑定和数据转换的示例

SimpleTypeConverter转换示例

我们首先采用SimpleTypeConverter转换器做类型转换,如下面的代码:

package com.cherry.a23;

import org.springframework.beans.SimpleTypeConverter;

import java.util.Date;

public class TestSimpleTypeConverter {
    public static void main(String[] args) {
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        // 仅仅做类型转换                                // 要转换的目标, 希望得到的类型
        Integer number = typeConverter.convertIfNecessary("13",int.class);
        Date date = typeConverter.convertIfNecessary("2024/08/08", Date.class);
        System.out.println(number);
        System.out.println(date);
    }
}
13
Thu Aug 08 00:00:00 CST 2024

BeanWrapperImpl转换示例(底层走得是反射调用get,set方法)

package com.cherry.a23;

import org.springframework.beans.BeanWrapperImpl;

import java.util.Date;

public class TestBeanWrapper {
    public static void main(String[] args) {
        // 利用反射机制,为属性赋值,当然,这其中也会涉及到类型转换
        MyBean bean = new MyBean();
        BeanWrapperImpl beanWrapper = new BeanWrapperImpl(bean);
        // 为bean的属性利用进行赋值
        beanWrapper.setPropertyValue("a","10");
        beanWrapper.setPropertyValue("b","hello");
        beanWrapper.setPropertyValue("c","2024/08/08");
        System.out.println(bean.toString());
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }


        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}
MyBean{a=10, b='hello', c=Thu Aug 08 00:00:00 CST 2024}

DirectFieldAccessor转换示例(底层反射直接走的是成员变量,不走get,set方法)

package com.cherry.a23;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.validation.DirectFieldBindingResult;

import java.util.Date;

public class TestDirectFieldAccessor {

    public static void main(String[] args) {
        // 利用反射的原理,为bean的属性赋值
        MyBean bean = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(bean);
        // 为bean的属性利用进行赋值
        accessor.setPropertyValue("a","10");
        accessor.setPropertyValue("b","hello");
        accessor.setPropertyValue("c","2024/08/08");
        System.out.println(bean.toString());
    }
    
    static class MyBean{
        private int a;
        private String b;
        private Date c;

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}
MyBean{a=10, b='hello', c=Thu Aug 08 00:00:00 CST 2024}

DataBinder转换示例

package com.cherry.a23;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.DataBinder;

import java.util.Date;

public class TestDataBinder {

    public static void main(String[] args) {
        // 执行数据绑定
        MyBean bean = new MyBean();
        DataBinder dataBinder = new DataBinder(bean);
        // 定义原始数据
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("a","10");
        pvs.add("b","hello");
        pvs.add("c","2024/08/08");
        // 将原始数据绑定到dataBinder中
        dataBinder.bind(pvs);
        System.out.println(bean.toString());
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}
MyBean{a=10, b='hello', c=Thu Aug 08 00:00:00 CST 2024}

当然,对于DataBinder来讲,它支持两种类型转换一种是基于反射调用set方法赋值,例如上面的代码; 还有一种是基于发射直接给属性赋值,不我们需要手动设置使用DirectFieldAccessor进行数据转换,例如下面:

package com.cherry.a23;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.DataBinder;

import java.util.Date;

public class TestDataBinder {

    public static void main(String[] args) {
        // 执行数据绑定
        MyBean bean = new MyBean();
        DataBinder dataBinder = new DataBinder(bean);
        dataBinder.initDirectFieldAccess(); // 控制dataBinder使用DirectFieldAccessor来实现转换
        // 定义原始数据
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("a","10");
        pvs.add("b","hello");
        pvs.add("c","2024/08/08");
        // 将原始数据绑定到dataBinder中
        dataBinder.bind(pvs);
        System.out.println(bean.toString());
    }

    static class MyBean{
        private int a;
        private String b;
        private Date c;

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}
MyBean{a=10, b='hello', c=Thu Aug 08 00:00:00 CST 2024}

5. 绑定工厂

对于一些不支持的格式,例如自己定义的数据格式,我们要添加自定义的转换器。此时Spring提供的转换器就不够使用了。

对于添加自定义转换器,有两种办法,对应着我们前面讲到的两套接口:

  • 一种是使用ConversionService接口配合Formatter转换器
  • 另一种是使用JDK提供的转换器:PropertyEditorRegistry接口配合PropertyEditor转换器

下面呢,我们就分别演示一下如何使用这两套接口实现自定义转换

package com.cherry.a23;

import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;

import javax.xml.crypto.Data;
import java.util.Date;
import java.util.List;

public class TestBinder1 {
    public static void main(String[] args) throws Exception {
        public static void main (String[]args) throws Exception {
            MockHttpServletRequest request = new MockHttpServletRequest();
            request.setParameter("birthday", "1999|01|02");
            request.setParameter("address.name", "西安");

            User target = new User();
            // "1. 用工厂, 无转换功能"
//        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        
            // "2. 用 @InitBinder 转换"          PropertyEditorRegistry PropertyEditor
//        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
//        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);
        
            // "3. 用 ConversionService 转换"    ConversionService Formatter
//        FormattingConversionService service = new FormattingConversionService();
//        service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
//        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
//        initializer.setConversionService(service);
//        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        
            // "4. 同时加了 @InitBinder 和 ConversionService"
//        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
//
//        FormattingConversionService service = new FormattingConversionService();
//        service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
//        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
//        initializer.setConversionService(service);
//
//        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);
            // "5. 使用默认 ConversionService 转换"
            ApplicationConversionService service = new ApplicationConversionService();
            ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
            initializer.setConversionService(service);

            ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

            WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
            dataBinder.bind(new ServletRequestParameterPropertyValues(request));
            System.out.println(target);
        }
    }


        static class MyController {
        @InitBinder     //
        public void aaa(WebDataBinder dataBinder) {
            // 扩展 dataBinder 的转换器的功能
            dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
        }
    }

    public static class User {
        @DateTimeFormat(pattern = "yyyy|MM|dd")
        private Date birthday;
        private Address address;

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        public Date getBirthday() {
            return birthday;
        }

        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }

        @Override
        public String toString() {
            return "User{" +
                    "birthday=" + birthday +
                    ", address=" + address +
                    '}';
        }
    }

    public static class Address {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Address{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}



public class MyDateFormatter implements Formatter<Date> {
    private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
    private final String desc;

    public MyDateFormatter(String desc) {
        this.desc = desc;
    }

    @Override
    public String print(Date date, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.format(date);
    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        log.debug(">>>>>> 进入了: {}", desc);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }
}

6. Spring提供的泛型操作技巧

我们在实际开发时(开发框架时),会有这样的需求,我这里有一个父类,其参数为泛型,现在子类继承了这个父类,那么我们如何拿到这个父类的泛型参数呢?

这里有两种办法:

  1. 使用JDK API获取泛型参数
  2. 使用Spring提供的API获取泛型参数

如下面的代码:

package com.cherry.a23;

import org.springframework.core.GenericTypeResolver;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

class BaseDao<T> {
    T findOne() {return null;}
}

class TeacherDao extends BaseDao<Student>{ }

class Student{ }

class StudentDao extends BaseDao<Student>{
    public static void main(String[] args) {
        // 使用JDK发生获取父类泛型参数
        Type type = TeacherDao.class.getGenericSuperclass();
        System.out.println(type);   // com.cherry.a23.BaseDao<com.cherry.a23.Student>
        // 将type进行类型转换
        if (type instanceof ParameterizedType parameterizedType) {
            // 获取泛型参数
            System.out.println(parameterizedType.getActualTypeArguments()[0]);
        }

        System.out.println("=======================");
        // 使用Spring提供的API获取父类的泛型参数
        Class<?> genericParamter = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
        System.out.println(genericParamter);
    }
}
com.cherry.a23.BaseDao<com.cherry.a23.Student>
class com.cherry.a23.Student
=======================
class com.cherry.a23.Student

Process finished with exit code 0
posted @   LilyFlower  阅读(4)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示