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

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

  1. 两套底层转换接口,一套高层转换接口
  2. 类型转换和数据绑定示例
  3. 类型转换绑定与绑定器工厂
  4. @DataTimeFormat 注解谁来解析
  5. Spring 提供的泛型操作技巧

1.两套底层转换接口,一套高层转换接口

底层第一套转换接口与实现

«interface»
Formatter
«interface»
Printer
«interface»
Parser
Converters
Set<GenericConverter>
«interface»
Converter
«interface»
ConversionService
FormattingConversionService
Adapter1
Adapter2
Adapter3
  • Printer 把其它类型转为 String
  • ParserString 转为其它类型
  • Formatter 综合 PrinterParser 功能
  • Converter 把类型 S 转为类型 T
  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
  • FormattingConversionService 利用其它们实现转换

底层第二套转换接口

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

高层接口与实现

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

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

SimpleTypeConverter

public class TestSimpleConverter {
    public static void main(String[] args) {
        // 仅有类型转换的功能
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        Integer number = typeConverter.convertIfNecessary("13", int.class);
        Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
        System.out.println(number);
        System.out.println(date);
    }
}

BeanWrapperImpl

public class TestBeanWrapper {
    public static void main(String[] args) {
        // 利用反射原理, 为 bean 的属性赋值
        MyBean target = new MyBean();
        BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
        wrapper.setPropertyValue("a", "10");
        wrapper.setPropertyValue("b", "hello");
        wrapper.setPropertyValue("c", "1999/03/04");
        System.out.println(target);
    }
	
    @Data
    static class MyBean {
        private int a;
        private String b;
        private Date c;
    }
}

DirectFieldAccessor

不需要get set 方法

public class TestFieldAccessor {
    public static void main(String[] args) {
        // 利用反射原理, 为 bean 的属性赋值
        MyBean target = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(target);
        accessor.setPropertyValue("a", "10");
        accessor.setPropertyValue("b", "hello");
        accessor.setPropertyValue("c", "1999/03/04");
        System.out.println(target);
    }

    static class MyBean {
        private int a;
        private String b;
        private Date c;
        @Override
        public String toString() {
            return "MyBean{" +
                   "a=" + a +
                   ", b='" + b + '\'' +
                   ", c=" + c +
                   '}';
        }
    }
}

ServletRequestDataBinder

directFieldAccess 选择走 Property 还是 Field

public class TestDataBinder {

    public static void main(String[] args) {
        // 执行数据绑定
        MyBean target = new MyBean();
        DataBinder dataBinder = new DataBinder(target);
        dataBinder.initDirectFieldAccess();
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("a", "10");
        pvs.add("b", "hello");
        pvs.add("c", "1999/03/04");
        dataBinder.bind(pvs);
        System.out.println(target);
    }

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

        @Override
        public String toString() {
            return "MyBean{" +
                "a=" + a +
                ", b='" + b + '\'' +
                ", c=" + c +
                '}';
        }
    }
}

3.类型转换绑定与绑定器工厂

绑定器工厂

日期绑定不成功

public class TestServletDataBinderFactory {
    public static void main(String[] args) {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "xi.an");

        User target = new User();
        ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println("--"+target);
    }

    @Data
    public static class User {
        private Date birthday;
        private Address address;

    }
    @Data
    public static class Address {
        private String name;

    }
}

在这里插入图片描述

1.使用工厂,无转化功能

ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");

1. 用 @InitBinder 转换

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

    User target = new User();
    // "2. 用 @InitBinder 转换"          PropertyEditorRegistry PropertyEditor
    InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
    ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Arrays.asList(method), null);

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


    System.out.println("--"+target);
}

MyController

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

自定义MyDateFormatter

@Slf4j
public class MyDateFormatter implements Formatter<Date> {

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

在这里插入图片描述

2.用 ConversionService 转换"

//"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);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));

3.同时加了 @InitBinder 和 ConversionService"

// "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(Arrays.asList(method), initializer);

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

优先使用InitBinder

在这里插入图片描述

4.使用默认 ConversionService 转换"

在这里插入图片描述

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

1.jdk api

2.Spring api

BaseDao

class BaseDao<T> {
    T findOne() {
        return null;
    }
}
--------------------------------------------
class StudentDao extends BaseDao<Student> {
}

--------------------------------------------
class Student {
}
----------------------------------------------
class TeacherDao extends BaseDao<Teacher> {
}
---------------------------------------------
class EmployeeDao extends BaseDao {
}

获取泛型参数

public class TestGenericType {
    public static void main(String[] args) {
        // 小技巧
        // 1. java api
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Type type = TeacherDao.class.getGenericSuperclass();
        System.out.println(type);

        if (type instanceof ParameterizedType parameterizedType) {
            System.out.println(
                .getActualTypeArguments()[0]);
        }

        // 2. spring api 1
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
        System.out.println(t);

        // 3. spring api 2
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());
    }

}

总结

ServletRequestDataBinderFactory 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
    • 控制器私有范围
  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
    • 公共范围
  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级
    1. 优先采用 @InitBinder 的转换器
    2. 其次使用 ConversionService 的转换器
    3. 使用默认转换器
    4. 特殊处理(例如有参构造)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值