第二十三讲-数据绑定与类型转换
第二十三讲-对象绑定与类型转换
1. 底层第一套转换接口实现(Spring提供)
底层第一套接口与实现
- Printer: 把其它类型转为String类型
- ParserL 把字符串类型转为其它类型
- Formatter: 综合Printer和Parser功能
- Converter: 把类型S转为类型T
- 我们可以将这些类型转换器收集起来形成一个转换器集合,就是Converter集合
- 由FormattingConversionService利用其它们实现转换
2. 第二套底层转换接口实现(JDK自带)
- PropertyEditor 把 String类型与其它类型相互转换
- PropertyEditorRegistry可以注册多个ProeprtyEditor对象
- 与第一套接口直接通过FormatterPropertyEditorAdapter来进行适配
为什么Spring要使用这两套接口呢?这可能是历史遗留问题吧:)
3. 高层接口转换与实现
- 它们都实现了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提供的泛型操作技巧
我们在实际开发时(开发框架时),会有这样的需求,我这里有一个父类,其参数为泛型,现在子类继承了这个父类,那么我们如何拿到这个父类的泛型参数呢?
这里有两种办法:
- 使用JDK API获取泛型参数
- 使用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
分类:
Spring 高级49讲
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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语句:使用策略模式优化代码结构