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标注值必须是格式正确的 Email 地址
@Length标注值字符串大小必须在指定的范围内
@NotEmpty标注值字符串不能是空字符串
@Range标注值必须在指定的范围内

三种非空的区别

@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同

  1. @NotNull (包装类型不为null)

    @NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解

  2. @NotEmpty (集合类型长度大于0)

    @NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败

  3. @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";
}