mapengpeng1999@163.com 数据流程绑定分析

数据流程绑定分析

前端表单传递过来的数据为什么能够自动的封装成实体类的对象?为什么能够进行自动的类型转换?
在这里插入图片描述
数据绑定流程:

1:SpringMVC框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以便创建DataBinder对象
2:DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换和数据类型格式化操作,将ServletRequest请求参数绑定到对应目标方法的入参对象中
3:调用Validator组件对已经绑定到了目标方法入参对象进行数据合法性的校验,并最终形成数据绑定的结果到BindingData对象
4:SpringMVC将数据校验、格式化、转换的错误信息封装到BindingResult对象中

自定义类型转换器

SpringMVC上下文中内建了很多的类型转换器,可以完成绝大多数的Java类型的转换工作。
这些不是我们关注的目标,我们的目标是自定义类型转换器。
ConversionService是类型转换核心的接口,可以利用ConversionServiceFactoryBean
在SpringIOC容器中定义一个ConversionService,Spring将自动的识别IOC容器中的ConversionService,
并在bean属性配置及SpringMVC处理方法入参绑定等场合使用这个ConversionService进行数据转换。
所以可以通过ConversionServiceFactoryBean的converters属性注册自定义类型转换器。
Spring支持的类型转换器:Spring中定义了三种类型转换的接口,实现任意一个接口都可以作为自定义的类型转换器,
自定义的类型转换器注册到ConversionServiceFactoryBean中,类型转换器就生效了,哪三种呢?
- Converter<S,T> : 表示将S类型转换成T类型,主要是用这个。例如,这里将String转化为Emp类型
- ConverterFactory
- GenericConverter
第2种或第3种是第一种的后代

范例:将字符串转换成Emp对象 比如字符串“jjm-jjm@163.com-1-3”

需要内建的类型转换器和自定义类型转换器结合使用

1:编写页面,此页面放在修改页面,在jsp/input.jsp页面。
//<input type="text" name="emp" value="jjm-jjm@163.com-1-3">
//<button>提交</button>
	<form action="${pageContext.request.contextPath}/emp" method="post">
		<input type="text" name="emp">
		<input type="submit" value="提交">
	</form>
2:编写请求处理器的目标方法,在EmpController类
//用于新增保存
	@RequestMapping(value="emp",method=RequestMethod.POST)
	public String insertEmp(Emp emp) {
		this.empService.insertEmp(emp);
		return "redirect:/emps";//重定向,视图解析为转发操作
		//新增完成后重定向到emps查询的地址,再执行一下查询,把最新的数据放到emps.jsp页面显示
	}
3:编写自定义的类型转换器StringToEmp,实现Converter<String,Emp>接口,复写convert方法
package com.wanbangee.util;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.wanbangee.entities.Emp;
@Component
public class StringToEmp implements Converter<String,Emp> {
	@Override
	public Emp convert(String empString) {
		//jjm-jjm@163.com-1-3 
		String empArray[] = empString.split("-");
		Emp emp = new Emp();
		try {
			emp.setEmpName(empArray[0]);
			emp.setEmpMail(empArray[1]);
			emp.setEmpGender(Integer.parseInt(empArray[2]));
			emp.setDeptId(Integer.parseInt(empArray[3]));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return emp;
	}
}

4:配置
<!-- mvc:annotation-driven : 是万能的注解驱动
	  	conversion-service : 配置SpringMVC上下文中中的类型转换器
	  		- 不配置:使用默认的SPringMVC内建的类型转换器
	  		- 配置:默认的+配置的自定义的类型转换器
 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 万能的配置 : 表示请求先找静态资源,如果没有静态资源则找正常的请求处理器 -->  
<!-- 将自定义的类型转换器定义为一个IOC容器中的bean,自定义类型转换器StringToEmp加了@Component注解 -->
<!-- 将自定义的类型转换器加入到ConversionService 中 
			- 不定义ConversionService,则ConversionService只有SPringMVC内建的类型转换器
			- SPringMVC内建的类型转换器 + 自定义的 -->
	<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
	 <!-- 配置ConversionFactoryBean 的 converters 属性 -->
		<property name="converters">
			<set>
				<ref bean="stringToEmp"/>
			</set>
		</property>
	</bean>
	
	
在springmvc-servlet.xml配置文件配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="com.wanbangee"></context:component-scan>
	<!-- 引入外部属性文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	<!-- 配置连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="${driver}"></property>
	<property name="jdbcUrl" value="${url}"></property>
	<property name="user" value="${user}"></property>
	<property name="password" value="${password}"></property>
	<property name="maxPoolSize" value="${maxSize}"></property>
	<property name="initialPoolSize" value="${initSize}"></property>
	</bean>
	<!-- JdbcTemplate配置 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 
		启用事务注解 
			- 默认情况下,事务注解会使用IOC容器中名称为transactionManager的bean,如果不存在,则报错
			- 我们也可以配置事务注解使用IOC容器中bean的名称
				- transaction-manager : 配置具体的事务管理器的bean名称
			- 约定大于配置大于编码
	-->
	<tx:annotation-driven/>
	
	<!-- springmvc配置 -->	
	<!-- 配置视图解析器 -->
	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/jsp/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
<!-- 处理静态资源 : SpringMVC会认为所有的请求都是静态资源,所以会导致正常经过请求处理器的请求出现404-->
	<mvc:default-servlet-handler/>
	<!-- 万能的额配置 : 表示请求先找静态资源,如果没有静态资源则找正常的请求处理器 -->
	<!-- mvc:annotation-driven : 是万能的注解驱动
	  	conversion-service : 配置SpringMVC上下文中中的类型转换器
	  		- 不配置:使用默认的SPringMVC内建的类型转换器
	  		- 配置:默认的+配置的自定义的类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>	
<!-- 将自定义的类型转换器定义为一个IOC容器中的bean,自定义类型转换器StringToEmp加了@Component注解 -->
		<!-- 
		将自定义的类型转换器加入到ConversionService 中 
			- 不定义ConversionService,则ConversionService只有SPringMVC内建的类型转换器
			- SPringMVC内建的类型转换器 + 自定义的-->
	<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
		<property name="converters">
			<set>
				<ref bean="stringToEmp"/>
			</set>
		</property>
	</bean>
</beans>

mvc:annotation-driven配置,万能的注解驱动

如果在SPring的配置文件(也就是SpringMVC的配置文件)中配置个annotation-driven配置,
SpringMVC的上下文中会自动的注册RequestMappingHandlerMapping,RequestMappingHandlerAdpater,ExceptionHandlerExceptionResolver这个三个bean,另外的话还提供下列支持:
- 支持使用ConversionService对参数进行类型转换
- 支持使用@NumberFormat,@DateFormat完成数据格式化
- 支持使用@Valid注解对请求参数进行JSR303校验
- 支持使用@RequestBody和@ResponseBody注解

数据格式化

数据格式也属于类型转换的范畴,Spring在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类即有类型转换的功能,也有格式化的功能,
这个实现类拥有一个工厂FormattingConversionServiceFactoryBean工厂类,
该工厂类就是用于SPring中构造FormattingConversionService实现类的。该工厂类中已经在内部注册了:
- @NumberFormat注解,用于进行数字类型的格式化
- @DateFormat注解,用来进行日期类型的格式化

范例:Emp中加入工资和生日属性,需要对工资和生日进行格式化

1:Emp中加入salary和birthday属性,并且制定格式化类型
	//123,456.99
	@NumberFormat(pattern="###,###.##")
	private Double salary;
	//2000-10-10
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date birthday;
	
2:配置,在springmvc-servlet.xml配置文件配置:
<!-- 万能的额配置 : 表示请求先找静态资源,如果没有静态资源则找正常的请求处理器 -->
	<!-- mvc:annotation-driven : 是万能的注解驱动
	  	conversion-service : 配置SpringMVC上下文中中的类型转换器
	  		- 不配置:使用默认的SPringMVC内建的类型转换器
	  		- 配置:默认的+配置的自定义的类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 将自定义的类型转换器定义为一个IOC容器中的bean,自定义类型转换器StringToEmp加了@Component注解 -->
		<!-- 
		将自定义的类型转换器加入到ConversionService 中 
			- 不定义ConversionService,则ConversionService只有SPringMVC内建的类型转换器
			- SPringMVC内建的类型转换器 + 自定义的
			FormattingConversionServiceFactoryBean : 即有类型转换也有类型格式化的功能-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<property name="converters">
			<set>
				<ref bean="stringToEmp"/>
			</set>
		</property>
	</bean>
	
3.修改表单,进行测试,在input.jsp页面加上下面这些代码:
	<!-- 
			items : 要使用的请求域中的数据
			itemLabel : 下拉列表显示的数据
			itemValue :  下拉列表真实的值
	-->
DEPT:<form:select path="deptId" items="${depts}"  itemLabel="deptName"itemValue="deptId">
     </form:select>
		<br>
		BIRTHDAY:<form:input path="birthday"/>
		<br>
		salary:<form:input path="salary"/>
		<br>
		<input type="submit" value="提交">
	</form:form>
也可以放在方法入参处,
@RequestMapping("textParamFormatting")
public String textParamFormatting(@DateTimeFormat(pattern="yyyy/MM/dd hh:mm:ss")Date date) {
		System.out.println(date);
		return "redirect:/emps";
	}


现在数据格式化是成功的,如果失败,我们说过,失败的信息都会绑定到BindingResult中,
BindingResult对象可以作为处理器目标方法的入参。
@RequestMapping(value="emp",method=RequestMethod.POST)
	public String insertEmp(@Valid Emp emp ,BindingResult result) {
		if(result.getErrorCount() > 0) {//表示有错误信息
			List<ObjectError> errors = result.getAllErrors();//获得所有的错误消息
			for (ObjectError objectError : errors) {
				System.out.println(objectError.getDefaultMessage());//打印错误消息的默认信息
			}
		}
//		this.empService.insertEmp(emp);
		System.out.println(emp);
		return "redirect:/emps";
	}
//用于新增保存	
	@RequestMapping(value="emp",method=RequestMethod.POST)
public String insertEmp(@Valid Emp emp ,BindingResult result,Map<String,Object> map) {
		if(result.getErrorCount() > 0) {//表示有错误信息
			List<ObjectError> errors = result.getAllErrors();//获得所有的错误消息
			for (ObjectError objectError : errors) {
				System.out.println(objectError.getDefaultMessage());//打印错误消息的默认信息
			}
			
			//准备genders
			Map<String,String> genders = new HashMap<>();
			genders.put("1","Male");
			genders.put("0","FeMale");
			map.put("genders", genders);
			
			//准备部门下拉列表
			List<Dept> depts = this.empService.selectAllDept();
			map.put("depts",depts);
			
			//准备Emp
			map.put("emp", emp);
			
			//如果存在错误消息,则回到input.jsp,实际上错误消息会加入到请求域中
			return "forward:/jsp/emp/input.jsp";
		}
//		this.empService.insertEmp(emp);
		System.out.println(emp);
		return "redirect:/emps";
	}

数据校验,JSR303

JSR303是Java为bean数据合法性校验提供的标准的框架,已经包含在JavaEE6.0中,
JSR303是通过在Bean的属性(实体类属性)上面注解类似于@NULL,@NOTNULL,@MAX等标准的注解指定校验规则,
并通过标准的接口对bean的属性进行验证,注解有:
序号注解说明
1@Null被注解的属性必须为NULL
2@NotNull被注解的属性必须不能为空
3@Past必须是一个过去的日期
4@Future必须是一个将来的日期
5@Pattern必须满足指定的正则规则
6@Max不能大于指定的最大值
7@Min不能小于指定的最小值
Hibernate Validator扩展注解,是JSR303的一个参考的实现,并且除了支持基本的校验注解外,还进行了扩展:
- @Email:必须符合电子邮箱格式
- @Length:字符串长度必须在指定范围内
- @NotEmpty:必须非空
- @Range:必须在指定的范围内
需要加入jar包:
classmate-0.8.0.jar
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
emp实体类:
	private Integer empId;
	@NotEmpty //不能为空
	private String empName;
	@Email //必须是一个邮箱格式
	private String empMail;
	private Integer empGender;
	private Integer deptId;
	private Dept dept;
	//123,456.99
	@NumberFormat(pattern="###,###.##")
	@Range(max=8000,min=2000)//工资必须在2000-8000之间
	private Double salary;
	//2000-10-10
	@Past //必须是一个过去的时间
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date birthday;

错误消息的显示及国际化

SpringMVC会将错误消息绑定到BindingResult中,除此之外还会降错误消息加入到隐含模型,
那么我们页面上获取表单提交验证的错误消息就是通过这个隐含模型中提取错误消息,
在页面上显示,在页面上显示可以通过springMVC的表单标签:
- <f:errors path=”*”/> 表示显示所有的错误消息
- <f:errors path=”empName”/> 显示empName属性验证的错误消息


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 引入SpringMVC的表单标签 -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="emp" method="post">
		<input type="text" name="emp">
		<input type="submit" value="提交">
	</form>
	<!-- 
		- SpringMVC的表单标签默认是要进行数据回显的,即使是新增表单,默认回显的数据来自请求域中的command,可以使用modelAttribute属性设置回显的数据名称
		- SpringMVC的表单标签提交的地址action默认为 到这个页面的地址
		- from:input 标签中的path属性相当于普通标签的name + value 属性
	-->
	<form:form modelAttribute="emp" action="emp" method="post">
		<!-- 显示所有的错误消息 -->
		<form:errors path="*"></form:errors>
		<br>
		<c:if test="${empty emp.empId}"> <!-- 新增操作 -->
			EMPNAME:<form:input path="empName"/> <span style="color:red"><form:errors path="empName"></form:errors></span>
		</c:if>
		<c:if test="${!empty emp.empId}"> <!-- 修改操作 -->
			<form:hidden path="empId"/>
			<input type="hidden" name="_method" value="put">
		</c:if>
		
		<%-- EMPNAME:<input type="text" name="empName" value="${}"/> --%>
		<br>
		EMPMAIL:<form:input path="empMail"/>  <span style="color:red"><form:errors path="empMail"></form:errors></span>
		<br>
<!-- tems="${genders}":表示会从请求域中找到响应的数据,渲染成单选钮组 -->
		EMPGENDER:<form:radiobuttons path="empGender" items="${genders}" delimiter="&nbsp;&nbsp;&nbsp;&nbsp;"/>
		<br>
		<!-- 
			items : 要使用的请求域中的数据
			itemLabel : 下拉列表显示的数据
			itemValue :  下拉列表真实的值
		 -->
		DEPT:<form:select path="deptId" items="${depts}"  itemLabel="deptName" itemValue="deptId"></form:select>
		 <span style="color:red"><form:errors path="deptId"></form:errors></span>
		<br>
		BIRTHDAY:<form:input path="birthday"/>  <span style="color:red"><form:errors path="birthday"></form:errors></span>
		<br>
		salary:<form:input path="salary"/> <span style="color:red"> <form:errors path="salary"></form:errors></span>
		<br>
		<input type="submit" value="提交">
	</form:form>
</body>
</html>
		
		
//用于新增保存	
	@RequestMapping(value="emp",method=RequestMethod.POST)
public String insertEmp(@Valid Emp emp ,BindingResult result,Map<String,Object> map) {
		if(result.getErrorCount() > 0) {//表示有错误信息
			List<ObjectError> errors = result.getAllErrors();//获得所有的错误消息
			for (ObjectError objectError : errors) {
				System.out.println(objectError.getDefaultMessage());//打印错误消息的默认信息
			}
			
			//准备genders
			Map<String,String> genders = new HashMap<>();
			genders.put("1","Male");
			genders.put("0","FeMale");
			map.put("genders", genders);
			
			//准备部门下拉列表
			List<Dept> depts = this.empService.selectAllDept();
			map.put("depts",depts);
			
			//准备Emp
			map.put("emp", emp);
			
			//如果存在错误消息,则回到input.jsp,实际上错误消息会加入到请求域中
			return "forward:/jsp/input.jsp";
		}
		
//		this.empService.insertEmp(emp);
		System.out.println(emp);
		return "redirect:/emps";
	}
错误消息的国际化,在指定的语言环境下,显示指定语言的错误信息,
我们肯定要配置国际化资源文件,配置国际化资源文件的bean。
1.在springmvc_servlet.xml配置文件中配置国际化资源文件的bean
	  <!-- 配置国际化资源文件的bean -->
	  <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
	  	<property name="basename" value="i18n"></property>
	  </bean>
2.配置国际化资源文件:SpringMVC为国际化资源文件配置,提供了四种规则:
- 注解名称.请求域中的对象名称.对象属性名
范例:NotEmpty.emp.empName
- 注解名.属性名
范例:NotEmpty.empName
- 注解名.属性的全类名
范例:NotEmpty.java.lang.String
- 注解名
范例:NotEmpty
常用的是第一种:注解名称.请求域中的对象名称.对象属性名,下面代码演示以第一种为例。

基础配置文件,i18n.properties
NotEmpty.emp.empName=not empty
Email.emp.empMail=email
Range.emp.empSalary=range
Past.emp.birthDay=past
Past.emp.deptId=range

国际化资源文件,中文,i18n_zh_CN.properties
NotEmpty.emp.empName=empName\u4E0D\u80FD\u4E3A\u7A7A
Email.emp.empMail=empMail\u4E0D\u7B26\u5408\u90AE\u7BB1\u683C\u5F0F
Range.emp.salary=salary\u5FC5\u987B\u57282000-8000\u4E4B\u95F4
Past.emp.birthDay=birthday\u5FC5\u987B\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65F6\u95F4
Past.emp.deptId=deptId \u5FC5\u987B\u662F1-3\u4E4B\u95F4

国际化资源文件,英文,i18n_en_US.properties
NotEmpty.emp.empName=empName must have content
Email.emp.empMail=empMail is not a email address
Range.emp.salary=salary must between 2000 and 8000
Past.emp.birthDay=birthday must be a past date
Range.emp.deptId=deptId must between 1 and 3

emp实体类:
	private Integer empId;
	@NotEmpty //不能为空
	private String empName;
	@Email //必须是一个邮箱格式
	private String empMail;
	private Integer empGender;
	private Integer deptId;
	private Dept dept;
	//123,456.99
	@NumberFormat(pattern="###,###.##")
	@Range(max=8000,min=2000)//工资必须在2000-8000之间
	private Double salary;
	//2000-10-10
	@Past //必须是一个过去的时间
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date birthday;

InitBinder注解(很少使用)

@InitBinder注解是用于标识方法的,可以对WebDataBinder的行为进行一些限制,
比如设置请求参数是否需要和处理器的目标方法的入参是否匹配。WebDataBinder是DataBinder的子类。
//设置数据绑定规则
	@InitBinder
	public void  testInitBinder(WebDataBinder webDataBinder) {
		//指定的四个属性不允许绑定到目标方法的入参对象中
		webDataBinder.setDisallowedFields("empName","empMail","salary","deptId");
	}

总结

对于Rest Ful CRUD操作一定要掌握;
了解一下数据绑定的流程
mcv:annotation-driven配置是一个标配,能够为我们生成很多的Adapter,可以进行数据的校验和数据的格式化;
数据的格式化,主要提供日期格式化和数字格式化;
数据校验是支持JSR303验证的,同时可以将Hibernate Validator加入进行验证的拓展;
错误消息的显示使用的是form标签中<form:errors path=”属性名/*”>;
国际化和之前的JSTL国际化是类似的操作,但是要注意消息名称的定义,虽然有四种,
常用的是:注解.modelAttribute名.属性名;
InitBinder注解在开发中比较少使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值