SpringMVC封装自定义类型对象的时候,JavaBean要和页面提交的数据一一绑定。下面要知道:
1)页面提交的数据都是字符串
2)JavaBean中的属性如:Integer age;
那么绑定数据的时候牵扯到以下操作:
1)数据绑定期间的数据类型转换?String--Integer
2)数据绑定期间的数据格式化问题?比如提交的日期进行转换:birth=2017-12-15 -->Date 2017/12/15
3)数据校验?提交的数据必须是合法的,有前端校验(JS+正则表达式),后端校验也是必须的。
SpringMVC通过反射及机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
ConversionService是Spring类型转换体系的核心接口。可以利用ConversionServiceFactoryBean在Spring的IOC容器中顶一个ConversionService。Spring将自动识别出IOC容器中的ConversionService,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据的转换。
可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器。
Spring自定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:
步骤:
1)实现Converter接口,写一个自定义类型的转换器
2)配置出ConversionService
3)让SpringMVC用ConversionService
需求:字符串转换为对象
1.定义页面:
2.控制器方法:
/*
* 发送请求带的数据让其工作
* [email protected]
* 可以通过写一个自定义类型的转换器
*/
@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{
@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:
5.让SpringMVC用ConversionService:
1.直接配置的页面:无需经过控制器来执行结果,但会导致其他请求路径失效,需要配置
2.RESTful-CRUD操作时,删除时,通过JQuery执行delete请求时,找不到静态资源,需要配置
将在 SpringMVC 上下文中定义一个
DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。
3.配置类型转换器服务时,需要指定转换服务引用。
会将自定义的
ConversionService 注册到 Spring MVC 的上下文中
源码:
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;
}
...
}
还提供以下支持:
1.既没有配置
动态资源(@RequestMapping映射的资源)能访问,静态资源(.html,.js)不能访问。
HandlerMapping:
动态资源能访问原因: DefaultAnnotationHandlerMapping 中的handlerMap中保存每一个资源的映射信息。
静态资源不能访问原因:就是handlerMap中没有保存静态资源映射的请求。
HandlerAdapter:
都没有配置情况下,AnnotationMethodHandlerAdapter是默认出厂设置,干活的(过期)。另外:conversionService是null(类型转换器是不起作用的)
2.配置
静态资源可以访问,但是动态资源不可以
动态资源不能访问的原因:HandlerMapping中没有DefaultAnnotationHandlerMapping 。
静态资源能访问的原因: SimpleUrlHandlerMapping将所有请求都映射给Tomcat。
HandlerAdapter:
(3)配置
SimpleUrlHandlerMapping将所有请求都映射给Tomcat,静态资源可以访问。
RequestMappingHandlerMapping:动态资源可以访问。
HandlerAdapter:AnnotationMethodHandlerAdapter已经过时,Spring3.2推荐RequestMappingHandlerAdapter来替代。所以说,默认情况下,没有配置这两个配置时,HelloWorld 程序可以正常运行,但是,涉及到静态资源查找的时候,就必须配置这个
对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴。Spring在格式化模块定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它即具有类型转换的功能,又具有格式化的功能。
FormattingConversionService拥有一个FormattingConversionFactoryBean工厂类,后者用于在Spring上下文中构造前者,FormattingConversionFactoryBean内部已经注册了:
装配了FormattingConversionFactoryBean后,就可以在Spring MVC入参绑定即模型数据输出时使用注解驱动了(
@DateTimeFormat注解可对java.util.Date,java.util.Calender,java.long.Long时间类型进行标注:
@NumberFormat可对类似数字类型的属性进行标注,它有两个互斥的属性:
1.页面表单
birth:
2.Employee类增加日期对象属性:
private Date birth;
3.关于格式错误(框架默认支持的格式为斜线方法,1992/09/09),在页面设置格式作为19992-09-09,报错:
4.解决404错误,在Employee类的日期属性上增加:
@DateTimeFormat(pattern="yyyy-MM-dd")
5. 配置
我们可以知道只做前端校验是不安全的,一方面是可以绕过页面提交请求,另一方面是可以禁用JS验证。所以在重要数据上一定要加上后端验证。
1)可以写程序将每一个数据取出来进行校验,如果失败直接来到添加页面,提示重新填写。(不可以)
2)SpringMVC可以利用JSR303进行数据校验。
JSP303::规范---Hibernate Validator(第三方校验框架)
JSR 303 是Java为bean数据合法性提供的标准框架,它已经包含在Java EE6.0中。JSR 303(Java Specification Request意思是Java规范提案)通过在Bean属性上标注类似于@NotNull等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
Hibernate Validator是JSR 303一个参考实现,除了支持所有标准的校验注解之外,它还支持以下的扩展注解。
Spring4.0拥有自家独立的数据校验框架,同时支持JSR 303标准的校验框架。Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,可以直接通过注解驱动的方式进行数据校验。
Spring的LocalValidatorFactoryBean即实现了Spring的Validator接口,也实现了JSR 303的Validator接口。只要在Spring容日中定义一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。
Spring本身没有提供JSR 303的实现,所以必须将JSR 303实现者的jar包放到类路径下。
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)验证失败后,使用
lastName:
email:
gender:
男:
女:
birth:
dept:
验证失败后,页面显示:
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 errorMap=new HashMap();
if(hasErrors)
{
List 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";
}
}
表单显示:
lastName: -->${errorInfo.lastName }
email: -->${errorInfo.email }
gender:
男:
女:
birth: -->${errorInfo.birth }
dept:
每个属性在数据绑定和数据校验发生错误时,都会形成一个对应的FieldError对象。
当一个属性校验失败后,校验框架会为属性生成4个消息代码,这些代码以注解类名称为前缀,结合modleAttribute,属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性标注了一个@Pattern注解,当该属性值不满足@Pattern所定义的规则时,就会产生以下4个错误代码:
当使用Spring MVC标签显示错误时,Spring MVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国家化消息。
若数据类型转换或数据格式转换发生错误的,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误信息,该错误代码前缀说明如下:
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管理国际化资源文件。
3.来到页面取值
4.高级国际化,动态传入参数
Length.java.lang.String=length incorrect,must between {2} and {1}~~
//0 号元素是当前属性名
//1 2号元素是按照字母大小写排序的排序