一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
一、类型转换器
在日常的企业开发需求中,我们输入文本框的内容全部都是字符串类型,但是在后端处理的时候我们可以用其他基本类型来接受数据,也可以使用实体类来接受参数,这个是怎么完成的呢?就是通过SpringMVC提供的类型转换器,SpringMVC内部提供了非常丰富的类型转换器的支持,但是有些情况下有可能难以满足我们的需求,因此需要我们自己实现
比如我们做一个数据采集平台,从不同的平台采集数据,拿日期来说,有的平台日期格式为:yyyy-MM-dd,有的平台为yyyy/MM/dd,当然也有可能是其他格式,为了实现日期的统一实现,我们就需要自定义一个类型转换器
1-1、创建一个转换器
我们目前这个转化器只支持 yyyy-MM-dd yyyy/MM/dd
package com.jony.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyStringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if(!StringUtils.isEmpty(source)){
// 即支持yyyy-MM-dd yyyy/MM/dd
try {
if(source.split("-").length==3){
DateFormat df=new SimpleDateFormat("yyyy-MM-dd");
return df.parse(source);
}
else if(source.split("/").length==3){
DateFormat df=new SimpleDateFormat("yyyy/MM/dd");
return df.parse(source);
}
else
{
throw new RuntimeException("日期转换错误:"+source);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
复制代码
1-2、创建一个User bean,并将生日设为日期格式
package com.jony.entity;
import org.junit.Test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Date;
@Controller
@RequestMapping("/converter")
public class User {
private String name;
private Integer age;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", birthday=" + birthday +
'}';
}
}
复制代码
1-3、在spring.mvc 中进行类转化器配置
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.jony.converters.MyStringToDateConverter"></bean>
</set>
</property>
</bean>
复制代码
1-4、测试
先使用yyyy/MM/dd
再次使用yyyy-MM-dd
1-5、总结
在实际项目中,我们需要根据实际情况,创建类转化器,这样就不需要在各个业务中再次进行处理。使用步骤如下:
1.定义类型转换器 需要明确 源类型 和目标性
2.在convert方法自定义类型转换的实现
3.在springmvc中配置自定类型转换器\
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.jony.converters.MyStringToDateConverter"></bean>
</set>
</property>
</bean>
复制代码
4.设置springmvc注解驱动
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
复制代码
二、数据格式化
Spring 提供了两个可以用于格式化数字、日期和时间的注解@NumberFormat和@DateTimeFormat,这两个标签可以用于javabean的属性或方法参数上。@NumberFormat可以用来格式化任何的数字的基本类型(如int,long)或java.lang.Number的实例(如 BigDecimal, Integer)。@DateTimeFormat可以用来格式化java.util.Date、java.util.Calendar和 java.util.Long类型.
2-1、实际使用
package com.jony.entity;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.util.Arrays;
import java.util.Date;
public class User {
private Integer id;
private String username;
@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birthday;
@NumberFormat(style = NumberFormat.Style.CURRENCY ) //货币
private Double balance; //余额 ¥5000
private String[] hobbies;
@NumberFormat(pattern = "#,###.##")
private Double salary; //工资 10,000.00
@NumberFormat(style = NumberFormat.Style.PERCENT) /*不加%会 *100来显示 加上会按你提交精度来显示*/
private Double taskCount;// 任务完成百分比 90%
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Double getTaskCount() {
return taskCount;
}
public void setTaskCount(Double taskCount) {
this.taskCount = taskCount;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", birthday=" + birthday +
", balance=" + balance +
", hobbies=" + Arrays.toString(hobbies) +
", salary=" + salary +
", taskCount=" + taskCount +
'}';
}
}
复制代码
2-2、特点及使用方式
注解类型 DateTimeFormat,互斥属性:
iso。类型为DateTimeFormat.ISO
-
DateTimeFormat.ISO.DATE: 格式yyyy-MM-dd。
-
DateTimeFormat.ISO.DATE_TIME: 格式yyyy-MM-dd HH:mm:ss .SSSZ。
-
DateTimeFormat.ISO.TIME: 格式HH:mm:ss.SSSZ。
-
DateTimeFormat.ISO.NONE: 表示不使用ISO格式的时间。
pattern。类型为String,使用自定义的时间格式化字符串。
style。类型为String,通过样式指定日期时间的格式,由两位字符组成,第1位表示日期的样式,第2位表示时间的格式:
-
S: 短日期/时间的样式;
-
M: 中日期/时间的样式;
-
L: 长日期/时间的样式;
-
F: 完整日期/时间的样式;
-
-: 忽略日期/时间的样式;
NumberFormat pattern。类型为String,使用自定义的数字格式化字符串,"##,###.##"。
style。类型为NumberFormat.Style,常用值:
-
Style.NUMBER正常数字类型
-
Style.PERCENT百分数类型
-
Style.CURRENCY 货币类型
三、数据校验
一般情况下我们会在前端页面实现数据的校验,但是大家需要注意的是前端校验会存在数据的不安全问题,因此一般情况下我们都会使用前端校验+后端校验的方式,这样的话既能够满足用户的体验度,同时也能保证数据的安全,下面来说一下在springmvc中如何进行后端数据校验。
3-1、JSR303
JSR303是 Java 为 Bean 数据合法性校验提供的标准,它已经包含在JavaEE 6.0 中 。JSR 303 (Java Specification Requests意思Java 规范提案)通过在Bean属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。
Hibernate Validator(不属于Hibernate) 实现了JSR349验证注解规范的技术
JSR303:
Hibernate Validator 扩展注解:
3-2、JSR303的使用
spring中拥有自己的数据校验框架,同时支持JSR303标准的校验框架,可以在通过添加注解的方式进行数据校验。
实现数据校验的步骤:
1、加入org.hibernate包的依赖
2、这个地方需要注意的是,如果使用的是IDE,需要将新加入的jar包,放到WEB-INF/lib中。
3、在需要验证的javaBean的属性上假如对应的验证注解。
4、在需要验证的处理方法对应的javaBean参数上加上@Valid 在spring中本身没有提供JSR303的实现,需要导入依赖的包。
5、在需要验证的处理方法中假如BindingResult,代表自己处理错误,这样就会将错误信息显示到错误页面了。
3-2-1、引入jar包
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
复制代码
3-2-2、将新导入的包放到WEB-INF/lib中(IDE)
之前文章有描述,这边就不再演示了。
3-2-3、给javaBean属性添加注解
3-2-3、给方法参数的javaBean添加@Valid
3-2-4、访问测试
可以看到我们提交数据id为null的时候就会报错 然后修改ID为1
3-2-4、错误信息的处理
3-2-4-1、在控制层添加BindingResult
首先需要在方法中假如BindingResult类,这个类会接收到javaBean添加注解错误的信息。如果有错误信息可以通过getFieldErrors()获取错误信息集合,这时候也可以直接返回到页面输出,也可以将错误集合进行迭代放入map中,这样传到前台,就可以根据每个字段输出错误信息了
package com.jony.controller;
import com.jony.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/valid")
public class ValidController {
@RequestMapping("/user")
public String add(@Valid User user, BindingResult bindingResult, Model model){
//判断是否有错误
if(bindingResult.hasErrors()){
//创建一个map用于存放字段的错误
Map<String,String> errMap=new HashMap<>();
//获得所有字段的错误
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
//将错误信息迭代到map中
for(FieldError fieldError:fieldErrors){
errMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}
//将错误信息放到model中返回到页面中
model.addAttribute("errors",errMap);
return "user/add";
}
System.out.println(user);
return "show";
}
}
复制代码
3-2-4-2、页面接收错误信息
我们在添加页面,设置一下每个标签的value值,这样即使有错误信息,从Controller再次跳转到页面的时候,也能把User带到视图层。
3-2-4-3、测试
可以看大由于ID不能为null,我们提交之后错误信息可以返回到页面,并且其他值的数据也并未丢失。
3-2-5、其他验证属性的使用
package com.jony.entity;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import java.util.Arrays;
import java.util.Date;
public class User {
@NotNull(message = "id不能为空")
@Min(value=1,message = "id必须大于0")
private Integer id;
@NotEmpty(message = "用户名不能为空")
@Length(min = 4,max = 8,message = "用户名的长度必须在{min}和{max}位之间")
@Pattern(regexp="^[0-9a-zA-Z]+$",message = "用户名只能输入0-9或者a-z")
private String username;
@Past(message = "您难道穿越了吗?")
@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birthday;
@NumberFormat(style = NumberFormat.Style.CURRENCY ) //货币
private Double balance; //余额 ¥5000
private String[] hobbies;
@NumberFormat(pattern = "#,###.##")
@Range(min = 2000,max = 1000000,message = "工资必须在{min}和{max}之间") //Size的范围是int 不能用于验证Double
private Double salary; //工资 10,000.00
@Range(min=0,max=100,message = "任务完成度必须在{min}和{max}之间") //Range的范围是Long所以,Range可以控制的范围更大
@NumberFormat(style = NumberFormat.Style.PERCENT) /*不加%会 *100来显示 加上会按你提交精度来显示*/
private Double taskCount;// 任务完成百分比 90%
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Double getTaskCount() {
return taskCount;
}
public void setTaskCount(Double taskCount) {
this.taskCount = taskCount;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", birthday=" + birthday +
", balance=" + balance +
", hobbies=" + Arrays.toString(hobbies) +
", salary=" + salary +
", taskCount=" + taskCount +
'}';
}
}
复制代码
四、Springmvc form标签
什么使用html的标签接收错误信息比较繁琐,需要在Controller和视图端都做一些处理,鉴于此spring给我们提供了spring的form表单元素。
特点: 自动绑定,自动回显数据, 如果是新增的情况下也需要保证有该标签所需的对象
Form标签
-
支持全部http请求方法 比如method=”put” put\delete 提交方式
-
数据自动回显:需要使用modelAttribute指定数据的对象
-
使用path来双向绑定属性4. 动态数据绑定:Select 、checkboxes、 radiobottons、 都可以使用Items 制定数据源 可以是list (当List的泛型是javaBean的时候需要制定itemValue和itemLabel)、map(不需要制定itemValue和itemLabel)
4-1、原生html和spring form标签的实现区别
* **基于原生html form表单实现方式**
复制代码
- 1.在将错误信息循环通过map存入到request域中
- 2.在jsp通过${errors.id}获取对应的错误信息
- 基于spring form标签库的实现方式
- 1.添加一个显示jsp的处理方法, 一定要传入一个空的User到model中
- 2.在jsp中导入spring-form标签库
- 3.在form标签上一定要加上ModelAtrribute
- 4.加上对应的form标签 必须都要以<form:开头
4-2、spring form标签的实现过程
4-2-1、添加显示jsp的处理方法
这个必须传入User到mode中,之前提到 放到方法参数中可以直接传到视图中
/**
* 跳转至add 页面
* @param user
* @return
*/
@RequestMapping("/u/add")
public String toAdd(User user){
return "user/add";
}
复制代码
4-2-2、修改Controller
可以看到已经取消了之前迭代错误集合并放入map中的处理逻辑
/**
* 使用spring form标签
* @param user
* @param bindingResult
* @param model
* @return
*/
@RequestMapping("/dataValidate")
public String addNew(@Valid User user, BindingResult bindingResult, Model model) {
//判断是否有错误
if (bindingResult.hasErrors()) {
System.out.println("验证失败");
return "user/add";
}else{
return "show";
}
}
复制代码
4-2-3、jsp处理
4-2-3-1、导入spring-form标签库
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
复制代码
4-2-3-2、form表单的创建
在form标签上一定要加上ModelAtrribute,然后标签都是以<form开头
<form:form action="${pageContext.request.contextPath}/form/user" method="post" modelAttribute="user">
<p>
id: <form:input path="id"></form:input><form:errors path="id"></form:errors>
</p>
<p>
username: <form:input path="username"></form:input><form:errors path="username"></form:errors>
</p>
<p>
birthday:<form:input path="birthday"></form:input><form:errors path="birthday"></form:errors>
</p>
<p>
balance:<form:input path="balance"></form:input><form:errors path="balance"></form:errors>
</p>
<p>
salary:<form:input path="salary"></form:input><form:errors path="salary"></form:errors>
</p>
<p>
taskCount:<form:input path="taskCount"></form:input><form:errors path="taskCount"></form:errors>
</p>
<p>
hobbies:
<%--静态数据源--%>
<form:checkbox path="hobbies" value="唱歌"></form:checkbox>
<form:label path="hobbies">唱歌</form:label>
<form:checkbox path="hobbies" value="跳舞"></form:checkbox>
<form:label path="hobbies">跳舞</form:label>
<hr>
<%--动态数据源--%>
<form:checkboxes path="hobbies" items="${list}"></form:checkboxes>
<%--<form:select path="hobbies"></form:select>--%>
</p>
<p>
<form:button>提交</form:button>
<input type="submit" value="提交">
</p>
</form:form>
复制代码
4-2-4、测试
一定要从Controller跳转到页面,并且带入User,因为我们表单中有个modelAttribute属性,指向user
可以看到这样错误信息就可以成功显示了
4-2-5、数据动态绑定
动态数据绑定:Select 、checkboxes、 radiobottons、 都可以使用Items 制定数据源 可以是list(当List的泛型是javaBean的时候需要制定itemValue和itemLabel)、map(不需要制定itemValue和itemLabel)
4-2-5-1、修改跳转视图控制器、添加数据
/**
* 跳转至add 页面
* @param user
* @return
*/
@RequestMapping("/u/add")
public String toAdd(User user,Model model){
List<String> strings = Arrays.asList("唱歌", "跳舞");
model.addAttribute("list",strings);
return "user/add";
}
复制代码
4-2-5-2、测试
缺点:通过使用list想视图传值,value和lable就一样了,一般情况来说这样是不可行的,如下图
因此我们需要对数据进行修改,再次修改控制器如下:
/**
* 跳转至add 页面
* @param user
* @return
*/
@RequestMapping("/u/add")
public String toAdd(User user,Model model){
// List<String> strings = Arrays.asList("唱歌", "跳舞");
// model.addAttribute("list",strings);
//使用map方式传值
Map<String,String> map=new HashMap<>();
map.put("1","唱歌");
map.put("2","跳舞");
model.addAttribute("list",map);
return "user/add";
}
复制代码
再次打开视图,这样value就是我们需要的值了。