spring原理(第十五天)

对象绑定与类型转换

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

  • Printer 把其它类型转为 String

  • Parser 把 String 转为其它类型

  • Formatter 综合 Printer 与 Parser 功能

  • Converter 把类型 S 转为类型 T

  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合

  • FormattingConversionService 利用其它们实现转换

底层第二套转换接口

  • PropertyEditor 把 String 与其它类型相互转换

  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象

  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

高层接口与实现

  • 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)

    • 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)

    • 再看有没有 ConversionService 转换

    • 再利用默认的 PropertyEditor 转换

    • 最后有一些特殊处理

  • SimpleTypeConverter 仅做类型转换

  • BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property

  • DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field

  • ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能

类型转换与数据绑定

基本的类型转换与数据绑定用法

  • 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(通过set,get方法来给属性赋值)

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

      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 +
                     '}';
          }
      }
    

    }

  • DirectFieldAccessor(直接通过属性赋值)

    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

    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 +
                     '}';
          }
      }
    

    }

数据绑定工厂

ServletRequestDataBinderFactory 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器

    • 控制器私有范围
  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器

    • 公共范围
  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级

    1. 优先采用 @InitBinder 的转换器

    2. 其次使用 ConversionService 的转换器

    3. 使用默认转换器

    4. 特殊处理(例如有参构造)

    public class TestServletDataBinderFactory {
    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 + '\'' +
                    '}';
         }
     }
    

    }

在spring中使用ApplicationConversionService来对类型进行转换,无法识别的日期参数加注解 @DateTimeFormat,在jdk中使用DefaultConversionService,他也需要配合注解进行使用,

在这些默认的转换其中,并不需要 @InitBinder 转换ConversionService 转换。

获取泛型参数

  1. java api 获取泛型参数

  2. spring api 获取泛型参数

    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(parameterizedType.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());
     }
    

    }

相关推荐
Adolf_19932 分钟前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
Jarlen2 分钟前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽8 分钟前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
叫我:松哥14 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Reese_Cool15 分钟前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
海里真的有鱼16 分钟前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺27 分钟前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
严文文-Chris40 分钟前
【设计模式-享元】
android·java·设计模式
Flying_Fish_roe1 小时前
浏览器的内存回收机制&监控内存泄漏
java·前端·ecmascript·es6