目录
拦截器
一、什么是拦截器
拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码
也就是说,允许开发人员提前预定于一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其执行
比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息,有的话正常响应,没有则进行拦截
二 拦截器的使用
拦截器的使用步骤分为两步:
- 定义拦截器
- 注册配置拦截器
1️⃣、首先自定义一个拦截器类(LoginInterceptor) 并实现HandlerInterceptor接⼝,并重写其所有⽅法 (这里我只重新了preHandle方法,根据自己的需求来决定的)
// 使用slf4j日志框架记录日志
// 将该类标记为Spring组件,使其被自动扫描并注册到Spring容器中
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
// 实现preHandle方法,该方法将在请求处理之前被调用
// 其主要作用是进行登录验证,确保请求是经过认证的
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取token
// 从HTTP请求的头部信息中获取token,这是进行登录验证的关键步骤
//2、校验token 判断是否放行
// 通过日志记录token信息,便于调试和排查问题
log.info("进入拦截器");
String token = request.getHeader(Constants.REQUEST_HEADER_TOKEN);
log.info("neader中获取token:{}", token);
// 这里使用JwtUtils工具类来解析token,以验证其有效性
Claims claims=JwtUtils.parseToken(token);
// 如果token解析失败,设置HTTP响应状态码为401,表示未授权,并阻止请求继续执行
if(claims == null){
response.setStatus(401);
return false;
}
// 如果token解析成功,表示验证通过,允许请求继续执行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor 目标方法执行后执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕后执⾏");
}
}
- preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true: 继续执⾏后续操作; 返回false: 中断后续操作.
- postHandle()⽅法:⽬标⽅法执⾏后执⾏
- afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图, 暂不了解)
2️⃣注册配置拦截器 : 实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法
//五大注解 其中的一个 将该类标记为Spring组件,使其被自动扫描并注册到Spring容器中
@Configuration
public class WebConfig implements WebMvcConfigurer {
//注入自定义拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addInterceptor是注册自定义拦截器
//addPathPatterns是要添加拦截路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**"); //表示拦截所以请求
}
}
启动服务, 试试访问任意请求, 观察后端⽇志
可以看到preHandle ⽅法执⾏之后就放⾏了, 开始执⾏⽬标⽅法, ⽬标⽅法执⾏完成之后执⾏postHandle和afterCompletion⽅法
我们把拦截器中preHandle⽅法的返回值改为false, 再观察运⾏结果
可以看到, 拦截器拦截了请求, 没有进⾏响应.
三 拦截路径配置
拦截路径是指我们定义的拦截器 对那些请求生效
- 通过 addPathPatterns() ⽅法指定要拦截哪些请求.
- 通过 excludePathPatterns() 指定不拦截哪些请求
/** * Web配置类,实现WebMvcConfigurer接口以自定义Spring MVC的配置 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; /** * 需要排除拦截的路径列表,这些路径主要用于静态资源和编辑器的访问 */ private final List excludes = Arrays.asList( "/**/*.html", "/blog-editormd/**", "/css/**", "/js/**", "/pic/**", "/user/login" ); /** * 添加拦截器配置 * * @param registry InterceptorRegistry对象,用于注册自定义拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns(excludes);//排除前端静态资源 } }
以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件, JS 和 CSS 等⽂件)
四 拦截器的执行流程
正常调用流程 如下图:
配置拦截器后的调用流程 如下图:
- 添加拦截器后, 执⾏Controller的⽅法之前, 请求会先被拦截器拦截住. 执⾏ preHandle() ⽅法, 这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的 ⽅法. 如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
- controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个方法afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据
统一数据返回格式
统一的数据返回格式使用
@ControllerAdvice
注解和ResponseBodyAdvice
的方式实现。ControllerAdvice
表示控制器通知类。
/**
* 全局响应建议类,用于统一处理控制器的响应体
* 实现了ResponseBodyAdvice接口以定制响应体的处理
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果已经是Result类型,则不作处理,直接返回
if(body instanceof Result){
return body;
}
// 如果是字符串类型,序列化封装后的成功结果为JSON字符串
if(body instanceof String){
objectMapper.writeValueAsString(Result.success(body));
}
// 对于其他类型,直接封装为成功结果并返回
return Result.success(body);
}
}
继承 ResponseBodyAdvice 接口后,需要实现该接口下的 supports 方法和 beforeBodyWrite 方法,supports 方法只需要更改返回值为 true 就可以了,表示是否要执行 beforeBodyWrite 方法,返回 true 表示执行,false 表示不执行,beforeBodyWrite 方法中的 body 参数就是我们原方法的返回值。
注意:@SneakyThrows 注解 主要目的是解决 Java 的异常处理问题。当我们在代码中抛出一个异常时,如果这个异常被包裹在一个方法中,并且这个方法没有 throws 关键字来声明会抛出这个异常,那么编译器会报错。通过使用 @SneakyThrows,你可以告诉编译器:“我知道这个方法可能会抛出异常,但我保证在 catch 块中处理它。” 这样编译器就不会报错了
定义一个统一的返回类型 :
@Data
public class Result<T> {
private ResultStatus code;
//错误信息
private String errMsg;
//接口响应的数据
private T data;
public static <T> Result<T> success(T data){
Result result = new Result();
result.setCode(ResultStatus.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
public static <T> Result<T> fail(String errMsg){
Result result = new Result();
result.setCode(ResultStatus.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
public static <T> Result<T> fail(String errMsg, T data){
Result result = new Result();
result.setCode(ResultStatus.FAIL);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
}
统一数据返回格式的优点:
- 便前端程序员更好的接收和解析后端数据接口返回的数据
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的
- 有利于项目统数据的维护和修改
- 有利于后端技术部的统规范的标准制定,不会出现稀奇古怪的返回内容
统一异常处理
@Slf4j
@ResponseBody //因为返回的数据都不是视图类型,所以加上这个注解防止出现问题
@RestControllerAdvice
public class ExceptionHandle extends RuntimeException{
@ExceptionHandler
public Result Handle(NullPointerException e){
log.error("空指针异常 ",e);
return Result.fail("内部错误,请联系管理员");
}
@ExceptionHandler
public Result Handle(Exception e){
log.error("发生异常 ",e);
return Result.fail("内部错误,请联系管理员");
}
@ExceptionHandler
public Result Handle(NoSuchFieldException e){
log.error("文件不存在:{}",e.getMessage());
return Result.fail("内部错误,请联系管理员");
}
@ExceptionHandler
public Result Handle(RuntimeException e){
log.error("运行时错误 ",e.getMessage());
return Result.fail("内部错误,请联系管理员");
}
}
类名, ⽅法名和返回值可以⾃定义, 重要的是注解接⼝返回为数据时, 需要加 @ResponseBody 注解
例子 如图: