Spring统一功能处理

一. 拦截器

我们在学习写强制登录时,后端程序根据需要根据 Session 来判断用户是否登录, 但是实现方法是比较麻烦的---需要修改每个接⼝的处理逻辑 ;需要修改每个接⼝的返回结果 ;接口定义修改,前端代码也需要跟着修改

有没有更简单的办法, 统⼀拦截所有的请求, 并进行 Session 校验呢, 这立我们学习⼀种新的解决办法: 拦截器

1.1 什么是拦截器

拦截器是在请求 / 数据抵达目标处理逻辑前、后自动拦截并执行通用处理(如登录校验、日志记录)的非侵入式代码组件,像业务流程中的统一关卡。

拦截器是Spring框架提供的核心功能之⼀,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码

无拦截器:请求---> controller---> service ......

请求 → 拦截器前置 → Controller → Service → 拦截器后置 → 拦截器完成 → 响应

总结:拦截器:拦截用户请求,在目标方法前后,执行的一段代码(由程序员自定义)

1.2 拦截器的使用

拦截器的使用步骤分为两步:

  1. 定义拦截器
  2. 注册配置拦截器

1.2.3 自定义拦截器

实现HandlerInterceptor接口,并重写其所有方法

java 复制代码
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override//前置处理(目标方法执行前执行)
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
        return true;//返回 true:放行(执行后续程序)
                    //返回 false 拦截(直接终止整个请求流程)

    }

    @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 视图渲染完毕后执⾏,最后执⾏");

    }
}

HandlerInterceptor接口 中的三个方法:

方法名 执行时机 返回值 / 核心行为
preHandle 目标方法(Controller 接口)执行 ✅ 返回true:继续执行后续操作(Controller 等);✅ 返回false:中断后续所有操作
postHandle Controller 方法执行 、响应返回 ❌ 无返回值;仅执行方法内逻辑,不影响流程走向
afterCompletion 响应已返回给客户端(无论是否异常) ❌ 无返回值;整个请求流程的最后一步,仅执行方法内逻辑

1.2.3 注册配置拦截器

实现WebMvcConfigurer接口,并重写addInterceptors方法

java 复制代码
@Configuration
public class webConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;//注入⾃定义的拦截器对象
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//注册⾃定义拦截器对象
                .addPathPatterns("/**")//要拦截的路径
                .excludePathPatterns("/user/login","/html/**","/css/**","/js/**");//排除拦截的路径
    }
}

1.3 拦截器详细

1.3.1 拦截路径

拦截路径 是指我们定义的这个拦截器 , 对哪些请求生效 . 我们在注册配置拦截器的时候,通过addPathPatterns() 方法指定要拦截哪些请求. 也可以通过 excludePathPatterns() 指定不拦截哪些请求. 上述代码中,我们配置的是 /** ,表式拦截所有的请求.

在拦截器中除了可以设置 /** 拦截所有资源外,还有⼀些常见拦截路径设置:

拦截路径 含义 匹配示例(能匹配) 不匹配示例
/* 一级路径 /user/book/login /user/login/book/1
/** 任意级路径 /user/user/login/user/reg 无(所有路径都能匹配)
/book/* /book 下的一级路径 /book/addBook/book/delete /book/book/addBook/1
/book/** /book 下的任意级路径 /book/book/addBook/book/addBook/2 /user/login/order/1

以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图片文件, JS 和 CSS 等文件).

1.3.2 拦截器执行流程

正常的调用顺序:

有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图

  1. 添加拦截器后, 执行Controller的方法之前,请求会先被拦截器拦截住.执行 preHandle() 方法, 这个方法需要返回⼀个布尔类型的值. 如果返回true, 就表示放行本次操作,继续访问controller中的方法. 如果返回false,则不会放行(controller中的方法也不会执行).

  2. controller当中的方法执行完毕后,再回过来执行 postHandle() 这个方法以及 afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据.

1.4 登录校验

1.4.1 自定义拦截器

java 复制代码
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override//前置处理(目标方法执行前执行)
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       HttpSession session =request.getSession();
       UserInfo userInfo=(UserInfo) session.getAttribute("SESSION_USER_KEY");
        if(userInfo!=null){
            log.info("登录成功");
            return true;
        }
        response.setStatus(401);
       return false;

    }

}

1.4.2 注册配置拦截器

java 复制代码
@Configuration
public class webConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;//注入⾃定义的拦截器对象
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//注册⾃定义拦截器对象
                .addPathPatterns("/**")//要拦截的路径
                .excludePathPatterns("/user/login","/html/**","/css/**","/js/**");//排除拦截的路径
    }
}

二. 统⼀数据返回格式

强制登录案例中,我们共做了两部分工作:

  1. 通过Session来判断用户是否登录

  2. 对后端返回数据进行封装,告知前端处理的结果

回顾

后端统⼀返回结果

java 复制代码
@Data 
public class Result { 
private int status; 
private String errorMessage;
 private T data; 
}

后端逻辑处理

java 复制代码
@RequestMapping("/getListByPage") 
public Result getListByPage(PageRequest pageRequest) {

 log.info("获取图书列表, pageRequest:{}", pageRequest); 

//⽤⼾登录, 返回图书列表
 PageResult pageResult = bookService.getBookListByPage(pageRequest);

 log.info("获取图书列表222, pageRequest:{}", pageResult); 

return Result.success(pageResult); 

} 

拦截器帮我们实现了第一个功能,接下来看SpringBoot对第二个功能如何支持

统⼀的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现

2.1 快速上手

添加 @ControllerAdvice 注解,并实现 ResponseBodyAdvice

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;//返回 true :处理返回数据格式
                    //返回 false:不处理返回数据格式

    }
    @SneakyThrows//该注解会给这整个方法都加上 try catch
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //body 为目标方法返回的结果
        if (body instanceof Result<?>){
            return body;
        }
        //统一结果返回, 需要对String类型额外处理,使⽤SpringBoot内置提供的Jackson来实现信息的序列化 
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);//将数据统一封装成result类型
    }
}

注意:

  • 统⼀数据返回格式时,当返回数据类型为 String 类型时 需要额外处理
  • supports方法: 判断是否要执行 beforeBodyWrite方法. true为执行, false不执行 . 通过该方法可以 选择哪些类或哪些方法的response要进行处理, 其他的不进行处理.

三. 统⼀异常处理

统⼀异常处理使的是@ControllerAdvice +@ExceptionHandler 来实现的, @ControllerAdvice 表式控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件

java 复制代码
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
    @ExceptionHandler
    public Object hand(Exception e){

        return Result.fail(e.getMessage());

    }

}

以上代码表式,如果代码出现Exception异常(包括Exception的子类), 返回⼀个 Result 的对象, Result 对象的设置参考 Result.fail(e.getMessage())

java 复制代码
public static Result fail(String msg) {
 Result result = new Result();
 result.setStatus(ResultStatus.FAIL);
 result.setErrorMessage(msg);
 result.setData("");
 return result;
}

我们可以针对不同的异常,返回不同的结果.

java 复制代码
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
    @ExceptionHandler
    public Object hand(Exception e){

        return Result.fail(e.getMessage());

    }
    @ExceptionHandler
    public Object handler(NullPointerException e) {
        return Result.fail("发⽣NullPointerException:"+e.getMessage());
    }
    
    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.fail("发⽣ArithmeticException:"+e.getMessage());
    }
    
}

注意:

  • 当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配
  • 同一异常处理和统一数据格式处理都会执行,但有明确的执行顺序

🚦 执行顺序(关键)

  1. 请求进入 Controller 方法
  • 正常业务:方法执行完毕,返回数据。

  • 业务异常:方法抛出异常,中断执行。

  1. 异常处理阶段( @RestControllerAdvice + @ExceptionHandler )
  • 如果 Controller 抛出异常,Spring 会先被 统一异常处理器 捕获。

  • 异常处理器会将异常信息封装为统一的 Result 对象(例如 Result.error(500, "系统错误") )并返回。

  • 如果没有异常,直接进入下一步。

  1. 数据封装阶段( ResponseBodyAdvice )
  • 无论是 Controller 正常返回的数据,还是异常处理器返回的 Result 对象,都会进入 beforeBodyWrite 方法。

  • 你的代码会判断:

  • 如果已经是 Result 类型 → 直接返回(避免重复封装)。

  • 如果是其他类型 → 包装成 Result.success(...) 。

  • 如果在controller层已经把异常处理了,统⼀异常处理就不会执行
相关推荐
Gopher_HBo2 小时前
ThreadLocal原理(二)
后端
学不完的2 小时前
ZrLog 博客系统部署指南(无 War 包版,Maven 构建 + 阿里云镜像优化)
java·linux·nginx·阿里云·maven
小杍随笔2 小时前
【Rust 语言编程知识与应用:元编程详解】
开发语言·后端·rust
神奇小汤圆2 小时前
B+ 树的物理代价:当SQL慢了10毫秒,计算机底层发生了什么?
后端
小江的记录本2 小时前
【Java】Java核心关键字:final、static、volatile、synchronized、transient(附《面试高频考点》)
java·开发语言·spring boot·后端·sql·spring·面试
神奇小汤圆2 小时前
滴滴一面:在项目中使用多线程时遇到过哪些问题?
后端
羊小猪~~2 小时前
【QT】--QWIdget与QDialog
开发语言·数据库·c++·后端·qt·求职招聘
oyzz1202 小时前
SpringBoot最佳实践之 - 使用AOP记录操作日志
java·spring boot·后端
yang_B6212 小时前
C# ISerializable 允许对象控制自己的序列化/反序列化过程
java·开发语言·c#