1. 服务端数据校验
1. 1 JSR303校验
1.1.1 简介
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
注:可以使用注解的方式进行验证
1.1.2 校验规则
-
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY. -
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false -
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included. -
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。 -
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
1.2 用法
1.2.1 添加jar包
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.16.Final</version>
</dependency>
1.2.2 为参数对象添加注解
@RequestMapping("/register")
public String register(@Valid UserVo userVo, Errors error){
//手动进行服务端数据校验
//if(userVo.getAge() < 0 || userVo.getAge() > 120){
// error.reject("年龄只能在0-120之间");//手动添加错误
//}
//判断是否有错误
if(error.hasErrors()){
System.out.println(error);
return "register";
}
System.out.println("UserController.register.userVo:" + userVo);
return "success";
}
1.2.3 为属性添加校验注解
在UserVo类中,在age属性上方添加如下注解
@Range(min = 1,max = 120,message = "年龄必须在1120之间")
private Integer age;
运行程序,在注册页面年龄一栏输入非法年龄,控制台输出如下
org.springframework.validation.BeanPropertyBindingResult: 1 errors Field error in object 'userVo' on field 'age': rejected value [123]; codes [Range.userVo.age,Range.age,Range.java.lang.Integer,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userVo.age,age]; arguments []; default message [age],120,1]; default message [年龄必须在1120之间]
1.2.4 为其他属性添加校验注解
@NotEmpty(message = "用户名不能为空")
@Pattern(regexp = "\\w{6,10}",message = "用户名只能包含数字、字母、下划线,且 长度为610位")
private String username;
@Length(min = 4,max = 10,message = "密码必须为410位")
private String password;
@Pattern(regexp = "(139|133|131)\\d{8}",message = "手机号码格式不正确")
private String phone;
@Email(message = "邮箱格式不正确")
private String email;
2. 数据类型转换
我们在注册页面添加一个地址输入框,然后添加一个实体类Address,其有province和city属性。
在UserVo中添加属性Address,通过用POST提交的方式来获取表单中的地址信息。
Filter解决中文乱码问题
首先遇到一个小问题,就是在注册页面填写中文地址后,控制台界面输出乱码
只需要在web.xml中添加过滤器即可
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
添加完过滤器后,在注册页面输入地址[北京-海淀]
,控制台输出错误信息。从前端传入的地址信息是String类型,而在后端需要接收Address类型的数据,所以需要进行类型转换
2.1 类型转换的方式
数据绑定流程:获取值——>查找转换器——>转换——>后台校验——>数据绑定
两种解决方式:
- 方式一:使用PropertyEditor
- 方式二:使用Converter(推荐)
2.2 使用PropertyEditor方式
2.2.1 使用步骤
- 定义属性编辑器
package editor;
import entity.Address;
import java.beans.PropertyEditorSupport;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AddressEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
Address address = (Address)getValue();
return "[" + address.getProvince() + "-" + address.getCity() + "]";
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
Pattern pattern = Pattern.compile("\\[(.*)-(.*)\\]");
Matcher matcher = pattern.matcher(text);
if(matcher.matches()){
String city = matcher.group(2);
String province = matcher.group(1);
Address address = new Address();
address.setCity(city);
address.setProvince(province);
setValue(address);
}
}
}
- 在Controller层注册属性编辑器
在UserController.java中添加如下代码
@InitBinder
public void initBinder(DataBinder binder){
binder.registerCustomEditor(Address.class, new AddressEditor());
}
配置完之后,程序就可以正常获取前端传过来的Address值
2.2.2 缺点
- 代码嵌套在Controller层中
- 只能从字符串转换
2.3 使用Converter方式
步骤:
- 定义转换器
package converter;
import entity.Address;
import org.springframework.core.convert.converter.Converter;
import javax.management.RuntimeErrorException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class String2AddressConverter implements Converter<String, Address> {
@Override
public Address convert(String s) {
Pattern pattern = Pattern.compile("\\[(.*)-(.*)\\]");
Matcher matcher = pattern.matcher(s);
if(matcher.matches()){
String city = matcher.group(2);
String province = matcher.group(1);
Address address = new Address();
address.setCity(city);
address.setProvince(province);
return address;
}else {
throw new RuntimeException("地址转换失败");
}
}
}
public class Address2StringConverter implements Converter<Address, String> {
@Override
public String convert(Address address) {
return "[" + address.getProvince() + "-" + address.getCity() + "]";
}
}
- 管理自定义转换器
在springmvc.xml中添加管理自定义转换器的代码
<!--管理自定义转换器-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="converter.String2AddressConverter"/>
<bean class="converter.Address2StringConverter"/>
</set>
</property>
</bean>
- 加载应用自定义转换器
以前在springmvc.xml写过如下代码:<mvc:annotation-driven/>
,这是mvc的注解驱动,可以用来简化配置。而且默认加载内置的类型转换器。如果要加载自定义的类型转换器,需要作如下声明
<mvc:annotation-driven conversion-service="conversionService"/>
配置完成之后,自定义的转换器就配置好了
3. @SessionAttribute
3.1
在实际开发中,注册页面一般分为几个步骤,第一个页面只需要填用户名和密码,之后再补充详细资料。我们也试着这样完成。
于是,添加一个SessionController.java
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import vo.UserVo;
@Controller
@RequestMapping("/session")
public class SessionController {
@RequestMapping("/step1")
public String step1(UserVo userVo){
System.out.println("step2.userVo: " + userVo);
return "step1";
}
@RequestMapping("/step2")
public String step2(UserVo userVo){
System.out.println("step2.userVo: " + userVo);
return "step2";
}
@RequestMapping("/step3")
public String step3(UserVo userVo){
System.out.println("step3.userVo: " + userVo);
return "step3";
}
@RequestMapping("/register")
public String register(UserVo userVo){
System.out.println("register.userVo: " + userVo);
return "success";
}
}
将register.jsp页面分成三个step1.jsp、step2.jsp、step3.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>register1</title>
</head>
<body>
<h2>用户注册</h2>
<form action="${pageContext.request.contextPath}/session/step2" method="post">
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="下一步">
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>register2</title>
</head>
<body>
<h2>用户注册</h2>
<form action="${pageContext.request.contextPath}/session/step3" method="post">
年龄:<input type="text" name="age"> <br>
手机号:<input type="text" name="phone"> <br>
<input type="submit" value="下一步">
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>register3</title>
</head>
<body>
<h2>用户注册</h2>
<form action="${pageContext.request.contextPath}/session/register" method="post">
邮箱:<input type="text" name="email"> <br>
地址:<input type="text" name="address"> <br>
<input type="submit" value="注册">
</form>
</body>
</html>
程序运行后,在每个页面输入用户数据,注册成功后,查看控制台输出
发现userVo
里的数据在每一次请求中都不一样,这时候@SessionAttributes
就能排上用场了
3.2 @SessionAttribute简介
- 若希望在多个请求之间共用数据,则可以在控制器类上标注一个 @SessionAttributes,配置需要在session中存放的数据范围,SpringMVC将存放在model中对应的数据暂存到 HttpSession 中。
- @SessionAttributes只能使用在类定义上。
- @SessionAttributes 除了可以通过属性名指定需要放到会 话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中 例如:
- @SessionAttributes(types=User.class)会将model中所有类型为 User的属性添加到会话中。
- @SessionAttributes(value={“user1”, “user2”}) 会将model中属性名为user1和user2的属性添加到会话中。
- @SessionAttributes(types={User.class, Dept.class}) 会将model中所有类型为 User和Dept的属性添加到会话中。
- @SessionAttributes(value={“user1”,“user2”},types={Dept.class})会将model中属性名为user1和user2以及类型为Dept的属性添加到会话中。
于是,我们在SessionController类添加上@SessionAttributes
注释
@Controller
@RequestMapping("/session")
@SessionAttributes("userVo")
public class SessionController {
再此运行程序,就会得到完整的信息,控制台输出如下
4. 统一异常处理
4.1 简介
默认的异常页是对用户或者调用者而言都是不友好的,所以一般上我们都会进行实现自己业务的异常提示信息。
统一异常处理的方式:
- 使用web技术提供的统一异常处理
- 使用SpringMVC提供的统一异常处理
4.2 使用web技术
在web.xml中,根据不同的错误码,对不同的错误进行处理
<!--404异常-->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
<!--500异常-->
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
如果出现404异常,系统就会使用404.jsp作为信息提示页
但是,使用web技术处理异常的方式比较粗略,使用SpringMVC提供的异常处理技术可以对异常进行仔细的划分。
4.3 使用SpringMVC提供的异常处理技术
步骤1
首先创建一个异常测试类
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/exception")
public class ExceptionTest {
@RequestMapping("/test1")
public String test1(){
int i = 5/0;
return "success";
}
@RequestMapping("/test2")
public String test2(){
String s = null;
s.toString();
return "success";
}
}
步骤2
定义一个异常处理类ExceptionAdvice.java(通知),添加@ControllerAdvice
在异常处理类中添加定义异常处理方法,添加@ExceptionHandler
package controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public String arithmetic(ExceptionTest e){
System.out.println("程序出现异常:" + e);
return "error/arithmetic";
}
@ExceptionHandler(NullPointerException.class)
public String nullPointer(ExceptionTest e){
System.out.println("程序出现异常:" + e);
return "error/nullpointer";
}
@ExceptionHandler(java.lang.Exception.class)
public String exception(ExceptionTest e){
System.out.println("程序出现异常:" + e);
return "error/exception";
}
}
步骤3
添加异常处理反馈页面,以arithmetic.jsp为例
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>arithmetic</title>
</head>
<body>
程序出现了异常:ArithmeticException
</body>
</html>
步骤4 测试
跳转到url/exception/test1
时,会跳转到如下页面