一、数据绑定
页面提交的数据都是字符串,要想和JavaBean进行一一绑定就会牵扯到数据绑定期间的数据类型转换、数据格式化、数据校验的问题。
数据绑定发生在
ModelAttributeMethodProcessor
这个类中的
bindRequestParameters(binder, webRequest)
方法,这个方法将页面提交过来的数据封装到JavaBean的属性中,封装期间可能会有数据类型转换、数据格式化、数据校验的问题。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else {
try {
attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
} catch (BindException var10) {
if (this.isBindExceptionRequired(parameter)) {
throw var10;
}
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = var10.getBindingResult();
}
}
if (bindingResult == null) {
//数据绑定器,进行绑定
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//将页面提交过来的数据封装到JavaBean的属性中
this.bindRequestParameters(binder, webRequest);
}
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder
类是数据绑定器,继承于
DataBinder
类。负责数据绑定工作,负责处理数据类型转换、数据格式化、数据校验的问题。
DataBinder
类有一个
ConversionService
接口属性,负责数据类型的转换以及格式化功能,
ConversionService
中有非常多的
converter
,不同类型的转换和格式化用不同的
converter
来转换。
@Nullable
private ConversionService conversionService;
validate()
方法负责数据校验工作,其中
bindingResult
对象负责保存以及解析数据绑定期间数据校验产生的错误。
public void validate() {
Object target = this.getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = this.getBindingResult();
Iterator var3 = this.getValidators().iterator();
while(var3.hasNext()) {
Validator validator = (Validator)var3.next();
validator.validate(target, bindingResult);
}
}
public void validate(Object... validationHints) {
Object target = this.getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = this.getBindingResult();
Iterator var4 = this.getValidators().iterator();
while(true) {
while(var4.hasNext()) {
Validator validator = (Validator)var4.next();
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator)validator).validate(target, bindingResult, validationHints);
} else if (validator != null) {
validator.validate(target, bindingResult);
}
}
return;
}
}
二、数据绑定流程
①SpringMVC主框架将
ServletRequest
对象及目标方法的入参实例传递给
WebDataBinderFactory
实例,以创建
WebDataBinder
实例对象。
②
WebDataBinder
调用装配在 SpringMVC 上下文中的
ConversionService
组件进行数据类型转换、数据格式化工作。将
Servlet
中的请求信息填充到入参对象中。
③调用
Validator
组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果
BindingData
对象。
④SpringMVC抽取
BindingResult
中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。
SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是
DataBinder
,运行机制如下:
三、自定义类型转换器
ConversionService
是一个接口,里面有
Converter
(转换器)进行工作
在Spring中定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到
ConversionServiceFactoryBean
中
-
将S类型对象转化为T类型对象。Convert<S,T>
-
将相同系列多个“同质”ConvertFactory
封装在一起。如果将一种类型的对象转换为另一种类型及其子类的对象,例如将Convert
转换为String
及Number
子类(Number
、Integer
、Long
等)对象,可以使用这个转换器工厂类。Double
-
会根据源类对象及目标类对象所在的宿主类的上下文信息进行类型转换。GenericConverter
首先实现
Converter
接口,写一个自定义的类型转换器
public class MyStringToEmployeeConverter implements Converter<String,Employee> {
@Autowired
private Department department;
//自定义的转换规则
@Override
public Employee convert(String s) {
Employee employee = new Employee();
if (s.contains("-")){
String[] split = s.split("-");
employee.setLastName(split[0]);
employee.setEmail(split[1]);
employee.setGender(Integer.parseInt(split[2]));
employee.setDepartmentId(Integer.parseInt(split[3]));
}
return null;
}
}
接下来配置
ConversionService
,
Converter
是
ConversionService
中的组件,所以需要将自定义的
Converter
放进
ConversionService
中,并且将
WebDataBinder
中
ConversionService
设置成包含自定义类型转换器的
ConversionService
<!--设置conversion-service,使用自定义的类型转换组件-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!--告诉SpringMVC不要用默认的ConversionService,而是用自定义的ConversionService,里面包含自定义的Converter-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--converters转换器是一个set集合,向其中添加自定义的类型转换器-->
<property name="converters">
<set>
<bean class="component.MyStringToEmployeeConverter"></bean>
</set>
</property>
</bean>
页面
form
表单提交
<form action="quickAdd">
<%--将员工的所有信息都写上,自动封装对象--%>
<input name="empinfo" value="[email protected]"/>
<input type="submit" value="快速添加"/>
</form>
Controller
控制器目标方法
@RequestMapping("/quickAdd")
public String quickAdd(Employee employee){
employeeService.save(employee);
return "redirect:/emps";
}
输出结果:
关于自定义类型转换器总结为三步:
- 实现
接口,做一个自定义类型的转换器Converter
- 将这个
配置在Converter
中ConversionService
- 设置
让SpringMVC使用自己配置的类型转换组件conversion-service
即使使用了自己配置的转换器,其它默认的转换器还在,只不过是将自定义的转换器加入了其中,所以执行其它类型的转换让仍然可以。
四、 <mvc:annotation-driven>
标签的解析
<mvc:annotation-driven>
<mvc:annotation-driven>
标签会自动注册
RequestMappingHandlerMapping
、
RequestMappingHandlerAdapter
、
RequestMappingExceptionResolver
这三个Bean。
除此之外还将支持:
- 支持使用
实例对表单参数进行类型转换ConversionService
- 支持使用
注解、@NumberFormat
注解完成数据类型的格式化@DateTimeFormat
- 支持使用
注解对JavaBean实例进行JSR 303验证@Valid
- 支持使用
和@RequestBody
注解@ResponseBody
SpringMVC会解析这个标签添加了哪些组件
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
XmlReaderContext readerContext = context.getReaderContext();
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
context.pushContainingComponent(compDefinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
configurePathMatchingProperties(handlerMappingDef, element, context);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
RuntimeBeanReference conversionService = getConversionService(element, source, context);
RuntimeBeanReference validator = getValidator(element, source, context);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<?> messageConverters = getMessageConverters(element, source, context);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
String asyncTimeout = getAsyncTimeout(element);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);
if (element.hasAttribute("ignore-default-model-on-redirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
RootBeanDefinition uriContributorDef =
new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
uriContributorDef.setSource(source);
uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(source);
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(methodExceptionResolver);
if (argumentResolvers != null) {
methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
statusExceptionResolver.setSource(source);
statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
statusExceptionResolver.getPropertyValues().add("order", 1);
String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);
context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(context, source);
context.popAndRegisterContainingComponent();
return null;
}
一般来说,只要请求出现问题加上
<mvc:annotation-driven/>
就可以解决大多数问题。
- 对于
和<mvc:default-servlet-handler></mvc:default-servlet-handler>
这两个标签,如果都没有配置,动态资源(<mvc:annotation-driven></mvc:annotation-driven>
映射的资源)都能访问,但是静态资源(.html, .js, .img)都不能访问。@RequestMapping
handlerMappings
handlerMappings
里面保存了请求映射信息,其中是
DefaultAnnotationHandlerMapping
在工作,请求一过来就会挨个遍历所有的
HandlerMapping
看哪个保存了发送来的请求的映射信息。先查看
BeanNameURLHandlerMapping
,而
BeanNameURLHandlerMapping
里面是
{}
空的,所以就查看下一个
DefaultAnnotationHandlerMapping
DefaultAnnotationHandlerMapping
里面有一个
handlerMap
,保存了哪个请求对应哪个类可以处理,匹配成功就可以处理请求。如果处理成功,就说明动态资源能够访问,能访问的原因就是
DefaultAnnotationHandlerMapping
中的
handlerMap
保存了每一个请求的对应的处理类,即保存了每一个资源的映射信息。
但是静态资源不能访问,原因是
handlerMap
中并没有保存静态资源请求的映射信息,所以当js或者html请求过来时,并不知道哪个处理器类可以处理。
handlerAdapters
handlerAdapters
是方法执行的适配器,其中
AnnotationMethodHandlerAdapter
帮我们来执行目标方法
- 如果只配置
静态资源可以访问,但是动态资源无法访问。<mvc:default-servlet-handler></mvc:default-servlet-handler>
HandlerMappings
对比两个标签都没有配置的情况,
HandlerMappings
中多了一个
SimpleUrlHandlerMapping
,却少了保存动态请求映射的
DefaultAnnotationHandlerMapping
,所以就无法访问动态资源。
而
SimpleUrlHandlerMapping
的
handlerMap
中
/**
是把所有的请求都交给了
DefaultServletHttpRequestHandler
,也就是都交给了Tomcat来处理。
所以,
SimpleUrlHandlerMapping
替换了
DefaultAnnotationHandlerMapping
,将所有的请求直接交给了Tomcat处理,而一些动态资源的请求映射并没有在Tomcat中配置,所以就只能访问静态资源,动态资源无法访问。
handlerAdapters
少了
AnnotationMethodHandlerAdapter
,所以也就不会运行目标方法去处理请求
- 如果两个配置都加上,静态和动态资源就都能访问。
HandlerMappings
如果两个配置都加上,
HandlerMappings
中就有3个
handlerMapping
,
SimpleUrlHandlerMapping
将请求直接交给Tomcat,所以可以访问静态资源。
而多了一个
RequestMappingHandlerMapping
并放在第一位,它里面有一个
urlMap
,保存了请求的相关信息。
RequestMappingHandlerMapping
里面还有一个
handlerMethods
,保存了每一个请求用哪个方法来处理,所以动态资源可以映射到,进而也就可以访问动态资源了。
handlerAdapters
多了
RequestMappingHandlerAdapter
,替代了原来的
AnnotationMethodHandlerAdapter
五、数据格式化
日期格式化
如果页面提交的数据格式不正确,我们希望报400错误,并且制定日期格式为
yyyy-MM-dd
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
ConversionServiceFactoryBean
创建的
ConversionService
组件是没有格式化器存在的,要么去掉自定义类型转换器,使用默认的转换器;要么使用
FormattingConversionServiceFactoryBean
<!--设置conversion-service,使用自定义的类型转换组件-->
<mvc:annotation-driven conversion-service="formattingConversionService"></mvc:annotation-driven>
<!--告诉SpringMVC不要用默认的ConversionService,而是用自定义的ConversionService,里面包含自定义的Converter-->
<bean id="formattingConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!--converters转换器是一个set集合,向其中添加自定义的类型转换器-->
<property name="converters">
<set>
<bean class="component.MyStringToEmployeeConverter"></bean>
</set>
</property>
</bean>
数值格式化
可以指定页面输出数值的格式
@NumberFormat(pattern = "#,###,###.####")
private Double salary;