转载

Spring Mvc(数据绑定) DataBinding 过程源码分析

无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper 接口,本文主要讲解BeanWrapper 在spring mvc的数据绑定阶段的应用

执行数据绑定的序列图如下

Spring Mvc(数据绑定) DataBinding 过程源码分析

PropertyEditor接口和ConversionService服务

数据绑定主要分两步,从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等对象,那么相应的特殊处理

ConversionService接口的注入过程

当spring 上下文启动时, 解析如下元素

<mvc:annotation-driven/>

如果annotation-driven 元素没有conversion-service属性,那么直接添加一个表示FormattingConversionServiceFactoryBean的BeanDefinitio到DefaultListableBeanFactory中

在FormattingConversionServiceFactoryBean.afterPropertiesSet() 中将查找到的converter和formatter注册到conversionService中。

Formatter接口

为了更好的适应数据在字符串与各种类型中转换, spring mvc定义了新的Formatter接口

public interface Formatter extends Printer, Parser {}

然后分别定义了ParserConverter(Parser.parse())和PrinterConverter(Printer.print())将新的接口集成到ConversionService中,见序列图中ParserConverter的调用链

@ModelAttribute 注解

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中,只是可能名称不同而已
}

DataBinding失败处理

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

原文  http://www.javacoder.cn/?p=1204
正文到此结束
Loading...