SpringMVC day03
1 数据类型转换:日期类型
SpringMVC默认可以转换 yyyy/MM/dd HH:mm:ss
格式的字符串转换为日期类型。如果使用其它格式,就需要自定义类型转换。
1.1 DateTimeFormat注解
@Controller
public class DateController {
@RequestMapping("/date")
public String date(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
System.out.println("date = [" + date + "]");
return "forward:/index.jsp";
}
}
特点:简单灵活,但是编码繁琐(有多少处需要转换,就需要配置多少处)
1.2 Converter 类型转换器
-
编码 实现Converter接口
public class DateConverter implements Converter<String, Date> { private String pattern; public void setPattern(String pattern){ this.pattern = pattern; } @Override //将请求中的字符串参数解析为目标日期类型数据 public Date convert(String source) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); try { Date date = simpleDateFormat.parse(source); return date; }catch(Exception e){ throw new IllegalArgumentException("必须提供合法的日期字符串数据"); } } }
-
配置
<!-- 配置自定义类型转换器--> <bean id="dateConverter" class="com.baizhi.converter.DateConverter"> <property name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> <!-- 配置类型转换器工厂 --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="dateConverter"/> </set> </property> </bean> <!-- 配置mvc框架使用类型转换器工厂 --> <mvc:annotation-driven conversion-service="conversionService"/>
注意:
一旦配置Converter,注解将不再起作用。
1.3 Formatter类型转换器
-
编码
public class DateFormatter implements Formatter<Date> { private String pattern; private SimpleDateFormat sdf; public void setPattern(String pattern) { this.pattern = pattern; this.sdf = new SimpleDateFormat(pattern); } @Override // 将请求中的字符串参数解析为日期类型数据 public Date parse(String text, Locale locale) throws ParseException { System.out.println("DateFormatter.parse"); Date date = null; try{ date = sdf.parse(text); }catch (ParseException e){ throw new IllegalArgumentException(e); } return date; } @Override // 设置日期类型数据解析为特定格式的字符串 public String print(Date object, Locale locale) { return sdf.format(object); } }
-
配置
<!-- 配置mvc框架使用类型转换器工厂 --> <mvc:annotation-driven conversion-service="conversionService2"/> <!-- 配置类型转换器工厂--> <bean id="conversionService2" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatters"> <set> <!-- 直接将DateFormatter的bean配置在set标签中,作为set的一个元素--> <bean class="com.baizhi.formatter.DateFormatter"> <property name="pattern" value="yyyy-MM-dd HH:mm:ss"/> </bean> </set> </property> </bean>
注意:
一旦配置Formatter类型转换器,Converter类型转换器将不再起作用
2 SpringMVC异常处理
程序在运行时不出现异常是很难得的。现在我们写的代码中,当代码里出现异常的时候,会跳转到 Tomcat 的 500 页面显示一堆的异常信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2zVFoS2-1630975505208)(SpringMVC day03.assets/image-20210626232830096.png)]
现在的效果存在着2个问题:
- 用户体验差:大多数的程序运行时的异常并不严重,但再小的错误对于小白用户的冲击都是极大的
- 暴露代码信息:异常堆栈信息暴露了程序运行的流程、错误原因、使用的框架技术,为黑客攻击提供了信息
在日常的项目开发中,我们一定要web层对dao和service层中不能处理的错误和异常进行处理,这就需要一个统一的异常处理机制处理异常。保证客户端能够收到友好的提示,这么做的目的主要是给用户一个较好的使用体验,避免一些错误信息或者错误页面直接暴露到用户面前。
2.1 实战中异常处理套路
实战中,在dao和service层未能及时处理的异常,一定在web层处理。
-
实战中,为了精细化分辨各种异常通常我们会自定义异常。在dao和service中如果不能处理异常,将其将异常包装成自定义异常,然后再抛出。
public class DaoException extends RuntimeException{ //此处省略的是构造方法 ... } public class ServiceException extends RuntimeException{ //此处省略的是构造方法 ... }
-
在controller中的服务方法中不使用try{}catch…处理异常,而是通过SpringMVC的全局异常处理机制,统一处理程序中抛出的所有异常。
SpringMVC中提供了全局异常处理机制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZ39Pttf-1630975505210)(SpringMVC day03.assets/image-20210626235444538.png)]
2.2 HandlerExceptionResolver接口
第1种方式,编码方式:实现HanlderInterceptor接口
-
在Controller中模拟程序中各种异常
@Controller @RequestMapping("ex") public class ExceptionController { @RequestMapping("throw") public String testThrow(){ System.out.println("ExceptionController.testThrow"); throw new RuntimeException("Controller运行时异常"); } @RequestMapping("dao") public String testDao(){ System.out.println("ExceptionController.testDao"); throw new DaoException("运行时,dao出现异常"); } @RequestMapping("service") public String testService(){ System.out.println("ExceptionController.testService"); throw new ServiceException("运行时,service出现异常"); } }
-
编码:定义全局异常处理类
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override /* request 请求 response 响应 handler 服务方法 ex 异常对象 */ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("MyHandlerExceptionResolver.resolveException"); //将异常信息保存到request作用域 request.setAttribute("message",ex.getMessage()); //根据异常类型,进行针对性的处理 if(ex instanceof DaoException){ System.out.println("Dao层产生的异常"); return new ModelAndView("forward:/error-dao.jsp"); }else if(ex instanceof ServiceException){ System.out.println("Service层产生的异常"); return new ModelAndView("forward:/error-service.jsp"); } //其他异常,统一跳转到error.jsp return new ModelAndView("forward:/error.jsp"); } }
-
配置
<!-- 配置自定义的全局异常解析器--> <bean class="com.baizhi.exception.MyHandlerExceptionResolver"/>
2.3 ExceptionHandler和ControllerAdvice注解
第2种方式:使用ExceptionHandler注解
2.3.1 ExceptionHandler注解基本使用
-
在Controller类中定义一个处理异常的方法
@Controller @RequestMapping("ex") public class ExceptionController { @RequestMapping("throw") public String testThrow(){ System.out.println("ExceptionController.testThrow"); throw new RuntimeException("Controller运行时异常"); } @RequestMapping("dao") public String testDao(){ System.out.println("ExceptionController.testDao"); throw new DaoException("运行时,dao出现异常"); } @RequestMapping("service") public String testService(){ System.out.println("ExceptionController.testService"); throw new ServiceException("运行时,service出现异常"); } //处理异常的方法 public ModelAndView handleException(){ System.out.println("ExceptionController.handleException"); return "forward:/error.jsp"; } }
-
在处理异常的方法上添加ExceptionHandler注解
//添加注解,并明确处理方法要处理的异常类型 @ExceptionHandler(Exception.class) public ModelAndView handleException(){ System.out.println("ExceptionController.handleException"); return new ModelAndView("forward:/error.jsp"); }
说明:
- ExceptionHandler注解描述处理方法,在服务方法发生设置类型的异常时执行处理方法
- ExceptionHandler注解方式优先于HandlerExceptionResolver接口方式执行
2.3.2 ExceptionHandler注解详解
-
处理方法的形参列表很灵活,可以定义
HttpServletRequest
、HttpServletResponse
、Model
和 各种异常类型参数;返回值类型也可以定义为String
/* request 请求 response 响应 model 模型,传递数据 e 程序运行出现的异常 */ @ExceptionHandler(Exception.class) public String handleException(HttpServletRequest request, HttpServletResponse response, Model model,Exception e){ System.out.println("ExceptionController.handleException"); model.addAttribute("message", e.getMessage()); if(e instanceof DaoException){ System.out.println("Dao层产生的异常"); return "forward:/error-dao.jsp"; }else if(e instanceof ServiceException){ System.out.println("Service层产生的异常"); return "forward:/error-service.jsp"; } return "forward:/error.jsp"; }
需要提升的地方:在处理方法中instanceof判断异常类型,提供针对性解决方案,过程比较麻烦。
-
一个Controller中可以定义多个ExceptionHandler方法,根据ExceptionHandler注解中设置的异常类型进行匹配。
//处理DaoException异常 @ExceptionHandler(DaoException.class) public String handleDaoException(HttpServletRequest req,HttpServletResponse resp,Model model,DaoException daoException){ System.out.println("ExceptionController.handleDaoException"); model.addAttribute("message",daoException.getMessage()); return "error-dao"; } //处理ServiceException异常 @ExceptionHandler(ServiceException.class) public String handleServiceException(HttpServletRequest req,HttpServletResponse resp,Model model,ServiceException serviceException){ System.out.println("ExceptionController.handleServiceException"); model.addAttribute("message",serviceException.getMessage()); return "error-service"; } //处理Exception异常 @ExceptionHandler(Exception.class) public String handleException(HttpServletRequest req,HttpServletResponse resp,Model model,Exception exception){ System.out.println("ExceptionController.handleException"); model.addAttribute("message",exception.getMessage()); return "error"; }
2.3.3 ControllerAdvice注解
单独使用 ExceptionHandler
注解,存在很明显的问题:多个Controller类处理相同异常时,需要将处理方法在多个Controller类中重复定义。这时候,就需要使用 ControllerAdvice
注解配合来解决问题。
-
将所有的处理方法,单独抽取到一个类中
public class ExceptionHandlerAdvice { //处理DaoException异常 @ExceptionHandler(DaoException.class) public String handleDaoException(HttpServletRequest req, HttpServletResponse resp, Model model, DaoException daoException) { System.out.println("ExceptionHandlerAdvice.handleDaoException"); model.addAttribute("message", daoException.getMessage()); return "error-dao"; } //处理ServiceException异常 @ExceptionHandler(ServiceException.class) public String handleServiceException(HttpServletRequest req, HttpServletResponse resp, Model model, ServiceException serviceException) { System.out.println("ExceptionHandlerAdvice.handleServiceException"); model.addAttribute("message", serviceException.getMessage()); return "error-service"; } //处理Exception异常 @ExceptionHandler(Exception.class) public String handleException(HttpServletRequest req, HttpServletResponse resp, Model model, Exception exception) { System.out.println("ExceptionHandlerAdvice.handleException"); model.addAttribute("message", exception.getMessage()); return "error"; } }
-
在这个异常处理类上添加
ControllerAdvice
注解@ControllerAdvice public class ExceptionHandlerAdvice { ... }
说明:
-
ControllerAdvice
注解描述的类,默认会增强所有@Controller
类(当前是异常处理的增强);可以为注解赋值,明确要增强哪个包下的@Controller
类。@ControllerAdvice("com.baizhi.controller") public class ExceptionHandlerAdvice { ... }
-
处理异常时,Controller类中的处理方法优先级更高
实战中,推荐使用注解方式处理程序中的异常。
3 参数校验
在Controller接受请求的参数时,为保证程序运行的正确性,必须要对请求中的参数进行数据格式的校验,比如说非空、长度限制…。当参数格式不满足要求时,Controller中要返回对应的错误信息。
传统的参数校验就在Controller服务方法中添加if语句做判断,有多少参数需要校验,就添加多个if语句:
@RequestMapping("add")
public String addStudent(Student s,Model model){
if(s.getStudentName() == null || s.getStudentName().isEmpty()){
model.addAttribute("studentName","学员姓名名不能为空");
}
if(s.getAge() == null || s.getAge() <= 0){
errors.put("age","学员年龄不能为空,且必须大于0");
}
...
if(errors.size() > 0){
model.addAttribute("errors",map);
return "forward:/addStudent.jsp";
}
System.out.println("s = " + s);
return "redirect:/index.jsp";
}
优化方案:使用JSR303注解方式简化参数校验。
JSR303 是一套 JavaBean 参数校验的标准,它简化了参数的校验操作。典型代码如下:
public class Student {
private Integer id;
//NotBlank 用于字符串,限制不能为null、空串、空格字符
@NotBlank(message="学员姓名不能为空")
private String studentName;
//NotNull 限制不能为null
@NotNull(message="年龄不能为空")
private Integer age;
...
}
3.1 JSR303校验基本使用
JSR303是JavaEE 6 中提出的参数校验规范。当然,只有规范不行,还得有落地实现,SpringWebMvc 选择了 Hibernate-Validator 作为 JSR-303 的落地实现。
开发步骤:
-
导入依赖
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.5.Final</version> </dependency>
-
配置springmvc-servlet.xml,开启注解校验
<!-- 将校验器注入到springmvc中 --> <mvc:annotation-driven conversion-service="conversionService" validator="validator"/> <!-- 配置校验器 --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> </bean>
-
实体属性添加校验注解
public class Student implements Serializable { private Integer studentId; @NotNull(message = "学员姓名不能为空") private String studentName; @NotNull(message = "学员年龄不能为空") private Integer age; ... }
-
服务方法形参前添加
Validated
注解,在校验的形参后添加BindingResult
类型参数@RequestMapping("add") //添加ModelAttribute注解原因:将student保存到model中,一旦参数校验失败,方便在jsp页面回显原有的学员数据 public String addStudent(@ModelAttribute("s") @Validated Student student, BindingResult bindingResult, Model model){ //如果bindingResult中校验错误,说明有参数校验失败 if (bindingResult.hasErrors()) { System.out.println("bindingResult.hasErrors()"); //遍历获取所有的校验错误信息,保存到map中 bindingResult.getAllErrors().forEach(objectError -> { if(objectError instanceof FieldError) { FieldError fieldError = (FieldError)objectError; model.addAttribute(fieldError.getField(), fieldError.getDefaultMessage()); System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage()); }else{ model.addAttribute(objectError.getObjectName(), objectError.getDefaultMessage()); System.out.println(objectError.getObjectName()+":"+objectError.getDefaultMessage()); } }); //将异常信息保存到model中,在addStudent.jsp回显错误信息 return "forward:/addStudent.jsp"; } System.out.println("s = " + student); return "redirect:/index.jsp"; }
注意:
- 在实体类型形参前添加@Validated注解
- 在实体类型形参后紧跟BindingResult形参,用来保存校验信息。每多添加一个校验的参数,就要配对紧跟一个BindingResult形参。
- 检验的数据类型只能是复杂的对象类型参数,不能校验单类型参数。
-
addStudent.jsp回显校验失败信息
<form action="${pageContext.request.contextPath}/student/add.do" method="post"> 学员姓名: <input type="text" name="studentName" value="${s.studentName}"> <span>${requestScope.studentName}</span> <br> 学员年龄: <input type="text" name="age" value="${s.age}"> <span>${requestScope.age}</span> <br> <input type="submit" value="添加"> </form>
3.2 嵌套校验
当属性又是一个实体类型时,如果需要级联嵌套校验,则需要在实体属性上添加 Valid
注解。
实体类:
public class Student implements Serializable {
private Integer studentId;
@NotBlank(message = "学员姓名不能为空")
private String studentName;
@NotNull(message = "学员年龄不能为空")
private Integer age;
@NotNull(message = "学员的地址不能为空")
@Valid//必须添加该注解,否则不会对addr的子属性进行校验
private Address addr;
...
}
public class Address implements Serializable {
@NotBlank(message = "城市不能为空")
private String city;
@NotBlank(message = "街道不能为空")
private String street;
...
}
addStudent.jsp回显addr子属性的校验信息时,需要特殊处理:
<form action="${pageContext.request.contextPath}/student/add.do" method="post">
学员姓名: <input type="text" name="studentName" value="${s.studentName}" id="">
<span>${requestScope.studentName}</span> <br>
学员年龄: <input type="text" name="age" value="${s.age}">
<span>${requestScope.age}</span> <br>
城市: <input type="text" name="addr.city" value="${s.addr.city}"/>
<!-- 这里要特殊处理-->
<span>${requestScope["addr.city"]}</span> <br>
街道: <input type="text" name="addr.street" value="${s.addr.street}"/>
<span>${requestScope["addr.street"]}</span> <br/>
<input type="submit" value="添加">
</form>
3.3 分组校验
对于同一个类型的实体参数,添加和修改的数据校验规则不同。例如:新增方法 实体id不能指定值可以为null,但是修改id不能为null,怎么来分情况进行校验呢?这时候就可以使用分组校验功能。
-
定义分组接口:创建两个标记接口(没有任何方法的空接口)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWt7zxoA-1630975505212)(SpringMVC day03.assets/image-20210627234417644.png)]
-
在校验注解中,配置groups属性设置分组
public class Student implements Serializable { @NotNull(message = "学员姓名不能为空",groups = {UpdateGroup.class}) private Integer studentId; @NotNull(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class}) private String studentName; @NotNull(message = "学员年龄不能为空",groups = {SaveGroup.class,UpdateGroup.class}) private Integer age; @NotNull(message = "学员的地址不能为空",groups = {SaveGroup.class,UpdateGroup.class}) @Valid//必须添加该注解,否则不会对addr的子属性进行校验 private Address addr; ... }
-
在Controller服务方法的Validated注解中配置要使用的校验规则分组
@RequestMapping("update") public String updateStudent(@ModelAttribute("s") @Validated({UpdateGroup.class}) Student student, BindingResult bindingResult, Model model){ ... } @RequestMapping("add") public String addStudent(@ModelAttribute("s") @Validated({SaveGroup.class}) Student student, BindingResult bindingResult, Model model){ ... }
注意:
一旦开启分组,则所有校验注解都要明确自己使用的分组信息。否则,校验注解将无法发挥作用。
3.4 JSR303的常见注解
注解 | 约束 |
---|---|
@Null | 限制属性只能为 null |
@NotNull | 限制属性不能为null(无法查检长度为0的字符串) |
@NotBlank | 限制字符串值不能为null、空串(长度为0的字符串)、空白字符 |
@NotEmpty | 限制字符串、数组、集合不能为null,长度不能为0 |
@Size(min=, max=) | 限制字符串、数组、集合的长度范围 |
@Pattern | 限制字符串内容必须满足正则表达式的格式要求 |
@Past | 限制日期类型数据必须是一个过去的时间(比当前时间早) |
@Future | 限制日期类型数据必须是一个未来的时间(比当前时间晚) |
@Min | 限制数字、字符串的最小值(必须大于等于指定的值) |
@Max | 限制数字、字符串的最大值(必须小于等于指定值) |
@Range(min=,max=) | 限制整数数值必须在设定的范围内 |
@DecimalMin | 限制小数数值的最小值(必须大于等于指定的值) |
@DecimalMax | 限制小数数值的最大值(必须小于等于指定值) |
典型案例:
public class Student implements Serializable {
@NotNull(message = "学员编号不能为空",groups = {UpdateGroup.class})
private Integer studentId;
@NotBlank(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
private String studentName;
@NotNull(message = "学员年龄不能为空",groups = {SaveGroup.class,UpdateGroup.class})
@Range(min = 18,max = 100,message = "学员年龄必须在18~100之间",groups = {SaveGroup.class,UpdateGroup.class})
private Integer age;
@NotNull(message = "生日不能为空",groups = {SaveGroup.class,UpdateGroup.class})
@Past(message = "生日必须是过去的某一个时间",groups = {SaveGroup.class,UpdateGroup.class})
private Date birthday;
@NotNull(message = "分数不能为空",groups = {SaveGroup.class,UpdateGroup.class})
@DecimalMin(value = "0.5",message = "分数最低不能低于0.5",groups = {SaveGroup.class,UpdateGroup.class})
@DecimalMax(value = "100.0",message = "分数再高不能高于100.0",groups = {SaveGroup.class,UpdateGroup.class})
private Double score;
@NotNull(message = "爱好不能为空")
@Size(min = 1,max=4,message = "最少选择一个,最多四个爱好",groups = {SaveGroup.class,UpdateGroup.class})
private List<Integer> favorites;
@NotNull(message = "手机号不能为空",groups={SaveGroup.class,UpdateGroup.class})
@Pattern(regexp = "^1(3[0-9]|5[0-3,5-9]|7[1-3,5-8]|8[0-9])\\d{8}$",
message = "手机号格式不正确",groups = {SaveGroup.class,UpdateGroup.class})
private String phone;
@NotNull(message = "学员的地址不能为空",groups = {SaveGroup.class,UpdateGroup.class})
@Valid//必须添加该注解,否则不会对addr的子属性进行校验
private Address addr;
...
}
addStudent.jsp
<form action="${pageContext.request.contextPath}/student/add.do" method="post">
学员姓名: <input type="text" name="studentName" value="${s.studentName}" id=""> <span>${requestScope.studentName}</span> <br>
学员年龄: <input type="text" name="age" value="${s.age}"> <span>${requestScope.age}</span> <br>
出生日期:<input type="text" name="birthday" value="${s.birthday}"/> <span>${requestScope.birthday}</span><br>
分数:<input type="text" name="score" value="${s.score}"/> <span>${requestScope.score}</span> <br>
爱好:
<input type="checkbox" name="favorites" value="1"> 唱
<input type="checkbox" name="favorites" value="2"> 跳
<input type="checkbox" name="favorites" value="3"> rap
<input type="checkbox" name="favorites" value="4"> 打篮球
${requestScope.favorites}<br>
城市: <input type="text" name="addr.city" value="${s.addr.city}"/> <span>${requestScope["addr.city"]}</span> <br>
街道: <input type="text" name="addr.street" value="${s.addr.street}"/> <span>${requestScope["addr.street"]}</span>
<br/>
<input type="submit" value="添加">
</form>
3.5 校验信息外部化
当前的校验方式存在一个明显的问题:校验提示信息硬编码在了程序中,不利于后续的维护。此时,就需要将校验信息抽取到外部配置文件中
-
将提示信息以
key=value
的形式,抽取到properties文件中。validatation-student-message.properties
student.studentId.notNull=学员标号 student.studentName.notblank=学员姓名不能为空 student.age.notnull=学员年龄不能为空 student.age.range=学员年龄必须在18~100之间 student.birthday.notnull=生日不能为空 student.birthday.past=生日必须是过去的某一个时间 student.score.notnull=分数不能为空 student.score.decimalmin=分数最低不能低于0.5 student.score.decimalmax=分数最高不能高于100.0 student.favorites.notnull=爱好不能为空 student.favorites.size=最少选择一个,最多选择4个 student.addr.notnull=学员地址不能为空
validatation-address-message.properties
address.city.notblank=城市不能为空 address.street.notblank=街道不能为空
-
在springmvc-servlet.xml中配置使用properties文件
<!-- 将校验器注入到springmvc中 --> <mvc:annotation-driven conversion-service="conversionService" validator="validator"/> <!-- 配置校验器 --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <property name="validationMessageSource" ref="validationMessageSource"/> </bean> <!--配置 校验消息的路径--> <bean id="validationMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <array> <value>classpath:validatation-student-message</value> <value>classpath:validatation-address-message</value> </array> </property> <property name="defaultEncoding" value="utf-8"/> </bean>
-
在实体类中,将原有硬编码的校验信息改为占位符形式
{校验参数key}
(注意这里不需要$)public class Student implements Serializable { @NotNull(message = "{student.studentId.notnull}",groups = {UpdateGroup.class}) private Integer studentId; // @NotNull(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class}) @NotBlank(message = "{student.studentName.notblank}",groups = {SaveGroup.class,UpdateGroup.class}) private String studentName; @NotNull(message = "{student.age.notnull}",groups = {SaveGroup.class,UpdateGroup.class}) @Range(min = 18,max = 100,message = "{student.age.range}",groups = {SaveGroup.class,UpdateGroup.class}) private Integer age; @NotNull(message = "{student.birthday.notnull}",groups = {SaveGroup.class,UpdateGroup.class}) @Past(message = "{student.birthday.past}",groups = {SaveGroup.class,UpdateGroup.class}) private Date birthday; @NotNull(message = "{student.score.notnull}",groups = {SaveGroup.class,UpdateGroup.class}) // @Range(min=0,max = 100,message = "分数必须在0~100之间",groups = {SaveGroup.class,UpdateGroup.class}) @DecimalMin(value = "0.5",message = "{student.score.decimalmin}",groups = {SaveGroup.class,UpdateGroup.class}) @DecimalMax(value = "100.0",message = "{student.score.decimalmax}",groups = {SaveGroup.class,UpdateGroup.class}) private Double score; @NotNull(message = "{student.favorites.notnull}",groups = {SaveGroup.class,UpdateGroup.class}) @Size(min = 1,max=4,message = "{student.favorites.size}",groups = {SaveGroup.class,UpdateGroup.class}) private List<Integer> favorites ; @NotNull(message = "{student.addr.notnull}",groups = {SaveGroup.class,UpdateGroup.class}) @Valid//必须添加该注解,否则不会对addr的子属性进行校验 private Address addr; ... } public class Address implements Serializable { @NotBlank(message = "{address.city.notblank}",groups = {SaveGroup.class, UpdateGroup.class}) private String city; @NotBlank(message = "{address.street.notblank}",groups = {SaveGroup.class,UpdateGroup.class}) private String street; ... }
3.6 自定义验证注解【了解】
JSR303和Hibernate-Validator中的校验注解常规情况足够使用,但是特殊情况下需要自定义校验规则。
以下代码以校验手机号为例
-
编写注解类
package com.baizhi.annotation; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; /** * 验证手机号码的注解类 */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) //指定校验规则的类 @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { /** * 写法固定 */ String message() default "手机号码格式错误"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
-
编写自定义的规则校验类
package com.baizhi.annotation; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Pattern; public class PhoneValidator implements ConstraintValidator<Phone, String> { //验证手机的正则表达式 private String phoneReg = "^1(3[0-9]|5[0-3,5-9]|7[1-3,5-8]|8[0-9])\d{8}$"; private Pattern phonePattern = Pattern.compile(phoneReg); public void initialize(Phone mobile) { } public boolean isValid(String value, ConstraintValidatorContext arg1) { //返回匹配结果 验证手机号的格式是否正确 return phonePattern.matcher(value).matches(); } }
-
在实体类上使用自己的校验注解
public class Student implements Serializable { ... //使用自定义注解 @NotNull(message="手机号码不能为空",groups = {SaveGroup.class,UpdateGroup.class}) @Phone(groups = {SaveGroup.class,UpdateGroup.class}) private String phone; ... }
4 静态资源的处理
Servlet的url-pattern的4种路径配置方式:
- 精确配置: /开头后面跟路径
- 路径配置:/前缀/* 匹配路径以指定前缀开头的请求
- 后缀名配置:*.do 匹配路径以指定后缀结尾的请求
- 缺省配置:/ 匹配所有请求
当springmvc的请求分发器url-pattern配置为/,图片、js、css等资源的请求也会被springmvc拦截,此时需要对静态资源处理。
springmvc-servlet.xml
<!--
mapping: 匹配请求路径 **任意层级路径 *任意字符
location: 静态资源在项目中的起始寻找路径
注意:必须配置在mvc:annotation-driven前
-->
<mvc:resources mapping="/**/*.html" location="/" order="0"/>
<mvc:resources mapping="/**/*.js" location="/" order="0"/>
5 SpringMVC的执行流程
eturn phonePattern.matcher(value).matches();
}
}
3. 在实体类上使用自己的校验注解
```java
public class Student implements Serializable {
...
//使用自定义注解
@NotNull(message="手机号码不能为空",groups = {SaveGroup.class,UpdateGroup.class})
@Phone(groups = {SaveGroup.class,UpdateGroup.class})
private String phone;
...
}
4 静态资源的处理
Servlet的url-pattern的4种路径配置方式:
- 精确配置: /开头后面跟路径
- 路径配置:/前缀/* 匹配路径以指定前缀开头的请求
- 后缀名配置:*.do 匹配路径以指定后缀结尾的请求
- 缺省配置:/ 匹配所有请求
当springmvc的请求分发器url-pattern配置为/,图片、js、css等资源的请求也会被springmvc拦截,此时需要对静态资源处理。
springmvc-servlet.xml
<!--
mapping: 匹配请求路径 **任意层级路径 *任意字符
location: 静态资源在项目中的起始寻找路径
注意:必须配置在mvc:annotation-driven前
-->
<mvc:resources mapping="/**/*.html" location="/" order="0"/>
<mvc:resources mapping="/**/*.js" location="/" order="0"/>
5 SpringMVC的执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgWMzE79-1630975505214)(SpringMVC day03.assets/image-20200602223850332.png)]