28-SpringMVC-4(数据绑定+数据格式化+数据校验)
1. 数据绑定#
SpringMVC 封装自定义类型对象的时候,JavaBean 要和页面提交的数据进行一一绑定。但页面提交的数据都是字符串,而服务器端 Java 数据类型各种各样。
牵扯到以下操作:
- 数据绑定期间的数据类型转换,如:name=root&age=35
- 数据绑定期间的数据格式化,如:日期时间格式化
- 数据绑定期间的数据校验,不仅要有前端(JS+正则)校验,也要有后端校验
1.1 源码#
新的源码在 ModelAttributeMethodProcessor:
// 工厂创建数据绑定器
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) { // binder.getTarget() ~ POJO 对象
// 将页面提交过来的数据封装到 JavaBean 的属性中
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
数据绑定器 WebDataBinder 负责数据绑定工作。数据绑定期间产生的类型转换、格式化。
ConversionService 中如下所示,内建了很多 Converter 和 Formatter。不同类型的转换和格式化用它自己的 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...
1.2 数据绑定流程#
SpringMVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:
- Spring MVC 主框架将 ServletRequest 对象及目标方法的形参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder(数据绑定器) 实例对象。
- DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到形参对象中。
- 调用 Validator 组件对已经绑定了请求消息的形参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象。
- Spring MVC 抽取 BindingResult 中的形参对象和校验错误对象,将它们赋给处理方法的响应形参。
1.3 自定义类型转换器#
ConversionService 是 Spring 类型转换体系的核心接口。
可以通过 ConversionServiceFactoryBean 在 Spring IOC 容器中定义一个 ConversionService。Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 Spring MVC 处理方法形参绑定等场合使用它进行数据的转换。
可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器。
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到 ConversionServiceFactroyBean 中:
- Converter<S, T> 将 S 类型对象转为 T 类型对象(使用这种)
- ConverterFactory
- GenericConverter
(1) 实现 Converter<S, T>
,写一个自定义类型转换器
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;
}
}
(2) Converter 是 ConversionService 中的组件,自定义的 Converter 得放进 ConversionService 中。
<!-- 利用 ConversionServiceFactoryBean 在 IOC 容器中定义一个 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>
(3) 将 WebDataBinder 中的 ConversionService 设置成带有我们自定义 Converter 的 ConversionService。
2. annotation-driven#
<mvc:annotation-driven />
会自动注册 RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个组件。还将提供以下支持:
- 支持使用 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 />
?
- 这俩都没加
- 只添加
<mvc:annotation-driven />
,只能访问动态,不能访问静态。 - 只添加
<mvc:default-servlet-handler />
- 这俩都加上
ha.handle(xxx) 确定参数都换成了解析器,不再 for 循环一个个识别参数了。
3. 数据格式化#
- 对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
- Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能(ConversionServiceFactoryBean 方式创建的 ConversionService 是没有「格式化器」存在的)。
public class ConversionServiceFactoryBean ... { private Set<?> converters; } public class FormattingConversionServiceFactoryBean ... { private Set<?> converters; private Set<?> formatters; }
- FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者。
- FormattingConversionServiceFactroyBean 内部已经注册了 :
- NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
- JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
- 装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 形参绑定及模型数据输出时使用注解驱动了。
<mvc:annotation-driven/>
默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean。
3.1 日期格式化#
@DateTimeFormat 注解可对 java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注
- pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:
yyyy-MM-dd hh:mm:ss
- iso 属性
- style 属性
3.2 数值格式化#
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
- style:类型为 NumberFormat.Style。用于指定样式类型,包括 3 种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
- pattern:类型为 String,自定义样式,如 pattern="#,###"
4. 数据校验#
4.1 JSR 303 引入#
- 只做前端校验是不安全的;在重要数据一定要加上后端验证。
- SpringMVC 可以 JSR 303 来做数据校验,它是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中
- JDBC 规范 → 实现(各个厂商的驱动包)
- JSR 303 规范 → Hibernate Validator(第三方校验框架)
- JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
- Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
4.2 实现步骤#
4.2.1 导包&属性加注解#
- 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 了)
4.2.2 @Valid & BindingResult#
在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该形参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的形参中,这个保存校验结果的形参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中 // BindingResult 扩展了 Errors 接口
FieldError getFieldError(String field)
List<FieldError> getFieldErrors()
Object getFieldValue(String field)
Int getErrorCount()
在目标方法中获取校验结果:在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。
需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的形参!
4.2.3 在页面上显示错误#
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。
即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。
在 JSP 页面上可通过 <form:errors path="userName">
显示错误消息或者自己封装好带过去。
效果展示:
4.3 提示消息的国际化#
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 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 在调用处理方法时发生了错误
编写国际化的文件:errors_zh_CN.properties,errors_en_US.properties
注册国际化资源文件
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="errors"></property>
</bean>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?