无论是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