SpringMVC-高级扩展
SpringMVC-全局异常处理
在传统的JavaWeb中,我们使用编程式异常处理,使用try-catch关键字来捕获 ->处理异常
在SpringMVC中,提供了声明式异常处理的方式
@RestConrollerAdvice
@ControllerAdvice是发生异常后调用此类的handler方法,但可以正常返回逻辑视图、转发、重定向操作
@RestConrollerAdvice = @ControllerAdvice + @ResponseBody
使用@RestConrollerAdvice标识全局异常处理器,直接返回JSON字符串(用于前后端分离项目)
这种异常处理的方式也就是AOP面向切面编程的思维
@RestControllerAdvice
public class GlobalExceptionHandler {
}
@ExceptionHandler
此方法在异常处理器的方法上,修饰异常处理的handler
通过.class的类名来捕获指定的异常,由异常处理器接管
注:全局异常处理类也需要被加入IoC容器中,所以在配置类中要将此包扫描
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 当发生ArithmeticException(算术异常)时,被此handler接管
* @return
*/
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionHandler(ArithmeticException e){
return null;
}
/**
* 当发生异常时,被此handler接管
* @param e Exception的异常信息
* @return
*/
@ExceptionHandler(Exception.class)
public Object ExceptionHandler(Exception e){
return null;
}
}
SpringMVC-拦截器
在使用SpringMVC后,我们仍旧可以使用Filter(过滤器)技术
但Filter是位于整个JavaWeb项目之外的,无论什么请求都会被拦截一次
为此,SpringMVC提供了拦截器,相比于Filter对请求做更为细化的拦截
但拦截器无法拦截SpringMVC接管之外的请求
总结:如果拦截器能够实现,则不使用Filter
HandlerInterceptor接口
intercepter中文释义:拦截器
新建一个拦截器,并实现HandlerInterceptor接口,重写以下三个方法:
- preHandle:在handler执行之前进行拦截,最终以拦截器方法的返回值决定是否放行(true放行)
- 编码格式设置
- 登陆保护
- 权限处理
- postHandle:在handler执行完毕后触发的方法(没有拦截功能)
- 敏感词汇检查
- 对结果进行处理
- afterCompletion:整体处理完毕后,调用此方法(没有拦截功能)
- 多一个ex参数:为报错后的异常对象
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置拦截器
将Mvc配置类实现WebMvcConfigurer接口后,重写addInterceptors方法
通过registry调用addInterceptor方法,将MyInterceptor的对象作为参数
即可拦截所有请求,都走MyInterceptor拦截器中的内容
@EnableWebMvc
@Configuration
@ComponentScan({"com.xiaobai.controller","com.xiaobai.Error"})
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
指定地址拦截
使用registry.addInterceptor(new MyInterceptor())的链式调用,可以指定拦截的路径
@EnableWebMvc
@Configuration
@ComponentScan({"com.xiaobai.controller","com.xiaobai.Error"})
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截访问URI为/user/data的请求
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/data");
}
}
排除地址拦截
@EnableWebMvc
@Configuration
@ComponentScan({"com.xiaobai.controller","com.xiaobai.Error"})
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 排除访问URI为/user/data请求
registry.addInterceptor(new MyInterceptor()).excludePathPatterns("/user/data1");
}
}
多个拦截器
其实学到现在,我们可以发现,拦截器就是针对于Handler的增强通知类
针对于handler的执行前后做响应的处理
所以,当存在多个拦截器的时候,执行顺序也是同心圆原则,也就是Filter的执行顺序规则
当同时存在MyInterceptor1和MyInterceptor2时:
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
registry.addInterceptor(new MyInterceptor1());
}
执行顺序为:preHandle -> preHandle1 -> handler -> postHandle1 -> postHandle -> afterCompletion1 -> afterCompletion
SpringMVC-参数校验
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中
通过在 Bean(实体类)属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
JSR 303 只是一套标准,需要提供其实现才可以使用
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
注解 | 规则 |
---|---|
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
三种非空的区别
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同
-
@NotNull (包装类型不为null)
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解
-
@NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败
-
@NotBlank (字符串,不为null,切不为" "字符串)
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验
总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验
依赖导入
这一套参数校验的注解由Java提供,由hibernate来实现,SpringMVC只是做了注解的支持
所以,我们仍需要导入hibernate注解实现的依赖
- hibernate-validator
- hibernate-validator-annotation-processor
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate.version}</version>
</dependency>
应用步骤
实体类
在想要做参数校验的实体类属性上加上相应的注解
public class User {
@NotBlank
String name;
@Length(min = 6, max = 20)
String password;
@Min(1)
private int age;
@Email
private String email;
@Past
private Date birthday;
}
@Validated
在controller中使用@Validated注解修饰实体类接收的形参,这样才能让实体类属性上的注解生效
@RestController
@RequestMapping("user")
public class UserController {
@PostMapping("register")
public String register(@Validated @RequestBody User user){
return "user";
}
}
结果响应
使用参数校验,如果校验失败会直接返回异常信息,这是不对的
我们应该返回自定义的响应错误信息,而不是直接将异常暴露给前端
BindingResult
在校验对象的形参后,我们加上一个形参——BindingResult(要求这个形参紧挨着校验对象)
使用bindingResult的hasErrors方法判断错误是否存在错误,自定义返回信息
@PostMapping("register")
public String register(@Validated @RequestBody User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "error"; // 自定义返回信息
}
return "user";
}