1. 拦截器
拦截器主要用来拦截用户的请求,在指定方法前后,根据业务需要执行设定好的代码,也就是提前定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其执行,例如登录操作,只有登录成功之后用户才可以访问应用页面,这时就可以使用拦截器来拦截前端发来的请求,判断 session 中是否有登录用户的信息,如果没有就拦截,有的话就放行
1.1. 快速开始
首先需要定义拦截器,定义好之后注册并配置拦截器
自定义拦截器需要实现 HandlerInterceptor 接口,并重写它的方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion...");
}
}
注册配置拦截器需要实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)//注册拦截器
.addPathPatterns("/**")//对哪些路径生效
.excludePathPatterns("/user/login");//对哪些路径不生效
}
}
拦截路径 | 含义 | 举例 |
/* | 一级路径 | 能匹配 /user,/book,/login,不能匹配 /user/login |
/** | 任意级路径 | 能匹配 /user,/user/login,/user/reg |
/book/* | /book 下的一级路径 | 能匹配 /book/addBook,不能匹配 /book/addBook/1,/book |
/book/** | /book 下的任意级路径 | 能匹配 /book,/book/addBook,/book/addBook/2,不能匹配 /user/login |
2. 统一数据返回格式
以之前的图书管理系统为例,之前是手动封装了一层返回结果的
对于多个接口,如果都进行封装的话,肯定是非常麻烦的,所以就可以对返回格式进行统一
首先定义一个类,实现ResponseBodyAdvice
接口并重写其中的方法,然后还要加上@ControllerAdvice
注解来交给 Spring 管理
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;// false:不处理 true:处理
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
来介绍一下这两个方法:
supports:用于判断是否要对该方法的返回值进行处理,可以根据返回类型等条件来自定义,false:不处理 true:处理
beforeBodyWrite:在将返回值写入响应体之前调用,可以在这里对返回值进行统一的包装或者修改
上面的代码中表示所有方法都进行处理,处理的逻辑就是再封装一层之前定义的 Result 类成功的方法
响应的 body 中也封装好了
但是有一个问题,原来封装好的类型又封装了一层
就可以加个判断,如果已经是 Result 类型的就直接返回 body
除此之外,还有一个错误,当访问更新图书的接口之后报错了,而数据库中的信息还是成功修改了
具体的报错信息是类型匹配时的错误
在 Spring 中,返回值会经过 HttpMessageConverter
转换为 HTTP 响应的内容 ,字符串类型和非字符串类型的处理流程是不同的
对于 String
类型的返回值,Spring 使用 StringHttpMessageConverter
将其直接作为字符 串写入到 HTTP 响应中,而不会进一步封装或序列化。 非字符串类型的返回值会通过 MappingJackson2HttpMessageConverter
等转换器,序列化为 JSON 字符串。
此时 body 如果是字符串类型,StringHttpMessageConverter
会尝试直接将 Result.success(body)
转换为字符串,导致类型不匹配。
解决办法:
3. 统一异常处理
在之前写的代码中,每一个模块都有需要处理异常的地方,就可以把这些异常进行统一处理,统一异常处理是通过@ControllerAdvice
注解和@ExceptionHandler
注解来实现的
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e) {
log.error("发生异常:e" + e);
return Result.fail();
}
}
来造几个常见的异常进行演示:
还可以通过重载的方式,细分具体发生的是什么异常,通过修改 fail 方法也可以自定义返回的错误信息
不过,在测试时发现最终返回的状态码是 200,这就不太合理
可以通过@ResponseStatus
注解来设置返回的状态码,传入的参数必须是 HttpStatus 的常量