无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper 接口,本文主要讲解BeanWrapper 在spring mvc的数据绑定阶段的应用
执行数据绑定的序列图如下
数据绑定主要分两步,从HttpServletRequest取原始的数据,用户输入的内容大多是字符串表示的值,比如日期表示为'yyyy-MM-dd'格式的字符串;第二步就是将字符串形式的值转换为对应的类型,比如将'yyyy-MM-dd'转换为Date类型。这时就需要借助于spring 的类型转换系统了(TypeConversion)
主要的决断逻辑在
TypeConverterDelegate.convertIfNecessary() 方法中,主要的逻辑如下
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
Object convertedValue = newValue;
// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException firstAttemptEx = null;
// No custom editor but custom ConversionService specified?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
TypeDescriptor targetTypeDesc = typeDescriptor;
if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {
return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);
}
}
// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate.
}
return (T) convertedValue;
}
主要的逻辑归纳如下:
如果没有自定义的PropertyEditor,那么使用ConversionService服务来转换
如果ConversionService 不支持该类型的数据转换,且有自定义的PropertyEditor,或者有默认的PropertyEditor, 那么使用PropertyEditor进行转换
如果数据类型不匹配,比如是一个数组,map, List等对象,那么相应的特殊处理
当spring 上下文启动时, 解析如下元素
<mvc:annotation-driven/>
如果annotation-driven 元素没有conversion-service属性,那么直接添加一个表示FormattingConversionServiceFactoryBean的BeanDefinitio到DefaultListableBeanFactory中
在FormattingConversionServiceFactoryBean.afterPropertiesSet() 中将查找到的converter和formatter注册到conversionService中。
为了更好的适应数据在字符串与各种类型中转换, spring mvc定义了新的Formatter接口
public interface Formatter extends Printer, Parser {}
然后分别定义了ParserConverter(Parser.parse())和PrinterConverter(Printer.print())将新的接口集成到ConversionService中,见序列图中ParserConverter的调用链
ModelAttributeMethodProcessor.resolveArgument(ModelAndViewContainer mavContainer, ...) {
//如果该参数使用@ModelAttribute("user")注解,那么该参数在model中的名称就为@ModelAttribute的值,否则为参数类型按照java命名约定的值,比如类"UserVo"的name为"userVo"。
String name = ModelFactory.getNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
//所以无论你是否使用@ModelAttribute注解,dataBing的结果都会存入Model中,只是可能名称不同而已
}
Controller中常有如下样例代码
@RequestMapping("initAddUser")
public String initAddUser(Model model) {
model.addAttribute("userVo", new UserVo());
return "WEB-INF/views/initAddUser.jsp";
}
@RequestMapping("saveUser")
public String saveUser(@Valid @ModelAttribute("user") UserVo user, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "WEB-INF/views/initAddUser.jsp";
}
this.userList.add(user);
return "redirect://userProfile/" + user.getUsername() + ".do";
}
在每个binding参数后,一般会跟一个BindingResult对象,该对象表示DataBinding是否成功, 注意,当绑定失败时和initAddUser请求处理器返回相同的逻辑view。
介绍@Valid的文章很多,这里提示一下嵌套属性的验证
public class UserListWrapper {
@NotEmpty(message="userList must provided")
@Valid
private List<UserVo> userList = new ArrayList<UserVo>();
}
UserListWrapper的userList属性的类型为List<UserVo> , 为了让UserVo中的校验规则(constraints)被验证, 需要在userList属性上添加@Valid注解,如果使用的jdk为jdk8及以上, 可以按如下格式改写
private List<@Valid UserVo> userList = new ArrayList();
Posted in:spring practise