SpringMVC-应用(数据绑定-自定义类型转换器,数据的格式化,数据校验)

一.提出问题

SpringMVC封装自定义类型对象的时候,JavaBean要和页面提交的数据一一绑定。下面要知道:

  1)页面提交的数据都是字符串

  2)JavaBean中的属性如:Integer age;

那么绑定数据的时候牵扯到以下操作:

  1)数据绑定期间的数据类型转换?String--Integer

  2)数据绑定期间的数据格式化问题?比如提交的日期进行转换:birth=2017-12-15  -->Date  2017/12/15

  3)数据校验?提交的数据必须是合法的,有前端校验(JS+正则表达式),后端校验也是必须的。

二.数据绑定

2.1 数据绑定流程

  1. SpringMVC主框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象。
  2. DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换,数据格式化工作。将Servlet中的请求信息填充到入参对象中。
  3. 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象。
  4. SpringMVC抽取BindingResult中入参对象和校验错误对象,将它们赋给处理方法的响应入参。

SpringMVC通过反射及机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:

2.2 自定义类型转换器

2.2.1 类型转换器概述

ConversionService是Spring类型转换体系的核心接口。可以利用ConversionServiceFactoryBean在Spring的IOC容器中顶一个ConversionService。Spring将自动识别出IOC容器中的ConversionService,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据的转换。

可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器。

2.2.2 Spring支持的转换器类型

Spring自定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:

  • Converter<S,T>:将S类型对象转换为T类型对象。
  • ConverterFactory:将相同系列多个“同质”Converter封装一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number子类(Integer,Long,Double等)对象)可使用该转换器工厂类。
  • GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换

2.2.3 自定义转换器的步骤

步骤:

1)实现Converter接口,写一个自定义类型的转换器

        2)配置出ConversionService

        3)让SpringMVC用ConversionService

2.2.3 自定义转换器示例

需求:字符串转换为对象

1.定义页面:

<form action="${ctp }/quickadd">

   <!-- 将所有员工信息都写上,自动封装对象 -->
   <input name="empInfo" value="empAdmin-admin@qq.com-1-101"/>
   <input type="submit" value="快速添加"/>
    
</form>

2.控制器方法:

	
	/*
	 * 发送请求带的数据让其工作
	 * quick?empInfo=empAdmin-admin@qq.com-1-101
	 * 可以通过写一个自定义类型的转换器
	 */
	@RequestMapping("/quickadd")
	public String quickAdd(@RequestParam("empInfo")Employee employee){
		employeeDao.save(employee);
		return "redirect:/emps";
	}
	

3.自定义类型转换器(实现Converter接口):

package com.test.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;

import com.test.bean.Employee;
import com.test.dao.DepartmentDao;




/*
 * 将String转换Employee
 */

public class MyStringToEmployeeConverter implements Converter<String, Employee>{

	@Autowired
	DepartmentDao dao;
	/*
	 * 自定义转换规则
	 */
	@Override
	public Employee convert(String source) {
	    System.out.println("页面提交的将要转换的字符串:"+source);
	    Employee employee=new Employee();
	    if(source.contains("-")){
	    	String[] split=source.split("-");
	    	employee.setLastName(split[0]);
	    	employee.setEmail(split[1]);
	    	employee.setGender(Integer.parseInt(split[2]));
	    	employee.setDepartment(dao.getDepartment(Integer.parseInt(split[3])));
	    }
		return employee;
	}

	

	

	

}

4.配置出ConversionService:

<!-- 告诉SpringMVC别用默认的ConversionService,用自定义的ConversionService -->
   <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!-- converters转换器中添加我们自定义的类型转换器 -->
   <property name="converters">
    <set>
      <bean class="com.test.component.MyStringToEmployeeConverter"></bean>
    </set>
   </property>
   
   </bean>

5.让SpringMVC用ConversionService:

<!-- conversion-service="conversionService" 使用自己配置的类型转换组件  -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

三.<mvc:annotation-driven />

3.1 配置的时机

1.直接配置的页面:无需经过控制器来执行结果,但会导致其他请求路径失效,需要配置<mvc:annotation-driven />标签。

<mvc:view-controller path="/success" view-name="success"/>

2.RESTful-CRUD操作时,删除时,通过JQuery执行delete请求时,找不到静态资源,需要配置<mvc:annotation-driven />标签。

<mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个
DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

3.配置类型转换器服务时,需要指定转换服务引用。

<mvc:annotation-driven conversion-service=“conversionService”/> 会将自定义的
ConversionService 注册到 Spring MVC 的上下文中

3.2 强大的功能

源码:

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

	private static final boolean jsr303Present = ClassUtils.isPresent(
			"javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

	private static final boolean jaxb2Present =
			ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

	private static final boolean jackson2Present =
			ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
					ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

	private static final boolean jacksonPresent =
			ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
					ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

	private static boolean romePresent =
			ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

	@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:annotation-driven/>会自动注册RequestMappingHandlerMapping,RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个bean。

还提供以下支持:

  • 支持使用ConversionService实例对表单参数进行类型转换
  • 支持使用@NumberFormat,@DateTimeFormat注解完成数据类型的格式化
  • 支持使用@Valid注解对JavaBean实例进行JSR 303验证
  • 支持使用@RequestBody和@ResponseBody注解

3.3 结合源码分析配置<mvc:default-servlet-handler/>   <mvc:annotation-driven/>这两个标签后,静态动态资源能不能访问的原因?

1.既没有配置<mvc:default-servlet-handler/> ,也没有配置<mvc:annotation-driven/>

动态资源(@RequestMapping映射的资源)能访问,静态资源(.html,.js)不能访问。

HandlerMapping:

动态资源能访问原因: DefaultAnnotationHandlerMapping  中的handlerMap中保存每一个资源的映射信息。

静态资源不能访问原因:就是handlerMap中没有保存静态资源映射的请求。

HandlerAdapter:

 都没有配置情况下,AnnotationMethodHandlerAdapter是默认出厂设置,干活的(过期)。另外:conversionService是null(类型转换器是不起作用的)

2.配置<mvc:default-servlet-handler/> ,没有配置<mvc:annotation-driven/>

静态资源可以访问,但是动态资源不可以

动态资源不能访问的原因:HandlerMapping中没有DefaultAnnotationHandlerMapping 。

静态资源能访问的原因: SimpleUrlHandlerMapping将所有请求都映射给Tomcat。

HandlerAdapter:

(3)配置 <mvc:default-servlet-handler/>   <mvc:annotation-driven/>,静态动态才能访问

SimpleUrlHandlerMapping将所有请求都映射给Tomcat,静态资源可以访问。

RequestMappingHandlerMapping:动态资源可以访问。

HandlerAdapter:AnnotationMethodHandlerAdapter已经过时,Spring3.2推荐RequestMappingHandlerAdapter来替代。所以说,默认情况下,没有配置这两个配置时,HelloWorld 程序可以正常运行,但是,涉及到静态资源查找的时候,就必须配置这个<mvc:annotation-driven/>配置了

四.数据的格式化

4.1 数据格式化概述

对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴。Spring在格式化模块定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它即具有类型转换的功能,又具有格式化的功能。

FormattingConversionService拥有一个FormattingConversionFactoryBean工厂类,后者用于在Spring上下文中构造前者,FormattingConversionFactoryBean内部已经注册了:

  • NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解。
  • JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解

装配了FormattingConversionFactoryBean后,就可以在Spring MVC入参绑定即模型数据输出时使用注解驱动了(<mvc:annotation-driven />默认创建的ConversionService实例即为DefaultFormattingConversionService)。

4.2 日期格式化概述

@DateTimeFormat注解可对java.util.Date,java.util.Calender,java.long.Long时间类型进行标注:

  • pattern属性:类型为字符串。指定解析/格式化字段数据的模型,如“yyyy-MM-dd”。
  • iso属性:类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模型,包括四种:ISO.NONE(不使用) -- 默认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、  ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)。
  • style属性:字符串类型。通过样式指定日期时间格式,由两位字符呢组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式。M:中日期/时间格式,L:长日期/时间格式,F:完整日期/时间格式,-:忽略日期或时间格式。

4.3 数值格式化概述

@NumberFormat可对类似数字类型的属性进行标注,它有两个互斥的属性:

  • tyle:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
  • pattern:类型为 String,自定义样式,如pattern="#,###"

4.4 实验代码(日期格式化)

1.页面表单

   birth:<form:input path="birth"/>       

2.Employee类增加日期对象属性:

	private Date birth;

3.关于格式错误(框架默认支持的格式为斜线方法,1992/09/09),在页面设置格式作为19992-09-09,报错:

4.解决404错误,在Employee类的日期属性上增加:

	@DateTimeFormat(pattern="yyyy-MM-dd")

5. 配置

<!-- conversion-service="conversionService" 使用自己配置的类型转换组件  -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

<!-- 告诉SpringMVC别用默认的ConversionService,用自定义的ConversionService -->
<!-- ConversionService创建的ConversionService组件是没有格式化器存在的 -->
<!-- 以后写自定义类型转换器的时候就使用org.springframework.format.support.FormattingConversionServiceFactoryBean
  即具有类型转换,又具有格式化功能 -->
   <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <!-- converters转换器中添加我们自定义的类型转换器 -->
   <property name="converters">
    <set>
      <bean class="com.test.component.MyStringToEmployeeConverter"></bean>
    </set>
   </property>
   
   </bean>

五.数据校验

我们可以知道只做前端校验是不安全的,一方面是可以绕过页面提交请求,另一方面是可以禁用JS验证。所以在重要数据上一定要加上后端验证。

1)可以写程序将每一个数据取出来进行校验,如果失败直接来到添加页面,提示重新填写。(不可以)

2)SpringMVC可以利用JSR303进行数据校验。

      JSP303::规范---Hibernate Validator(第三方校验框架)

5.1 JSR 303 

JSR 303 是Java为bean数据合法性提供的标准框架,它已经包含在Java EE6.0中。JSR 303(Java Specification Request意思是Java规范提案)通过在Bean属性上标注类似于@NotNull等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

5.2 Hibernate Validator扩展注解

Hibernate Validator是JSR 303一个参考实现,除了支持所有标准的校验注解之外,它还支持以下的扩展注解。

5.3 Spring MVC数据校验

Spring4.0拥有自家独立的数据校验框架,同时支持JSR 303标准的校验框架。Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,可以直接通过注解驱动的方式进行数据校验。

Spring的LocalValidatorFactoryBean即实现了Spring的Validator接口,也实现了JSR 303的Validator接口。只要在Spring容日中定义一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。

Spring本身没有提供JSR 303的实现,所以必须将JSR 303实现者的jar包放到类路径下。<mvc:annotation-driven />会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解可让Spring MVC在完成数据绑定后执行数据校验工作。在已经标注了JSR 303注解的表单/命令对象标注一个@Valid,Spring MVC框架在请求参数绑定该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验。

5.4实验代码

1)导入校验框架的jar包。

hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar

2)在验证属性上增加验证注解


	private Integer id;
	
	@NotEmpty
	@Length(min=6,max=18)
	private String lastName;

	@Email
	private String email;
	//1 male, 0 female
	private Integer gender;
	
	private Department department;
	
	//必须是一个过去的时间
	@DateTimeFormat(pattern="yyyy-MM-dd")
	@Past
	private Date birth;
	
	

3)在SpringMVC封装对象的时候,告诉SpringMVC这个JavaBean需要校验

@RequestMapping(value="/emp",method=RequestMethod.POST)
	public String addEmp(@Valid Employee employee){

4)如何知道校验结果:给需要校验的JavaBean后面紧跟一个BindingResult,这个就是封装了前一个bean的校验结果。

@RequestMapping(value="/emp",method=RequestMethod.POST)
	public String addEmp(@Valid Employee employee,BindingResult result){

5)根据不同的校验结果决定怎么办?如果验证失败,回到添加页面

	@RequestMapping(value="/emp",method=RequestMethod.POST)
	public String addEmp(@Valid Employee employee,BindingResult result){
		System.out.println("要添加的员工"+employee);
	boolean hasErrors=result.hasErrors();
	if(hasErrors)
	{
		System.out.println("有校验错误");
		return "add1";
	}else{
		employeeDao.save(employee);
		//重定向查询全部员工
		return "redirect:/emps";
	}
	}

6)验证失败后,使用<form:errors/>进行提示。SpringMVC除了会将表单/命令对象的校验结果保存到对应的BindingResult或Errors对象中外,还会将所有的校验结果保存到“隐含模型”。即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在“隐含模型中”。隐含模型中的所有数据最终将通过HttpServletRequest的属性列表暴露给JSP视图对象,因此在JSP可以获取错信息。在JSP页面可通过<form:errors path=""/>显示错误消息。

<form:form action="${ctp }/emp"  modelAttribute="employee"  method="POST">
     <!-- path:就是html-input的name 
                                     (1)  当做原生的name项
                                       (2)自动回显隐含模型中某个对象对应的属性值
          -->
    lastName:<form:input path="lastName"/><form:errors path="lastName"/><br/>
    email:<form:input path="email"/><form:errors path="email"/><br/>
    gender:<br/>
                            男:<form:radiobutton path="gender" value="1"/><br/>
                            女:<form:radiobutton path="gender" value="0"/><br/>
   <!-- items:指定要遍历的集合 ,自动遍历,遍历出的每一个元素是一个department对象
        itemLable="属性名" :指定遍历出的这个对象的哪个属性时作为option标签体的值
        itemValue="属性名":指定遍历出的这个对象的哪个属性作为提交的值
    
    -->
     birth:<form:input path="birth"/><form:errors path="birth"/><br/>              
   dept:<form:select path="department.id" items="${depts }" itemLabel="departmentName" itemValue="id">
   <br/>
   </form:select><br/>
   <input type="submit" value="保存"/> 
</form:form>

验证失败后,页面显示:

5.5 BindingResult

public interface BindingResult extends Errors

Spring MVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验的入参必须是BindingResult或Error类型,这两个类都位于org.springframework.validation包中。

需校验的Bean对象和其绑定结果对象时成对出现的,它们之间不允许声明其他的入参。

Errors接口提供了获取错误信息的方法,如getErrorCount()等。BindingResult扩展了Errors接口。

原生的表单怎么提取错误信息?将错误放在请求域中,使用BindingResult来取出错误消息,将错误消息放在Model中。

@RequestMapping(value="/emp",method=RequestMethod.POST)
	public String addEmp(@Valid Employee employee,BindingResult result,Model model){
		System.out.println("要添加的员工"+employee);
	boolean hasErrors=result.hasErrors();
	Map<String,Object> errorMap=new HashMap<String,Object>();
	if(hasErrors)
	{
		
		List<FieldError> errors=result.getFieldErrors();
		for(FieldError fieldError:errors)
		{
			System.out.println("错误消息提示"+fieldError.getDefaultMessage());
			System.out.println("错误字段是"+fieldError.getField());
			System.out.println(fieldError);
		     errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
			
		}
		model.addAttribute("errorInfo", errorMap);
		System.out.println("有校验错误");
		return "add1";
	}else{
		employeeDao.save(employee);
		//重定向查询全部员工
		return "redirect:/emps";
	}
	}

表单显示:

<form:form action="${ctp }/emp"  modelAttribute="employee"  method="POST">
     <!-- path:就是html-input的name 
                                     (1)  当做原生的name项
                                       (2)自动回显隐含模型中某个对象对应的属性值
          -->
    lastName:<form:input path="lastName"/><form:errors path="lastName"/>-->${errorInfo.lastName }<br/>
    email:<form:input path="email"/><form:errors path="email"/>-->${errorInfo.email }<br/>
    gender:<br/>
                            男:<form:radiobutton path="gender" value="1"/><br/>
                            女:<form:radiobutton path="gender" value="0"/><br/>
   <!-- items:指定要遍历的集合 ,自动遍历,遍历出的每一个元素是一个department对象
        itemLable="属性名" :指定遍历出的这个对象的哪个属性时作为option标签体的值
        itemValue="属性名":指定遍历出的这个对象的哪个属性作为提交的值
    
    -->
     birth:<form:input path="birth"/><form:errors path="birth"/>-->${errorInfo.birth }<br/>              
   dept:<form:select path="department.id" items="${depts }" itemLabel="departmentName" itemValue="id">
   <br/>
   </form:select><br/>
   <input type="submit" value="保存"/> 
</form:form>

验证失败后,显示:

5.6 自定义国际化错误消息的显示

每个属性在数据绑定和数据校验发生错误时,都会形成一个对应的FieldError对象。

当一个属性校验失败后,校验框架会为属性生成4个消息代码,这些代码以注解类名称为前缀,结合modleAttribute,属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性标注了一个@Pattern注解,当该属性值不满足@Pattern所定义的规则时,就会产生以下4个错误代码:

  • Pattern.user.password
  • Pattern.password
  • Pattern.java.lang.String
  • Pattern

当使用Spring MVC标签显示错误时,Spring MVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国家化消息。

若数据类型转换或数据格式转换发生错误的,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误信息,该错误代码前缀说明如下:

  • required:必要的参数不存在。
  • typeMisMatch:在数据绑定时,发生数据类型不匹配的问题
  • methodInvocation:Spring MVC在调用处理方法时发生了错误

5.6. 1实验代码

1.编写国际化的文件:

errors_en_US.properties:

Email.email=email incorrect~~
NotEmpty=must not empty~~
Length.java.lang.String=length incorrect~~
Path=must a past time
typeMismatch.employee.birth=birth geshi buzhengque

errors_zh_CN.properties:

Email.email=\u90AE\u7BB1\u9519\u4E86\uFF01
NotEmpty=\u4E0D\u80FD\u4E3A\u7A7A
Length.java.lang.String=\u957F\u5EA6\u4E0D\u5BF9
Path=\u65F6\u95F4\u5FC5\u987B\u662F\u8FC7\u53BB\u7684
typeMismatch.employee.birth=\u751F\u65E5\u683C\u5F0F\u4E0D\u6B63\u786E

2.让Spring MVC管理国际化资源文件。

 <!-- 管理国家化资源文件 -->
   <bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messgaeSource">
      <property name="basename" value="errors"></property>
   
   </bean>

3.来到页面取值

4.高级国际化,动态传入参数

Length.java.lang.String=length incorrect,must between {2} and {1}~~
//0 号元素是当前属性名
//1 2号元素是按照字母大小写排序的排序

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值