一、資料綁定
頁面送出的資料都是字元串,要想和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;
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL3UjNwAjMxEDMzATNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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;