1.数据绑定工厂的使用
数据绑定工厂能够创建数据绑定器,将数据绑定到对象中,比如说当接收到请求时,经过http协议解析后数据刚开始都是字符串,此时我们希望将这些属性进行类型转换,并为对象赋值,示例如下:
1)先创建两个实体类Student和Teacher
@Getter
@Setter
@ToString
public class Student {
private String name;
private int age;
private Date birthday;
private Teacher teacher;
}
@Getter
@Setter
@ToString
public class Teacher {
private String name;
}
2)使用ServletRequestDataBinderFactory工厂创建数据绑定器,进行数据绑定
public class Test01 {
public static void main(String[] args) throws Exception {
// 模拟一个Web请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 简单字符串属性
request.setParameter("name", "lm");
// 需要将字符串转换为整型
request.setParameter("age", "28");
// 需要能够解析自定义格式
request.setParameter("birthday", "1999-01-02");
// 需要能够绑定到对象中的属性
request.setParameter("teacher.name", "lhm");
Student student = new Student();
// 使用数据绑定工厂创建数据绑定器,此时没有传入bindMethod或initializer
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(student);
}
}
执行Test01测试,绑定失败,会报如下错误:
这是因为我们的数据绑定工厂中没有传入bindMethod或者initializer,无法自定义解析日期格式,创建Date对象时没有使用默认的格式导致解析错误,此时我们有如下2种方式完成自定义数据格式的绑定
1.1 向数据绑定工厂中传入bindMethod
通过@InitBinder注解向数据绑定工厂中添加bindMethod
1)首先创建自定义的转换器类
public class DateFormatter implements Formatter<Date> {
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd");
@Override
public Date parse(String text, Locale locale) throws ParseException {
return simpleDateFormat.parse(text);
}
@Override
public String print(Date date, Locale locale) {
return simpleDateFormat.format(date);
}
}
2)创建一个类并添加@InitBinder修饰的转换方法
public class MyBinder {
@InitBinder
public void bind(WebDataBinder webDataBinder) {
// 添加自定义的转换器
webDataBinder.addCustomFormatter(new DateFormatter());
}
}
3)修改测试代码,在数据转换工厂中添加bindMethod,即可绑定成功
public class Test02 {
public static void main(String[] args) throws Exception {
// 模拟一个Web请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 简单字符串属性
request.setParameter("name", "lm");
// 需要将字符串转换为整型
request.setParameter("age", "28");
// 需要能够解析自定义格式
request.setParameter("birthday", "1999-01-02");
// 需要能够绑定到对象中的属性
request.setParameter("teacher.name", "lhm");
Student student = new Student();
// 添加bindMethod
InvocableHandlerMethod method = new InvocableHandlerMethod(new MyBinder(), MyBinder.class.getMethod("bind", WebDataBinder.class));
// 使用数据绑定工厂创建数据绑定器,此时没有传入bindMethod或initializer
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Arrays.asList(method), null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
// 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
System.out.println(student);
}
}
1.2 向数据绑定工厂中传入initializer
修改测试代码,在数据转换工厂中添加initializer,使用ConversionService接口进行转换,即可绑定成功
public class Test03 {
public static void main(String[] args) throws Exception {
// 模拟一个Web请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 简单字符串属性
request.setParameter("name", "lm");
// 需要将字符串转换为整型
request.setParameter("age", "28");
// 需要能够解析自定义格式
request.setParameter("birthday", "1999-01-02");
// 需要能够绑定到对象中的属性
request.setParameter("teacher.name", "lhm");
Student student = new Student();
// 创建ConversionService,添加自定义的转换器
FormattingConversionService conversionService = new FormattingConversionService();
conversionService.addFormatter(new DateFormatter());
// 创建initializer对象,添加ConversionService
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
// 使用数据绑定工厂创建数据绑定器,此时传入initializer
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
// 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
System.out.println(student);
}
}
如果同时传入了bindMethod和initializer,会优先使用bindMethod
另外ApplicationConversionService中添加了许多默认的转换器,可以直接使用
但是在使用前,需要在字段上添加相应的注解,例如date类型上添加@DateTimeFormat注解即可完成绑定
@Getter
@Setter
@ToString
public class Student {
private String name;
private int age;
@DateTimeFormat(pattern = "yyyy-mm-dd")
private Date birthday;
private Teacher teacher;
}
public class Test04 {
public static void main(String[] args) throws Exception {
// 模拟一个Web请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 简单字符串属性
request.setParameter("name", "lm");
// 需要将字符串转换为整型
request.setParameter("age", "28");
// 需要能够解析自定义格式
request.setParameter("birthday", "1999-01-02");
// 需要能够绑定到对象中的属性
request.setParameter("teacher.name", "lhm");
Student student = new Student();
// 创建ConversionService,使用默认的转换器
FormattingConversionService conversionService = new ApplicationConversionService();
// 创建initializer对象,添加ConversionService
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
// 使用数据绑定工厂创建数据绑定器,此时传入initializer
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), student, "student");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));
// 打印Student(name=lm, age=28, birthday=Sat Jan 02 00:01:00 CST 1999, teacher=Teacher(name=lhm))
System.out.println(student);
}
}
2.数据绑定原理
2.1 创建数据绑定器
示例代码使用factory.createBinder通过ServletRequestDataBinderFactory创建dataBinder,源码如下:
public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
// 1、创建dataBinder
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
// 2.如果构造工厂时传入了initializer,则初始化ConversionService方式扩展的绑定器
this.initializer.initBinder(dataBinder, webRequest);
}
// 3.初始化@InitBinder方式扩展的绑定器
initBinder(dataBinder, webRequest);
return dataBinder;
}
// ServletRequestDataBinderFactory#createBinderInstance
protected ServletRequestDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {
// 返回ExtendedServletRequestDataBinder
return new ExtendedServletRequestDataBinder(target, objectName);
}
// ConfigurableWebBindingInitializer#initBinder
public void initBinder(WebDataBinder binder) {
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.validator != null && binder.getTarget() != null &&
this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
if (this.conversionService != null) {
// 设置conversionService
binder.setConversionService(this.conversionService);
}
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
// InitBinderDataBinderFactory#initBinder
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
// 遍历传入的binderMethods
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
// 判断方法上是否有InitBinder注解且符合条件
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
// 反射调用binderMethod方法
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
if (returnValue != null) {
throw new IllegalStateException(
"@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
Assert.state(ann != null, "No InitBinder annotation");
String[] names = ann.value();
// value为空或者value包含数据绑定器名称
return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
}
2.2 执行数据转换
使用WebDataBinder#bind方法转换数据类型,并设置到对象中
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
// 执行绑定
doBind(mpvs);
}
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
// DataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
// DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// 获取属性访问器设置属性
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
// AbstractPropertyAccessor#setPropertyValues
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
// 省略其他代码...
// 循环处理每个名值对
for (PropertyValue pv : propertyValues) {
try {
setPropertyValue(pv);
}
// 省略其他代码...
}
// 省略其他代码...
}
public void setPropertyValue(PropertyValue pv) throws BeansException {
// 省略中间调用链,会调用到TypeConverterDelegate#convertIfNecessary
setPropertyValue(pv.getName(), pv.getValue());
}
通过setPropertyValue会调用到TypeConverterDelegate#convertIfNecessary
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 1.首先检查是否有自定义的PropertyEditor,即我们通过@InitBinder扩展的转换方法
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// 2.如果没有自定义的PropertyEditor,再看有没有ConversionService转换器
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
// 判断该conversionService能否将原始类型sourceTypeDesc转换为实际类型typeDescriptor
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
// 如果可以转换则通过此ConversionService转换类型
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// 如果没有ConversionService转换器,或者ConversionService转换器无法处理
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
// 获取默认的PropertyEditor
editor = findDefaultEditor(requiredType);
}
// 3.通过默认的PropertyEditor执行类型转换
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
// 省略其他代码...
return (T) convertedValue;
}
从源码可以看出,在转换过程中会调用到TypeConverterDelegate委派给ConversionService或PropertyEditor真正执行类型转换,转换的优先级如下:
1)首先判断是否有通过@InitBinder扩展的自定义转换器,通过适配器模式将Formatter转换为PropertyEditor,源码见DataBinder#addCustomFormatter
public void addCustomFormatter(Formatter<?> formatter) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}
2) 其次再判断能否通过ConversionService进行转换
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
// 根据原始类型和目标类型获取GenericConverter转换器
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
// 调用转换器执行数据转换,得到转换结果
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
return handleConverterNotFound(source, sourceType, targetType);
}
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
// 先从缓存中查找
GenericConverter converter = this.converterCache.get(key);
if (converter != null) {
return (converter != NO_MATCH ? converter : null);
}
// 在Converters成员变量中查找用户添加的转换器
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
// 如果原始类型是目标类型或其子类,获取默认的转换器NoOpConverter,否则返回null
converter = getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
this.converterCache.put(key, converter);
return converter;
}
this.converterCache.put(key, NO_MATCH);
return null;
}
protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
}
3)最后再使用默认的PropertyEditor进行转换,查找内置的PropertyEditor逻辑如下:
private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
PropertyEditor editor = null;
if (requiredType != null) {
// 查找默认的PropertyEditor
editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
if (editor == null && String.class != requiredType) {
editor = BeanUtils.findEditorByConvention(requiredType);
}
}
return editor;
}
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
if (!this.defaultEditorsActive) {
return null;
}
if (this.overriddenDefaultEditors != null) {
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
if (editor != null) {
return editor;
}
}
if (this.defaultEditors == null) {
// 如果defaultEditors为空,创建默认的PropertyEditor
createDefaultEditors();
}
// 获取默认的PropertyEditor
return this.defaultEditors.get(requiredType);
}
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// 创建Java中常见类型的转换器
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Path.class, new PathEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}