`
liyixing1
  • 浏览: 961874 次
  • 性别: Icon_minigender_1
  • 来自: 江西上饶
社区版块
存档分类
最新评论

springmvc的验证 注解验证 与参数格式无法转换时

阅读更多
spring 官方提供了一个例子

https://src.springframework.org/svn/spring-samples/mvc-basic/trunk

例子



@RequestMapping(value = { "/", "home.do", "index", "index.jsp", "index.html", "index.htm" })

public String home(@Valid @ModelAttribute("user") User user, BindingResult result,

ModelMap model) throws IOException {

if(result.hasErrors()) { //给当前对象(BindingResult 前面的参数就是当前对象,每一个参数和BindingResult 一一对应,他们之间必须紧跟着,否则会报错,如这里的result必须跟着user,而不能将result写在model之后)注册一个字段错误。字段名可以为null,表示整个对象错误)。
result.rejectValue(null, null, null, null); System.out.println();
}

List messages = new ArrayList(); 
messages.add("你没有登录,请先登录!"); 
messages.add("你输入的用户名不存在!");
messages.add("无效的账号");
model.put("messages", messages); 
return "application/index"; 
} 

public class User { 
private Integer id; 
private String userName; 
/** * * 返回 id 的值 * * @return id */ 
@NotNull(message = "user.id.notnull") 
public Integer getId() { return id; } 
/** * * 设置 id 的值 * * @param id */ 
public void setId(Integer id) { this.id = id; }
}


我在自己的一个测试应用中会报错 错误内容大概就是:
StandardWrapper.Throwable org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.validation.beanvalidation.LocalValidatorFactoryBean#0': Invocation of init method failed; nested exception is javax.validation.ValidationException: Unable to find a default provider

这是因为我只加了validation-api-1.0.0.GA.jar架包,但是hibernate-validator-4.1.0.Final.jar架包没有加入
(hibernate-validator的版本不一定必须是是4.1.0.Final)。
另外它还需要
log4j-1.2.16.jar
slf4j-api-1.6.1.jar
slf4j-log4j12-1.6.1.jar

在调用spring.ftl的showErrors宏会调用status.errorMessages,这里的errorMessages消息是怎么生成的(因为我的项目中要考虑到国际化问题,所以需要了解,看看怎么做国际化消息提醒)。

注解适配器,在初始化参数的时候,会进入 HandlerMethodInvoker类的resolveHandlerArguments(Method, Object, NativeWebRequest, ExtendedModelMap) 方法,它会根据方法的参数的注解,来初始化数据。 其中有一句是

else if ("Valid".equals(paramAnn.annotationType().getSimpleName())) { 
  validate = true; 
} 

来确定这个参数是否需要验证。

获取这个字段的
WebDataBinder WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);


注意这个绑定器和WebBindingInitializer绑定器不是一回事。

绑定器有效,就调用绑定方法。

doBind(binder, webRequest, validate, !assignBindingResult);

绑定方法如下

private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate, boolean failOnErrors) throws Exception { 
doBind(binder, webRequest); 
if (validate) { binder.validate(); } 
if (failOnErrors && binder.getBindingResult().hasErrors()) { throw new BindException(binder.getBindingResult()); }
} 

可以看出来,验证只和

if (validate) { binder.validate(); }

这句有关系。 我们只需跟踪这里。 验证方法是

public void validate() { //首先获取的是验证器 
Validator validator = getValidator(); 
if (validator != null) { //然后调用验证器的验证方法,这个时候我们使用的是LocalValidatorFactoryBean验证器,但它实际上则调用它的父类SpringValidatorAdapter的验证方法,因为LocalValidatorFactoryBean没实现验证方法 validator.validate(getTarget(), getBindingResult()); } 
} 


跟踪验证器的验证方法
public void validate(Object target, Errors errors) { //这部是调用jsr validator 的实现。不会影响spring mvc的运行结果。不做解释。这里由于我只在User模写的id属性设置的NotNull,所以只会有一个错误返回如图 
result = this.targetValidator.validate(target); 
for (ConstraintViolation violation : result) { //这部分当然是获取验证的出错的字段名 String field = violation.getPropertyPath().toString(); //将错误字段封装成一个fieldError,因为是第一次验证这个字段,所以是null 
FieldError fieldError = errors.getFieldError(field); 
if (fieldError == null || !fieldError.isBindingFailure()) { 
try { //violation.getConstraintDescriptor(),返回的javax.validation.metadata.ConstraintDescriptor对象包含了这个错误字段的注解等信息。以及注解中的值,其中annotationType的信息就是注解值信息(@NotNull(message="xxx")这个时候message就是他的字段值。未设置值的字段取它的注解里面的默认值)如图 getSimpleName方法一般返回的是注解类名,如果是多个注解,组合起来返回。这个时候就返回NotNull,这个时候NotNull就作为errors.rejectValue方法的code参数 getArgumentsForConstraint方法则是通过对象名(errors保存了当前验证的对象名,如我这里验证的是action中的方法的user参数对象的id,那么就是user),字段和验证描述信息,获取国际化参数列表值,作为rejectValue方法的第三个参数(args)。第四个参数是默认消息,将注解中的message属性为默认值。很可能从属性文件取消息的时候,所使用的code就是在这里面初始化好的。所以我这里跟踪进rejectValue方法。 errors.rejectValue(field, violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(), getArgumentsForConstraint(errors.getObjectName(), field, violation.getConstraintDescriptor()), violation.getMessage()); } catch (NotReadablePropertyException ex) { throw new IllegalStateException("JSR-303 validated property '" + field + "' does not have a corresponding accessor for Spring data binding - " + "check your DataBinder's configuration (bean property versus direct field access)", ex); } } } } getArgumentsForConstraint方法 protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor> descriptor) { List arguments = new LinkedList(); //将对象和字段组合user.id,字段自己id组合成一个字符串数组。 String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field}; 创建DefaultMessageSourceResolvable,以字段名(这里是id)作为默认消息,这个是在消息参数化处理的,它会在第一个参数化值{0}的处理的时候,使用codes,类似的操作去调用messageSource的getMessage(codes, "id", locale),如果没有取到,就把fieldName作为值返回,如果取到,就用这个参数值覆盖,如果没有取到,就用fileName覆盖。这个时候当然先找user.id的message,没找到找id的message,没找到就直接使用id。 arguments.add(new DefaultMessageSourceResolvable(codes, field)); // Using a TreeMap for alphabetical ordering of attribute names Map attributesToExpose = new TreeMap(); //遍历注解中的属性 for (Map.Entry entry : descriptor.getAttributes().entrySet()) { String attributeName = entry.getKey(); Object attributeValue = entry.getValue(); //判断属性是否在internalAnnotationAttributes中。internalAnnotationAttributes是以开始就初始化好的,初始化的代码如下。如果不存在,就添加进这个玩意 /*private static final Set internalAnnotationAttributes = new HashSet(3); static { internalAnnotationAttributes.add("message"); internalAnnotationAttributes.add("groups"); internalAnnotationAttributes.add("payload"); }*/ if (!internalAnnotationAttributes.contains(attributeName)) { attributesToExpose.put(attributeName, attributeValue); } } //将所有的不存在于internalAnnotationAttributes里面的属性值添加进arguments,这会使得注解中的其他属性值最为属性文件里面的变量消息值了。。这个时候由于我没有增加更多的属性,所以是空。 arguments.addAll(attributesToExpose.values()); return arguments.toArray(new Object[arguments.size()]); } errors.rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage)方法 public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { if ("".equals(getNestedPath()) && !StringUtils.hasLength(field)) { reject(errorCode, errorArgs, defaultMessage); return; } String fixedField = fixedField(field); Object newVal = getActualFieldValue(fixedField); //这里是初始化字段错误 FieldError fe = new FieldError( getObjectName(), fixedField, newVal, false, //resolveMessageCodes方法初始化codes,也就是这个字段的在属性文件中消息的key,因此大概就是在这里初始化好了这个字段所有的key,我首先的怀疑是codes包含了:{errorCode,errorCode+field}两种code(这里的errorCode的值是NotNull)。进入。 resolveMessageCodes(errorCode, field), errorArgs, defaultMessage); addError(fe); } //看来这个方法不简单,最后的codes数组不是简单的几个code,可能由多个code组合而成。继续跟踪getMessageCodesResolver方法,只是返回return this.messageCodesResolver;然后使用messageCodesResolver来生成codes,这里返回的MessageCodesResolver是org.springframework.validation.DefaultMessageCodesResolver,这个org.springframework.validation.DefaultMessageCodesResolver对象是在new BindResult对象的时候,在其AbstractBindingResult的类属性初始化好的。初始化代码是 private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver(); 可以看到,默认创建的MessageCodes对象是一个DefaultMessageCodesResolver,它没有设置任何属性(实际上它只有一个prefix属性) public String[] resolveMessageCodes(String errorCode, String field) { Class fieldType = getFieldType(field); return getMessageCodesResolver().resolveMessageCodes( errorCode, getObjectName(), fixedField(field), fieldType); } DefaultMessageCodesResolver的resolveMessageCodes方法 这里就是生成codes的实际方法了 public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) { List codeList = new ArrayList(); List fieldList = new ArrayList(); //这个方法的doc解释是Add both keyed and non-keyed entries for the supplied field to the supplied field list,我英文不咋的,求解释。不过暂时还用不上。 另外,静态常量CODE_SEPARATOR = "."; buildFieldList(field, fieldList); for (String fieldInList : fieldList) { //第一个code应该就是errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + fieldInList了,还是进去看看postProcessMessageCode方法就是一句,return getPrefix() + code;,因为我没设置前缀,所以这里的结果应该是NotNull.user.id codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + fieldInList)); } int dotIndex = field.lastIndexOf('.'); if (dotIndex != -1) { //将组合字段的最后一个字段名取出,如 user的name.firstName.bigChar.firstChar就是firstChar了。 buildFieldList(field.substring(dotIndex + 1), fieldList); } //这里我的结果应该是添加一个NotNull.id for (String fieldInList : fieldList) { codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldInList)); } if (fieldType != null) { //这里我的结果就是NotNull.java.lang.Integer codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldType.getName())); } //这里我的结果是NotNull 所以最后我的codes有四个,分别是 NotNull.user.id, NotNull.id, NotNull.java.lang.Integer, NotNull codeList.add(postProcessMessageCode(errorCode)); return StringUtils.toStringArray(codeList); } 下面就是我如果想在前面添加code的前缀,就需要设置BindingResult的MessageCodesResolver。而设置MessageCodesResolver是通过WebDataBinder的binder.setMessageCodesResolver(messageCodesResolver);设置的,他会调用getInternalBindingResult().setMessageCodesResolver(messageCodesResolver);来设置实际的BindResult来设置。如果我们要设置全局的前缀,只需写一个WebBindingInitializer,并设置到注解适配器的bean中就可以了。因为在WebBindingInitializer里面可以获取到WebDataBinder。而如果我们需要在每一个Controller类添加不同的前缀,只需要写一个@InitBinder注解过的方法就好了。 @InitBinder() public void initBinder(WebDataBinder binder) { DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver(); messageCodesResolver.setPrefix("user."); binder.setMessageCodesResolver(messageCodesResolver); } 在ftl中展示字段错误,是通过showErrors 来暂时字段错误的。 "/> 我们一般在showErrors 之前需要调用一次formInput ,才能让showErrors正常工作 因为formInput 会调用bind宏,来初始化这个字段对应的status ${error} #if> ${error} #if> ${separator}#if> #list> #macro> 而bind宏则是 #if> #if> #macro> 可以看出来,bind宏每次调用之后,都会重新定义status和stringStatusValue 所以才能让showErrors 正常工作。 首先status对应的类型是org.springframework.web.servlet.support.BindStatus类型。 进到它的 public String[] getErrorMessages() { initErrorMessages(); return this.errorMessages; } 方法,可以发现首先需要initErrorMessages初始化消息。 if (this.errorMessages == null) { this.errorMessages = new String[this.objectErrors.size()]; for (int i = 0; i (); } Errors errors = this.errorsMap.get(name); boolean put = false; if (errors == null) { //从这里可以看出,BindResult对象在model中,以org.springframework.validation.BindingResult.(MODEL_KEY_PREFIX 的值是org.springframework.validation.BindingResult.)+对象名方式命名,这里是org.springframework.validation.BindingResult.user errors = (Errors) getModelObject(BindingResult.MODEL_KEY_PREFIX + name); // Check old BindException prefix for backwards compatibility. if (errors instanceof BindException) { errors = ((BindException) errors).getBindingResult(); } if (errors == null) { return null; } put = true; } if (htmlEscape && !(errors instanceof EscapedErrors)) { errors = new EscapedErrors(errors); put = true; } else if (!htmlEscape && errors instanceof EscapedErrors) { errors = ((EscapedErrors) errors).getSource(); put = true; } if (put) { this.errorsMap.put(name, errors); } return errors; } codes大概内容是 NotNull.user.id(errorCode.+objectName.field)优先级 0 NotNull.id(errorCode.+fieldLastName)优先级 1 NotNull.java.lang.Integer(errorCode.+fieldType.getName())优先级 2 NotNull(errorCode)优先级 3 参数无法转换 以Date类型作为例子 当出现请求参数无法转换成Date类型。直接输出字段error,就会发现,输出的是异常的message。 调试发现它这个时候的codes是 [typeMismatch.activitySave.activityStartTime, typeMismatch.activityStartTime, typeMismatch.java.util.Date, typeMismatch]
  • 大小: 6.2 KB
  • 大小: 2 KB
分享到:
评论
4 楼 gnomewarlock 2013-12-02  
代码自己能看懂吗?
3 楼 clamy01 2013-08-30  
这代码看着真揪心!!!
2 楼 liyixing1 2012-11-09  
wkq72376 写道
messages

什么
1 楼 wkq72376 2012-10-16  
messages

相关推荐

    SpringMVC全注解配置

    7. **数据绑定和验证**:`@Valid`注解结合JSR 303/349提供的bean验证,可以在处理请求时自动进行数据验证。 8. **拦截器**:`@AspectJ`风格的切面注解,如`@Before`和`@After`,可以创建拦截器,实现请求前后的处理...

    springmvc注解式控制器的数据验证、类型转换及格式化 SpringMVC数据验证

    总结来说,Spring MVC的注解式控制器提供了强大的数据验证、类型转换和格式化功能,简化了Web开发过程,提升了应用的安全性和用户体验。通过合理利用这些特性,开发者可以构建更加健壮、易于维护的Web应用。

    注解式springMVC的demo

    `@RequestBody`用于将HTTP请求体中的数据转换为方法参数,而`@ResponseBody`将方法返回值直接写入HTTP响应体,通常用于返回JSON或XML格式的数据。 8. **@ModelAttribute** `@ModelAttribute`常用于数据绑定,它...

    springMVC注解开发代码

    8. **Validation 验证**:通过 `@Valid` 和 `javax.validation` 注解,可以在处理请求时对输入数据进行验证。 9. **配置类**:Spring 4.0 引入了 Java 配置,允许开发者用类代替 XML 配置。`@Configuration` 和 `@...

    SpringMVC数据类型转换超详细介绍

    - **标准支持**:SpringMVC支持JSR-303标准的注解验证,如`@NotNull`、`@Pattern`等,简化了验证逻辑的编写。 - **集成便利**:只需在控制器方法的参数前加上`@Valid`或`@Validated`注解,并配合`BindingResult`对象...

    看透springMvc源代码分析与实践.pdf

    7. **转换与格式化**:SpringMVC支持自定义转换器和格式化器,用于处理不同类型的请求参数和响应数据,如日期、货币等的格式化。 8. **RESTful支持**:SpringMVC通过@RequestMapping的pathVariable、MatrixVariable...

    SpringMVC后台接收请求参数的几种方式Demo

    它们与`@ModelAttribute`一起使用,但更专注于表单验证和数据绑定: ```java @PostMapping("/submit") public String submitForm(@Validated @ModelAttribute("form") FormCommand command, BindingResult ...

    IDEA社区版编写的SpringMVC小项目

    同时,@RequestParam、@PathVariable、@ModelAttribute等注解用于从请求中提取参数。 **总结** "IDEA社区版编写的SpringMVC小项目"涵盖了SpringMVC的基本使用,包括了文件上传和JSON解析两个常见功能。这个项目...

    扩展SpringMVC以支持绑定JSON格式的请求参数

    扩展Spring MVC以支持绑定JSON格式的请求参数,能够使我们的服务更好地与前端或API客户端进行交互。本文将深入探讨如何实现这一功能。 首先,我们需要了解Spring MVC的模型绑定机制。模型绑定是Spring MVC中的一项...

    非注解方式配置springmvc

    在Spring MVC框架中,配置方式主要有两种:一种是基于XML的配置,另一种是基于Java的注解配置。本文将详细探讨非注解...这种方式虽然比注解配置繁琐,但在某些场景下,如需要灵活控制组件行为时,仍有一定的应用价值。

    SpringMVC框架实例

    在SpringMVC中,请求参数可以通过注解如@RequestParam、@PathVariable和@RequestBody进行获取。例如,使用@RequestParam可以将请求参数绑定到方法参数上。对于复杂的表单数据,可以使用@ModelAttribute注解结合...

    springmvc3+json参数传递后台接收json参数

    Spring MVC提供了数据绑定和验证机制,可以通过`@Valid`注解配合`@NotBlank`, `@Size`等JSR-303/JSR-349验证注解来验证接收到的数据。如果数据不合法,Spring MVC会自动返回错误信息。 7. **配置支持** 要使Spring...

    springmvc 改造

    改造可能需要定制化这些行为,比如自定义转换器或验证器,以适应特定的数据格式或业务规则。 7. **异常处理**: 应用程序可能需要统一处理各种异常情况。SpringMVC允许自定义异常处理器,改造时可能需要增加或修改...

    SpringMVC.docx

    注解驱动的SpringMVC简化了配置,常见的注解有: 1. `@Controller`:标记控制器类。 2. `@RequestMapping`:用于映射URL,可以指定标准映射、Ant风格映射和占位符映射,并限定请求方法。 3. `@PathVariable`:获取...

    SpringMVC接收请求参数所有方式总结

    - 最佳实践是定义全局日期格式,或使用`WebMvcConfigurer`接口自定义日期格式转换。 15. 请求URL匹配: - `'?'`形式的URL:用于匹配单个动态段,如`/user/{id}?`。 - `'*'`形式的URL:用于匹配多个动态段,如`/...

    SpringMVC 开发接口demo

    - `@ResponseBody`注解用于将方法返回的对象转换为JSON或XML格式,直接写入HTTP响应体。 - 可以使用`ResponseEntity`对象更精细地控制响应状态码和头部信息。 6. **异常处理** - 使用`@ExceptionHandler`注解...

    SpringMVC-8 数据类型转换、数据格式化与数据校验

    在Spring MVC框架中,数据类型转换、数据格式化和数据校验是开发Web应用程序时不可或缺的部分。这些功能有助于确保从客户端接收到的数据准确无误,同时提供了一种优雅的方式来处理和展示这些数据。本篇文章将深入...

    springMVC的demo

    6. **转换器和格式化器**:SpringMVC提供了`@InitBinder`和`@DateTimeFormat`等注解,用于自定义数据绑定和格式化逻辑。 7. **异常处理**:`@ExceptionHandler`注解允许我们为特定类型的异常定义处理方法,这样可以...

    SpringMVC restful 注解之@RequestBody进行json与object转换

    在Spring MVC中,`@RequestBody`注解是用于将HTTP请求体中的数据转换为Java对象的,特别适用于处理JSON或XML格式的数据。当控制器方法接收POST、PUT等带有请求体的HTTP请求时,`@RequestBody`可以帮助我们将接收到的...

    扩展SpringMVC以支持更精准的数据绑定1

    在默认情况下,Spring MVC使用`WebDataBinder`来处理数据绑定,它可以将请求参数与Java对象的字段进行匹配,并通过类型转换器(`Converter`)和格式化器(`Formatter`)将字符串值转化为对象类型。如果遇到无法直接...

Global site tag (gtag.js) - Google Analytics