[Re] SpringMVC-4
阿新 • • 發佈:2020-09-15
資料繫結
SpringMVC 封裝自定義型別物件的時候,JavaBean 要和頁面提交的資料進行一一繫結。但頁面提交的資料都是字串,而伺服器端 Java 資料型別各種各樣。
牽扯到以下操作:
- 資料繫結期間的資料型別轉換,如:name=root&age=35
- 資料繫結期間的資料格式化,如:日期時間格式化
- 資料繫結期間的資料校驗,不僅要有前端(JS+正則)校驗,也要有後端校驗
原理
新的原始碼在 ModelAttributeMethodProcessor:
// 工廠建立資料繫結器 WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { // 將頁面提交過來的資料封裝到 JavaBean 的屬性中 bindRequestParameters(binder, request); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } }
資料繫結器 WebDataBinder 負責資料繫結工作。資料繫結期間產生的型別轉換、格式化。
ConversionService 中如下所示,內建了很多 Converter。不同型別的轉換和格式化用它自己的 Converter。
ConversionService converters = java.lang.Long -> java.lang.String: DateTimeFormatAnnotationFormatterFactory@6434d4f2,NumberFormat java.lang.Long -> java.lang.String: java.time.LocalDate -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.LocalDate -> java.lang.String : standard.TemporalAccessorPrinter@2c7927e6 java.time.LocalDateTime -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.LocalDateTime -> java.lang.String : standard.TemporalAccessorPrinter@d6b3e27 java.time.LocalTime -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.LocalTime -> java.lang.String : standard.TemporalAccessorPrinter@35e809fb java.time.OffsetDateTime -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.OffsetDateTime -> java.lang.String : standard.TemporalAccessorPrinter@c6d02e6 java.time.OffsetTime -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.OffsetTime -> java.lang.String : standard.TemporalAccessorPrinter@64423271 java.time.ZonedDateTime -> java.lang.String: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.time.ZonedDateTime -> java.lang.String : standard.TemporalAccessorPrinter@6166f845 java.util.Calendar -> java.lang.String: DateTimeFormatAnnotationFormatterFactory@6434d4f2 java.util.Date -> java.lang.String: DateTimeFormatAnnotationFormatterFactory@6434d4f2 NumberFormat java.lang.Double -> java.lang.String: NumberFormat java.lang.Float -> java.lang.String: NumberFormat java.lang.Integer -> java.lang.String: NumberFormat java.lang.Short -> java.lang.String: NumberFormat java.math.BigDecimal -> java.lang.String: NumberFormat java.math.BigInteger -> java.lang.String: java.lang.Boolean -> java.lang.String : ObjectToStringConverter@29e583ed java.lang.Character -> java.lang.Number : CharacterToNumberFactory@4b9fbd0a java.lang.Character -> java.lang.String : ObjectToStringConverter@67f8467 java.lang.Enum -> java.lang.String : EnumToStringConverter@13c965f6 java.lang.Long -> java.util.Calendar : DateFormatterRegistrar$LongToCalendarConverter@7f0655da java.lang.Long -> java.util.Date : DateFormatterRegistrar$LongToDateConverter@15b2ed49 java.lang.Number -> java.lang.Character : NumberToCharacterConverter@4a2e1eee java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@17fb24cd java.lang.Number -> java.lang.String : ObjectToStringConverter@5f98d55 java.lang.String -> java.lang.Long: DateTimeFormatAnnotationFormatterFactory@6434d4f2,java.lang.String -> NumberFormat java.lang.Long: java.lang.String -> java.time.LocalDate: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.LocalDate: standard.TemporalAccessorParser@3d03b4a0 java.lang.String -> java.time.LocalDateTime: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.LocalDateTime: standard.TemporalAccessorParser@1c9c3989 java.lang.String -> java.time.LocalTime: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.LocalTime: standard.TemporalAccessorParser@7e805010 java.lang.String -> java.time.OffsetDateTime: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.OffsetDateTime: standard.TemporalAccessorParser@7011c3ab java.lang.String -> java.time.OffsetTime: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.OffsetTime: standard.TemporalAccessorParser@4b8f44ae java.lang.String -> java.time.ZonedDateTime: standard.Jsr310DateTimeFormatAnnotationFormatterFactory@1de0943c,java.lang.String -> java.time.ZonedDateTime: standard.TemporalAccessorParser@793f7beb java.lang.String -> java.util.Calendar: DateTimeFormatAnnotationFormatterFactory@6434d4f2 java.lang.String -> java.util.Date: DateTimeFormatAnnotationFormatterFactory@6434d4f2 java.lang.String -> NumberFormat java.lang.Double: java.lang.String -> NumberFormat java.lang.Float: java.lang.String -> NumberFormat java.lang.Integer: java.lang.String -> NumberFormat java.lang.Short: java.lang.String -> NumberFormat java.math.BigDecimal: java.lang.String -> NumberFormat java.math.BigInteger: java.lang.String -> java.lang.Boolean : StringToBooleanConverter@5eec8efa java.lang.String -> java.lang.Character : StringToCharacterConverter@11dac86 java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@48cb1a15 java.lang.String -> java.lang.Number : StringToNumberConverterFactory@77c9768d java.lang.String -> java.time.Instant: standard.InstantFormatter@2a5b8feb java.lang.String -> java.util.Locale : StringToLocaleConverter@33a9f1cc java.lang.String -> java.util.Properties : StringToPropertiesConverter@66ed1c44 java.lang.String -> java.util.UUID : StringToUUIDConverter@3455abb1 java.time.Instant -> java.lang.String : standard.InstantFormatter@2a5b8feb java.time.ZoneId -> java.util.TimeZone : ZoneIdToTimeZoneConverter@9d772b8 java.util.Calendar -> java.lang.Long : DateFormatterRegistrar$CalendarToLongConverter@6bc06701 java.util.Calendar -> java.util.Date : DateFormatterRegistrar$CalendarToDateConverter@f5e2e3e java.util.Date -> java.lang.Long : DateFormatterRegistrar$DateToLongConverter@d3cfc51 java.util.Date -> jav...
資料繫結流程
- Spring MVC 主框架將 ServletRequest 物件及目標方法的形參例項傳遞給 WebDataBinderFactory 例項,以建立 DataBinder 例項物件。
- DataBinder 呼叫裝配在 Spring MVC 上下文中的 ConversionService 元件進行資料型別轉換、資料格式化工作。將 Servlet 中的請求資訊填充到形參物件中。
- 呼叫 Validator 元件對已經綁定了請求訊息的形參物件進行資料合法性校驗,並最終生成資料繫結結果 BindingData 物件。
- Spring MVC 抽取 BindingResult 中的形參物件和校驗錯誤物件,將它們賦給處理方法的響應形參。
自定義型別轉換器
- ConversionService 是 Spring 型別轉換體系的核心介面。
- 可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定義一個 ConversionService。Spring 將自動識別出 IOC 容器中的 ConversionService,並在 Bean 屬性配置及 Spring MVC 處理方法形參繫結等場合使用它進行資料的轉換。
- 可通過 ConversionServiceFactoryBean 的 converters 屬性註冊自定義的型別轉換器。
- Spring 定義了 3 種類型的轉換器介面,實現任意一個轉換器介面都可以作為自定義轉換器註冊到 ConversionServiceFactroyBean 中:
- Converter<S, T>:將 S 型別物件轉為 T 型別物件(使用這種)
- ConverterFactory
- GenericConverter
- 實現 Converter 介面,寫一個自定義型別轉換器
public class StringToEmpConverter implements Converter<String, Employee>{ @Autowired DepartmentDao deptDao; @Override public Employee convert(String source) { System.out.println("要轉換的字串:" + source); Employee emp = new Employee(); if(source.contains("-")) { String[] params = source.split("-"); emp.setLastName(params[0]); emp.setEmail(params[1]); emp.setGender(Integer.parseInt(params[2])); emp.setDepartment(deptDao.getDepartment(Integer.parseInt(params[3]))); } return null; } }
- Converter 是 ConversionService 中的元件,自定義的 Converter 得放進 ConversionService 中
<!-- 自定義的 ConversionService --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <!-- 新增我們自定義的型別轉換器 --> <property name="converters"> <set> <bean class="cn.edu.nuist.component.StringToEmpConverter"></bean> </set> </property> </bean>
- 將 WebDataBinder 中的 ConversionService 設定成帶有我們自定義 Converter 的 ConversionService
annotation-driven
<mvc:annotation-driven />
會自動註冊 RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 與 ExceptionHandlerExceptionResolver 三個 bean。- 還將提供以下支援:
- 支援使用 ConversionService 例項對錶單引數進行型別轉換
- 支援使用 @NumberFormat annotation、@DateTimeFormat 註解完成資料型別的格式化
- 支援使用 @Valid 註解對 JavaBean 例項進行 JSR 303 驗證
- 支援使用 @RequestBody 和 @ResponseBody 註解
通過檢視這個解析該標籤的類,會發現,它添了好多東西 ...
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
if (element.hasAttribute("enable-matrix-variables") || element.hasAttribute("enableMatrixVariables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute(
element.hasAttribute("enable-matrix-variables") ? "enable-matrix-variables" : "enableMatrixVariables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
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, parserContext);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
String asyncTimeout = getAsyncTimeout(element, source, parserContext);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
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);
if (element.hasAttribute("ignore-default-model-on-redirect")
|| element.hasAttribute("ignoreDefaultModelOnRedirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(
element.hasAttribute("ignore-default-model-on-redirect")
? "ignore-default-model-on-redirect" : "ignoreDefaultModelOnRedirect"));
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);
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
uriCompContribDef.setSource(source);
uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
parserContext.getReaderContext().getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
RootBeanDefinition exceptionHandlerExceptionResolver = new
RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
exceptionHandlerExceptionResolver.setSource(source);
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
return null;
}
為什麼請求不好使就加 <mvc:default-servlet-handler />
和 <mvc:annotation-driven />
?
- 這倆都沒加
- 動態資源(@RequestMapping 對映的資源) 能訪問
- 靜態資源(HTML、CSS、JS) 不能訪問:由 '能訪問動態的原因' 可知,就是因為 handlerMap 中沒有儲存靜態資源對映的請求,所以不能訪問
- 動態資源(@RequestMapping 對映的資源) 能訪問
- 只新增
<mvc:default-servlet-handler />
- 動態資源不能訪問,如下所示,處理動態資源對映的 DefaultAnnotationHandlerMapping 被 SimpleUrlHandlerMapping 替換了,它的作用是將所有的請求都交給 tomcat 來處理。
- 靜態資源能訪問的原因:正是因為請求都交給了 tomcat 來處理
- 動態資源不能訪問,如下所示,處理動態資源對映的 DefaultAnnotationHandlerMapping 被 SimpleUrlHandlerMapping 替換了,它的作用是將所有的請求都交給 tomcat 來處理。
- 這倆都加上
- 如果是請求靜態資源,handlerMapping[2] 來處理(交給 tomcat)
- 如果是請求動態資源 → handlerMapping[0] 來處理,handlerMethods 屬性儲存了每一個請求用哪個方法來處理
- 如果是請求靜態資源,handlerMapping[2] 來處理(交給 tomcat)
確定引數都換成了解析器,不再 for 迴圈一個個識別了。
資料格式化
- 對屬性物件的輸入/輸出進行格式化,從其本質上講依然屬於 “型別轉換” 的範疇。
- Spring 在格式化模組中定義了一個實現 ConversionService 介面的 FormattingConversionService 實現類,該實現類擴充套件了 GenericConversionService,因此它既具有型別轉換的功能,又具有格式化的功能(ConversionServiceFactoryBean 建立的 ConversionService 是沒有格式化器存在的)。
ConversionServiceFactoryBean { private Set<?> converters; } FormattingConversionServiceFactoryBean { private Set<?> converters; private Set<?> formatters; }
- FormattingConversionService 擁有一個 FormattingConversionServiceFactroyBean 工廠類,後者用於在 Spring 上下文中構造前者。
- FormattingConversionServiceFactroyBean 內部已經註冊了 :
- NumberFormatAnnotationFormatterFactroy:支援對數字型別的屬性使用 @NumberFormat 註解
- JodaDateTimeFormatAnnotationFormatterFactroy:支援對日期型別的屬性使用 @DateTimeFormat 註解
- 裝配了 FormattingConversionServiceFactroyBean 後,就可以在 Spring MVC 形參繫結及模型資料輸出時使用註解驅動了。
<mvc:annotation-driven/>
預設建立的 ConversionService 例項即為 FormattingConversionServiceFactroyBean。
日期格式化
@DateTimeFormat 註解可對 java.util.Date、java.util.Calendar、java.long.Long 時間型別進行標註
- pattern 屬性:型別為字串。指定解析/格式化欄位資料的模式,如:
yyyy-MM-dd hh:mm:ss
- iso 屬性
- style 屬性
數值格式化
@NumberFormat 可對類似數字型別的屬性進行標註,它擁有兩個互斥的屬性:
- style:型別為 NumberFormat.Style。用於指定樣式型別,包括 3 種:Style.NUMBER(正常數字型別)、 Style.CURRENCY(貨幣型別)、 Style.PERCENT(百分數型別)
- pattern:型別為 String,自定義樣式,如 pattern="#,###"
資料校驗
JSR 303 引入
- 只做前端校驗是不安全的;在重要資料一定要加上後端驗證。SpringMVC:可以 JSR 303 來做資料校驗
- JSR 303 是 Java 為 Bean 資料合法性校驗提供的標準框架,它已經包含在 JavaEE 6.0 中
- JDBC 規範 → 實現(各個廠商的驅動包)
- JSR 303 規範 → Hibernate Validator(第三方校驗框架)
- JSR 303 通過在 Bean 屬性上標註類似於 @NotNull、@Max 等標準的註解指定校驗規則,並通過標準的驗證介面對 Bean 進行驗證
- Hibernate Validator 是 JSR 303 的一個參考實現,除支援所有標準的校驗註解外,它還支援以下的擴充套件註解
實現步驟
導包&屬性加註解
- Spring 4.0 擁有自己獨立的資料校驗框架,同時支援 JSR 303 標準的校驗框架。
- Spring 在進行資料繫結時,可同時呼叫校驗框架完成資料校驗工作。在 Spring MVC 中,可直接通過註解驅動的方式進行資料校驗。
- Spring 的 LocalValidatorFactroyBean 既實現了 Spring 的 Validator 介面,也實現了 JSR 303 的 Validator 介面。只要在 Spring 容器中定義了一個 LocalValidatorFactoryBean,即可將其注入到需要資料校驗的 Bean 中。
- Spring 本身並沒有提供 JSR303 的實現,所以必須將 JSR303 的實現者的 jar 包放到類路徑下。
classmate-0.8.0.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jar hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar
<mvc:annotation-driven/>
會預設裝配好一個 LocalValidatorFactoryBean,通過在處理方法的形參上標註 @valid 註解即可讓 Spring MVC 在完成資料繫結後執行資料校驗的工作- 給 JavaBean 的屬性新增校驗註解(註解有個 message 屬性,直接定義錯誤提示資訊;但這樣就不能 i8n 了)
@Valid&BindingResult
- 在已經標註了 JSR303 註解的表單/命令物件前標註一個 @Valid,Spring MVC 框架在將請求引數繫結到該形參物件後,就會呼叫校驗框架根據註解宣告的校驗規則實施校驗
- Spring MVC 是通過對處理方法簽名的規約來儲存校驗結果的:前一個表單/命令物件的校驗結果儲存到隨後的形參中,這個儲存校驗結果的形參必須是 BindingResult 或 Errors 型別,這兩個類都位於 org.springframework.validation 包中 // BindingResult 擴充套件了 Errors 介面
- 需校驗的 Bean 物件和其繫結結果物件或錯誤物件時成對出現的,它們之間不允許宣告其他的形參。
- 在目標方法中獲取校驗結果
- 在表單/命令物件類的屬性中標註校驗註解,在處理方法對應的入參前新增 @Valid,Spring MVC 就會實施校驗並將校驗結果儲存在被校驗入參物件之後的 BindingResult 或 Errors 入參中。
- 常用方法
FieldError getFieldError(String field) List<FieldError> getFieldErrors() Object getFieldValue(String field) Int getErrorCount()
在頁面上顯示錯誤
- Spring MVC 除了會將表單/命令物件的校驗結果儲存到對應的 BindingResult 或 Errors 物件中外,還會將所有校驗結果儲存到 “隱含模型”。
- 即使處理方法的簽名中沒有對應於表單/命令物件的結果入參,校驗結果也會儲存在 “隱含物件” 中。
- 隱含模型中的所有資料最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 檢視物件,因此在 JSP 中可以獲取錯誤資訊
- 在 JSP 頁面上可通過
<form:errors path="userName">
顯示錯誤訊息;或者自己封裝好帶過去
效果展示:
提示訊息的國際化
- 每個屬性在資料繫結和資料校驗發生錯誤時,都會生成一個對應的 FieldError 物件。
- 當一個屬性校驗失敗後,校驗框架會為該屬性生成 4 個訊息程式碼,這些程式碼以校驗註解類名為字首,結合 modleAttribute、屬性名及屬性型別名生成多個對應的訊息程式碼;國際化檔案中錯誤訊息的 key 必須對應一個錯誤程式碼
codes [ Email.employee.email, 校驗規則.隱含模型中這個物件的key.物件的屬性 Email.email, 校驗規則.屬性名 Email.java.lang.String, 校驗規則.屬性型別 Email ];
- 隱含模型中 employee 物件的 email 屬性欄位發生了 @Email 校驗錯誤,就會生成 Email.employee.email
- Email.email:所有的 email 屬性只要發生了@Email 錯誤,...
- Email.java.lang.String:只要是 String 型別發生了@Email 錯誤,...
- Email:只要發生了@Email校驗錯誤,...
- 當使用 Spring MVC 標籤顯示錯誤訊息時, Spring MVC 會檢視 WEB 上下文是否裝配了對應的國際化訊息,如果沒有,則顯示預設的錯誤訊息,否則使用國際化訊息。
- 若資料型別轉換或資料格式轉換時發生錯誤,或該有的引數不存在,或呼叫處理方法時發生錯誤,都會在隱含模型中建立錯誤訊息。其錯誤程式碼字首說明如下:
- required:必要的引數不存在。如
@RequiredParam("param1")
標註了一個形參,但是該引數不存在 - typeMismatch:在資料繫結時,發生資料型別不匹配的問題
- methodInvocation:Spring MVC 在呼叫處理方法時發生了錯誤
- required:必要的引數不存在。如
- 編寫國際化的檔案:errors_zh_CN.properties,errors_en_US.properties
- 註冊國際化資原始檔
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="errors"></property> </bean>