统一功能处理

目录

一、拦截器

自定义拦截器:

注册配置拦截器:

拦截器执行流程

二、统一数据返回格式

三、统一异常处理


一、拦截器

拦截器是spring框架提供的核心功能之一,它可以拦截用户请求,在指定方法前后执行预先设定的代码。

比如我们可以通过拦截器拦截前端发来的请求,判断session中是否有用户的登录信息,如果用户登录则放行,如果没有就进行拦截

拦截器的使用分为两步:

1、定义拦截器

2、注册配置拦截器

自定义拦截器:

需要实现HandlerInterceptor接口,并重新其方法。

java 复制代码
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
        log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
        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就进行拦截。

我们可以在preHandle方法中进行用户登录的判断,用户没有登录就进行拦截,不执行目标方法,

比如:

java 复制代码
 @Override
public boolean  preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //System.out.println("目标方法执行前......");
        log.info("登录拦截器进行拦截校验...");
        //判断用户是否登录
        HttpSession session = request.getSession(false);//false表示request中没有session不会创建一个session
        if(session == null){
            response.setStatus(401);//http状态码,不是业务码
            response.setContentType("text/html;charset=utf-8");//设置编码
            response.getOutputStream().write("用户未登录".getBytes(StandardCharsets.UTF_8));

            return false;
        }
        //true 放行,false 拦截
        return true;
    }

注册配置拦截器:

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

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    //⾃定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册⾃定义拦截器对象
        registry.addInterceptor(loginInterceptor)
        .addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所
有请求)
        .excludePathPatterns("/user/login")//表示排除这个路径,允许放行
        .excludePathPatterns("/**/*.js") //排除前端静态资源
        .excludePathPatterns("/**/*.css")
        .excludePathPatterns("/**/*.png")
        .excludePathPatterns("/**/*.html");
    }
}

|------------|-------------|-------------------------------------------|
| 路径 | 含义 | 举例 |
| /* | 一级路径 | 能匹配/book, /user, 不能匹配/book/list |
| /** | 任意级路径 | 匹配/book, /book/list, /user/login, /user |
| /book/* | book下的一级路径 | 匹配/book/add, /book/list, 不能匹配/book/add/1 |
| /book/** | book下的任意级路径 | 匹配/book/add, /book/add/1, 不能匹配/user/login |
[拦截器路径设置]

拦截

拦截器执行流程

用户发送请求,在执行controller中的目标方法之前先调用拦截器中的preHandle方法,

preHandle方法返回true就放行 执行目标方法,preHandle方法返回false就拦截 不执行目标方法。

然后执行拦截器中的postHandle、afterCompletion方法。

二、统一数据返回格式

java 复制代码
import com.example.demo.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest
request, ServerHttpResponse response) {
        return xxx;//根据需求统一返回数据
    }
}

@ControllerAdvice是控制器通知类,有这个注解的类对象也是交由spring loc容器进行管理,它是派生于@Component注解的

通知类需要实现ResponseBodyAdvice接口,重写它的supports和beforeBodyWrite方法,并添加@ControllerAdvice注解。

supports方法返回true表示执行beforeBodyWrite方法,返回false表示不执行。

响应正文写入之前执行beforeBodyWrite方法,可以在这做统一处理,统一返回数据格式。beforeBodyWrite方法的参数body是指目标方法的返回值,returnType是目标方法的返回类型。

因此可以通过supports方法来确定哪些方法的返回值需要在beforeBodyWrite中进行统一处理,哪些不需要处理。

比如:

java 复制代码
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //是否要执行下面的方法(beforeBodyWrite),可以做不同处理
        //比如:
        if(returnType.getDeclaringClass().equals(UserController.class)){
            return false;
        }
        //false:不执行下面的方法
        //true:执行下面的方法
        return true;
    }

这个代码就表示如果目标方法的返回值类型是UserController类就不执行beforeBodyWrite方法,其他的返回值类型都执行beforeBodyWrite方法。

java 复制代码
 @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof Result<?>){
            return body;
        }
        //返回数据body类型是String需要处理
        if(body instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        return Result.success(body);
    }

这个重写的beforeBodyWrite方法中统一返回的类型都是Result,第一个if语句表示如果目标方法返回值类型就是Result,就直接return。

后面的就是对返回值进行统一处理:

当控制器方法返回String类型时 ,Spring 默认会将其视为视图名称或直接文本响应,而不是 JSON 格式。如果不手动转换为 JSON,客户端可能会收到纯文本而非结构化的 JSON 数据。

(简言之,beforeBodyWrite方法的参数body如果是String类型,那么必须手动将要return的内容转换成json格式)

下面这个代码就是我们定义的统一数据返回的格式:

java 复制代码
package com.bit.book.model;

import com.bit.book.enums.CodeStatus;
import lombok.Data;

@Data
public class Result<T> {
    //业务状态码
    //自己定义
    //200正常  -1 未登录  -2 异常 ...
    private CodeStatus code;

    private String errorMsg;
    private T data;

    public static <T> Result<T> success(T data){
        Result result = new Result();
        result.setCode(CodeStatus.SUCCESS);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> fail(String errMsg){
        Result result = new Result();
        result.setCode(CodeStatus.ERROR);
        result.setErrorMsg(errMsg);
        return result;
    }

    public static Result unLogin(){
        Result result = new Result();
        result.setCode(CodeStatus.UNLOGIN);
        result.setErrorMsg("用户未登录");
        return result;
    }

    public static <T> Result<T> unLogin(T data){
        Result result = new Result();
        result.setCode(CodeStatus.UNLOGIN);
        result.setErrorMsg("用户未登录");
        result.setData(data);
        return result;
    }
}

统一数据返回格式有利于前端更好地接收、分析数据,有利于项目统一数据的维护、修改。

三、统一异常处理

统一异常处理是使用@ControllerAdvice和@ExceptionHandler来实现的。

@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,

这两个注解结合就表示当出现异常的时候执行某个方法。

类名、方法名、返回值可以自定义。

比如:

java 复制代码
import com.bit.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@ControllerAdvice
@ResponseBody//这个注释一定要加,表示返回的是数据,不加返回的就是页面 会出错
public class ResponseExceptionHandle {
    @ExceptionHandler
    public Result exceptionHandler(Exception e){
        log.error("发生异常,e:",e);
        return Result.fail("内部错误,e: " + e);
    }
    @ExceptionHandler
    public Result exceptionHandler(NullPointerException e){
        log.error("空指针异常, e:",e);
        return Result.fail("空指针异常,e:"+ e);
    }
    @ExceptionHandler
    public Result exceptionHandler(ArithmeticException e){
        log.error("算术运算异常,e:",e);
        return Result.fail("算术运算异常,e:"+ e);
    }
    @ExceptionHandler
    public Result exceptionHandler(IndexOutOfBoundsException e){
        log.error("数组越界异常,e:",e);
        return Result.fail("数组越界异常,e:"+ e);
    }
}

还有另一种写法:

java 复制代码
package com.bit.book.advice;

import com.bit.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@ControllerAdvice
@ResponseBody//这个注释一定要加,表示返回的是数据,不加返回的就是页面 会出错
public class ResponseExceptionHandle {
    //第二种
    @ExceptionHandler(Exception.class)
    public Result exceptionHandler1(Exception e){
        log.error("发生异常,e:",e);
        return Result.fail("内部错误,e: " + e);
    }
    @ExceptionHandler(NullPointerException.class)
    public Result exceptionHandler2(Exception e){
        log.error("空指针异常, e:",e);
        return Result.fail("空指针异常,e:"+ e);
    }
    @ExceptionHandler(ArithmeticException.class)
    public Result exceptionHandler3(Exception e){
        log.error("算术运算异常,e:",e);
        return Result.fail("算术运算异常,e:"+ e);
    }
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public Result exceptionHandler4(Exception e){
        log.error("数组越界异常,e:",e);
        return Result.fail("数组越界异常,e:"+ e);
    }
}

当出现对应的异常时,前端就会收到我们处理的异常数据,这里的Result.fail()就是前面Result类中的静态方法

复制代码
public static <T> Result<T> fail(String errMsg){
    Result result = new Result();
    result.setCode(CodeStatus.ERROR);
    result.setErrorMsg(errMsg);
    return result;
}

比如我们写一个TestController,来测试一下算术运算异常:

java 复制代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public int t1(){
        return 10/0;
    }
}

运行程序,在浏览器上访问接口(如果配置了拦截器,记得放行这个路径):

复制代码
{
  "code": "ERROR",
  "errorMsg": "算术运算异常,e:java.lang.ArithmeticException: / by zero",
  "data": null
}
相关推荐
咩咩啃树皮8 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚8 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白9 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司10 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地11 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415911 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风14 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠14 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898614 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112315 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python