Spring MVC 数据绑定流程

Spring MVC 数据绑定流程

前端表单穿过了的数据,为什么会被自动封装成实体对象?Spring MVC 是如何把我们的实体类和表单中参数对应封装起来,又是如何完成自动类型转换呢?

数据绑定流程:

  1. Spring MVC 框架将 ServletRequest 对象及目标方法的入参实例传给 WebDataBinderFactory 实例,这个实例便用来创建 DataBinder 对象
  2. DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据的类型转换和格式化,将 ServletRequest 请求参数绑定到对应目标方法的入参对象中。
  3. 随后 DataBinder 调用 Validator 组件对已经绑定到目标方法入参对象进行数据校验,并把最终形成的结果到 BindingData 对象
  4. 然后把其中格式、类型转换和数据校验发生的错误失败信息,都保存到 BindingResult

理论很复杂,我们可以实际应用看看。

格式化和类型转换器

类型转换器

Spring 中有着许多的类型转换器,可以完成大多数 Java 类型转换,但是如果遇到某些常见,比如把一个字符串封装成一个 实体类,那么就需要自定义类型转换器了。

ConversionService 是类型转换器的接口。我们可以利用 ConversionServiceFactoryBean 这个实现类来生产 ConversionService。

在 SpringIOC 容器定义 ConversionServiceFactoryBean ,Spring 将自动识别到这个实体,并且在 bean 属性设置及 SpringMVC 处理方法入参绑定等场合使用这个 ConversionServiceFactoryBean。查看该类的源码

public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {

	@Nullable
	private Set<?> converters;

	@Nullable
	private GenericConversionService conversionService;

	public void setConverters(Set<?> converters) {
		this.converters = converters;
	}
    ...
    ...
    ...
}

可以看到一个 Set 集合,用来保存自定义注册的类型转换器。我们可以通过在 XML 中属性注入来导入自己的类型转换器。

Spring 定义了三种类型转换器,实现其中一个便可以自定义类型转换。

  • Converter<S,T>
    • 表示把 S 类型转为 T 类型
  • ConverterFactory
  • GenericConverter

如何使用?

范例:把 MrWang-xiaowang@163.com-1-18 转为一个 Student 实例。

  1. 编写界面

     <form action="${pageContext.request.contextPath}/insertStu" method="post" >
     	<input type="text" name="emp" value="MrWang-xiaowang@163.com-1-18"><br>
     	<input type="submit" value="提交">
     </form>
    
  2. 编写目标处理器

    	@RequestMapping(value = "insertStu", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
    	public String insertEmp1(Student stu) {
    		System.out.println(stu);
    		stuManagerService.insertStu(stu);
    		return "success.jsp";
    	}
    
  3. 编写自定义类型转换器,实现 Converter 接口

    public class StudentConverter implements Converter<String, Student>{
    
    	@Override
    	public Student convert(String arg0) {
    		String[] stu = arg0.split("-");
    		Student s = new Student();
    		s.setStuName(stu[0]);
    		s.setEmpMail(stu[1]);
    		s.setStuGender(Integer.valueOf(stu[2]));
    		s.setStuAge(Integer.valueOf(stu[3]));
    		
    		return s;
    	}
    
    }
    
  4. 配置 ConversionServiceFactoryBean 和自定义类型转换类型的 bean

    <bean id="studentConverter" class="com.nhky.converter.StudentConverter"></bean>
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
         	<property name="converters">
         		<set>
         			<ref bean="studentConverter"></ref>
         		</set>
         	</property>
    </bean> 
    

格式化

说完了类型转换器,那么格式化?

对于格式化,它也属于类型转换的范畴。SpringMVC 的 ConversionService 有一个实现类 FormattingConversionService ,它有着类型转换和格式化的功能,并且该类也有一个工厂类FormattingConversionServiceFactoryBean 。它可以用来实现 FormattingConversionService。

并且在内部,提供了一些注解来实现格式化,例如

  • @NumberFormat
    • 用于数字类型的格式化
  • @DateFormat
    • 用于日期类型的格式化

修改上面的例子,在 Student 加入 money 和 birthday 属性。制定格式化内容。

  1. 修改表单

    	<form id="stu" action="${pageContext.request.contextPath}/stu" method="post">
    		
    		姓名:<input id="stuName" name="stuName" type="text" value="">
    			<br>
    		邮箱:<input id="stuMail" name="stuMail" type="text" value="">
    		<br>
    		性别:<input id="stuGender" name="stuGender" type="text" value="">
    		<br>
    		年龄:<input id="stuAge" name="stuAge" type="text" value="">
    		<br>
    		零花钱:<input id="money" name="money" type="text" value=""><br>
    		生日:<input id="birthday" name="birthday" type="text" value=""><br>
    		<button type="submit" value="Submit">提交</button>
    	</form>
    
  2. 修改实体类字段

    	@NumberFormat(pattern="#,###,###,###.#")
    	private Double money;
    	
    	@DateTimeFormat(pattern="yyyy-MM-dd")
    	private Date birthday;
    
  3. 配置 FormattingConversionServiceFactoryBean

    <bean id="studentConverter" class="com.nhky.converter.StudentConverter"></bean>
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
         	<property name="converters">
         		<set>
         			<ref bean="studentConverter"></ref>
         		</set>
         	</property>
    </bean> 
    

失败信息

上面如果失败,例如发生类型转换错误,格式化失败,都会把失败信息保存到 BindingResult。可以在上面的目标方法入参加上 BindingResult 来获取错误信息。(获取了这个错误信息,页面也不会跳转 404 等错误界面)

@RequestMapping(value = "insertStu", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
	public String insertEmp1(Student stu,BindingResult result) {
		System.out.println(stu);
		List<FieldError> errors = result.getFieldErrors();
		for (FieldError fieldError : errors) {
				System.out.println(fieldError.getField() + "-----" + fieldError.getDefaultMessage());
		}
		return "success.jsp";
	}

数据校验

JSR 303 是 Java 为 Bean 数据的合法性提供的一个标准框架,在 JavaEE 6.0 中,有着例如 @NULL,@NOTNULL,@MAX 等标准的注解指定校验规则。几个常用的如下:

序号注解说明
1@Null被注解的属性必须为NULL
2@NotNull被注解的属性必须不能为空
3@Past必须是一个过去的日期
4@Future必须是一个将来的日期
5@Pattern必须满足指定的正则规则
6@Max不能大于指定的最大值
7@Min不能小于指定的最小值
8@Future限制必须是一个将来的日期

范例:使用 @Email ,@Range 等注解进行数据校验

首先要导入 Hibernate Validator 扩展注解。

  1. 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
    
  2. Hibernate Validator 扩展了以下几个注解

    • @Email:必须符合电子邮箱格式

    • @Length:字符串长度必须在指定范围内

    • @NotEmpty:必须非空(包括空串)

    • @Range:必须在指定的返回内

  3. 在实体类上添加注解

    public class Student {
    	private Integer stuId;
    	@NotEmpty
    	private String stuName;
    	
    	@NotEmpty
    	@Email
    	private String stuMail;
    	private Integer stuGender;
    	private Integer stuAge;
    
    	
    	@Range(max = 8000,min = 2000)
    	@NumberFormat(pattern = "#,###,###,###.#")
    	private Double money;
    	
    	@Past
    	@DateTimeFormat(pattern = "yyyy-MM-dd")
    	private Date birthday;
    	....
        // getter setter 方法
    }
    
  4. 表单(这边要导入 <%@taglib prefix="f" uri="http://www.springframework.org/tags/form" %>

    <f:form  modelAttribute="stdu">
    		姓名:<f:input path="stuName"/>
    			<br>
    		邮箱:<f:input path="stuMail"/>
    		<br>
    		性别:<f:input path="stuGender" />
    		<br>
    		年龄:<f:input path="stuAge"/>
    		<br>
    		零花钱:<f:input path="money"/><br>
    		生日:<f:input path="birthday"/><br>
    		<f:button >提交</f:button>
    	</f:form>
    
  5. 编写目标处理器

    	@RequestMapping(value="stu",method = RequestMethod.GET)
    	public String getForm(Map map) {
    		Student stu = new Student();
    		map.put("stu", stu);
    		return "input2.jsp";
    	}
    	
    	@RequestMapping(value="stu",method = RequestMethod.POST)
    	public String getForm1(@Valid Student stu,BindingResult result,Map map) {
    		map.put("stu", stu);
    		List<FieldError> errors = result.getFieldErrors();
    		for (FieldError fieldError : errors) {
    			
    			System.out.println(fieldError.getField() + "-----" + fieldError.getDefaultMessage());
    		}
    		return "input2.jsp";
    	}
    
  6. 直接访问 http://localhost:8080/XXXX/stu ,输入如下信息

    我们就可以看到控制台打印如下:

    money-----需要在2000和8000之间
    stuName-----不能为空
    birthday-----需要是一个过去的事件
    stuMail-----不能为空
    
  7. 注意

    1. 如果 @Valid 报 400 错误。可能是 @Valid 后面如果使用 BindingResult ,那么 @Valid 所带的参数后面必须接上参数 BindingResult 这个参数,如果中间隔着其它会出现400错误。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值