spring 22 对象绑定与类型转换
底层第一套转换接口与实现
classDiagram
Formatter --|> Printer
Formatter --|> Parser
class Converters {
Set~GenericConverter~
}
class Converter
class ConversionService
class FormattingConversionService
ConversionService <|-- FormattingConversionService
FormattingConversionService o-- Converters
Printer --> Adapter1
Adapter1 --> Converters
Parser --> Adapter2
Adapter2 --> Converters
Converter --> Adapter3
Adapter3 --> Converters
<<interface>> Formatter
<<interface>> Printer
<<interface>> Parser
<<interface>> Converter
<<interface>> ConversionService
- Printer 把其它类型转为 String
- Parser 把 String 转为其它类型
- Formatter 综合 Printer 与 Parser 功能
- Converter 把类型 S 转为类型 T
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
- FormattingConversionService 利用其它们实现转换
底层第二套转换接口
classDiagram
PropertyEditorRegistry o-- "多" PropertyEditor
<<interface>> PropertyEditorRegistry
<<interface>> PropertyEditor
- PropertyEditor 把 String 与其它类型相互转换
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
高层接口与实现
classDiagram
TypeConverter <|-- SimpleTypeConverter
TypeConverter <|-- BeanWrapperImpl
TypeConverter <|-- DirectFieldAccessor
TypeConverter <|-- ServletRequestDataBinder
SimpleTypeConverter --> TypeConverterDelegate
BeanWrapperImpl --> TypeConverterDelegate
DirectFieldAccessor --> TypeConverterDelegate
ServletRequestDataBinder --> TypeConverterDelegate
TypeConverterDelegate --> ConversionService
TypeConverterDelegate --> PropertyEditorRegistry
<<interface>> TypeConverter
<<interface>> ConversionService
<<interface>> PropertyEditorRegistry
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field
- DataBinder 普通环境下的属性数据绑定类型转换
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能
类型转换与数据绑定
点击查看代码
//仅有类型转换功能
SimpleTypeConverter converter = new SimpleTypeConverter();
System.out.println(converter.convertIfNecessary("11", int.class));
System.out.println(converter.convertIfNecessary("2011/01/02", Date.class));
//类型转换与数据绑定 ,根据反射,用 set 方法为 bean 属性赋值
BeanWrapperImpl beanWrapper = new BeanWrapperImpl();
S22Bean1 s22Bean1 = new S22Bean1();
beanWrapper.setBeanInstance(s22Bean1);
beanWrapper.setPropertyValue("name",13);
beanWrapper.setPropertyValue("age","32");
beanWrapper.setPropertyValue("date","2011/11/20");
System.out.println(s22Bean1);
//类型转换与数据绑定, 利用反射,直接赋值给 bean 的属性,不需要 set 方法
S22Bean2 s22Bean2 = new S22Bean2();
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(s22Bean2);
fieldAccessor.setPropertyValue("name",22);
fieldAccessor.setPropertyValue("age","19");
System.out.println(s22Bean2);
//类型转换与数据绑定,根据 DirectFieldAccess 存不存在判断用哪种方法数据绑定 默认用 set 方法
S22Bean2 s22Bean2 = new S22Bean2();
DataBinder dataBinder = new DataBinder(s22Bean2);
dataBinder.initDirectFieldAccess();
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("name", 34);
propertyValues.add("age", "12");
dataBinder.bind(propertyValues);
System.out.println(s22Bean2);
//类型转换与数据绑定,根据 DirectFieldAccess 存不存在判断用哪种方法数据绑定 默认用 set 方法
S22Bean2 s22Bean2 = new S22Bean2();
DataBinder dataBinder = new ServletRequestDataBinder(s22Bean2);
//可直接对属性进行绑定
dataBinder.initDirectFieldAccess();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name","13");
request.setParameter("age","13");
request.setParameter("date","2011/2/2");
ServletRequestParameterPropertyValues propertyValues = new ServletRequestParameterPropertyValues(request);
//也可以使用此方式进行数据绑定类型转换
// propertyValues.add("name", 34);
// propertyValues.add("age", "12");
dataBinder.bind(propertyValues);
System.out.println(s22Bean2);
- SimpleTypeConverter
- BeanWrapperImpl
- DirectFieldAccessor
- ServletRequestDataBinder
数据绑定工厂
点击查看代码
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 + '\'' +
'}';
}
}
}
- 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
- 控制器私有范围
- 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
- 公共范围
- 同时加了 @InitBinder 和 ConversionService 的转换优先级
- 优先采用 @InitBinder 的转换器
- 其次使用 ConversionService 的转换器
- 使用默认转换器
- 特殊处理(例如有参构造)
获取泛型参数
点击查看代码
// 小技巧
// 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());