SpringBoot统一功能处理

1、拦截器

在完成强制登录这一功能时,后端程序需要根据Session来判断用户是否登录,但是实现方法是很麻烦的:1、需要修改每个接口的处理逻辑 2、需要修改每个接口的返回结果。 3、接口定义修改,前端代码也需要跟着修改。

有没有一种简单的方法能统一拦截所有的请求,并进行Session校验呢?

这就需要我们的拦截器出马了~~

快速上手拦截器

什么是拦截器

拦截器是Spring提供的核心功能之一,主要用来拦截用户请求,在指定方法前后,根据业务需要执行预先设定的代码。也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其执行。

在拦截器中,开发人员可以在应用程序中做一些通用性的操作,比如拦截前端发来的请求,判断Session中是否有登录用户的信息,如果有就放行,没有就进行拦截。

拦截器的使用主要分为两步:

1、定义拦截器

2、注册配置拦截器

自定义拦截器

自定义拦截器需要实现HandlerInterceptor接口,并重写方法。

java 复制代码
@Slf4j
@Component
public class StringInterceptor implements HandlerInterceptor{
    //请求执行前的逻辑
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("目标方法执行前执行");
            return true;
    }
    //请求执行后的逻辑
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("目标方法执行后执行");
    }
    //视图渲染后的逻辑
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log.info("视图渲染后执行");
    }
}
  • preHandle():目标方法执行前执行,返回true:继续执行后续操作;返回false:中断后续操作。
  • postHandle():目标方法执行后执行
  • afterCompletion():视图渲染完成后执行,最后执行(后端开发现在几乎不涉及视图了,暂不了解)
注册配置拦截器

注册配置拦截器需要实现WebMvcConfigurer接口并重写addInterceptors方法。

java 复制代码
@Configuration
public class Webconfig implements WebMvcConfigurer {
    //自定义拦截器对象
    @Autowired
    private StringInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        // addPathPatterns():设置拦截的请求路径,/**表示所有路径    excludePathPatterns()设置不拦截的路径
        registry.addInterceptor(interceptor).
                addPathPatterns("/**").
                excludePathPatterns("/test/t2");
    }
}

Controller测试类:

java 复制代码
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public void t1(){
      log.info("t1执行中");
    }

    @RequestMapping("/t2")
    public void t2(){
        log.info("t2执行中");
    }

    @RequestMapping("/t3")
    public void t3(){
        log.info("t3执行中");
    }
}

测试结果:

t1: t2:t3:

可以看到拦截器没有拦截t2请求,并执行拦截器中重写的方法。

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

拦截器执行流程

正常调用顺序:

有了拦截器后:

1、添加拦截器后,执行Controller方法之前,请求会先被拦截住并执行preHandle()方法, 这个方法需要返回一个boolean类型的值。如果返回true,则表示放行,继续访问Controller中的方法。如果返回false,则不会放行,不会执行Controller中的方法。

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

2、统一返回类型

在开发环境中,我们的方法往往有许多不同的返回类型。如果返回的类型不同,前端就要使用各种不同的类型进行接收,这样会增加前端开发人员的工作量。SpringBoot为我们提供了统一返回类型的功能实现。

快速入门

统一数据返回类型需要使用@ControllerAdvice(标识控制器通知类,这里的通知并不是我们日常生活中的通知,而是某些功能的实现)并实现ResponseBodyAdvice接口。

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper mapper;
    //什么请求需要处理      true------所有请求都处理       false------所有请求都不处理
    @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) {
       if(body instanceof Result){
           return body;
       }
       if(body instanceof String){
           //如果是字符串,转成json字符串再返回,否则可能会产生类型转换异常
            return mapper.writeValueAsString(Result.success(body));
       }
        return Result.success(body);
    }
}

supports方法:判断是否要执行beforeBodyWrite方法。true为执行,flase不执行。提供该方法可以选择那些类或那些方法的response要进行处理,其他的不进行处理。

从returnType获取类名和方法名:

java 复制代码
 //获取执行的类
        Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
        //获取执行的方法
        Method method = returnType.getMethod();

beforeBodyWritr方法:对response方法进行具体操作处理

测试相关类及方法:

Controller:

java 复制代码
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    private BookSeverice bookSeverice;

    //根据id查询图书
    @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId) {
        log.info("根据id查询图书信息,id:" + bookId);
        return bookSeverice.queryBookById(bookId);
    }
}

Service:

java 复制代码
@Service
public class BookSeverice {
    @Autowired
    private BookMapper mapper;


    //根据id查询图书
    public BookInfo queryBookById(Integer bookId) {
       BookInfo bookInfo =  mapper.queryById(bookId);
       bookInfo.setStatusCN(BookStatus.getDescByCode(bookInfo.getStatus()).getDesc());
       return bookInfo;
    }
}

mapper:

java 复制代码
    @Select("select * from book_info where id = #{id}")
    BookInfo queryById(Integer id);

Result:

java 复制代码
@Data
public class Result<T> {
    private ResultStatus code;//业务码     不是HTTP状态码   200------成功   -2 失败    -1未登录
    private String errMsg;//错误信息        如果业务成功,errMsg为空
    private T data;

    public static<T> Result success(T data){
        Result result = new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setData(data);
        return result;
    }
    public static <T> Result noLogin(){
            Result result = new Result<>();
            result.setCode(ResultStatus.NOLOGIN);
            result.setErrMsg("用户未登录");
            return result;
        }

    public  static Result fail(String msg){
        Result result = new Result<>();
        result.setCode(ResultStatus.FAIL);
        result.setErrMsg(msg);
        return result;
    }
    public  static Result fail(String msg,ResultStatus resultStatus){
        Result result = new Result<>();
        result.setCode(resultStatus);
        result.setErrMsg(msg);
        return result;
    }

  }

业务码枚举类:

java 复制代码
public enum ResultStatus {
    SUCCESS(200),
    FAIL(-1),
    NOLOGIN(-2);

    ResultStatus(int code) {
        this.code = code;
    }

    private int code;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

Bookinfo:

java 复制代码
@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;//1-正常   2-不可借阅
    private String  statusCN ;
    private Date createTime;
    private Date updateTime;
}

添加统一数据返回格式之前:

添加统一数据返回格式之后:

优点:

  1. 方便前端程序员更好地接收和解析后端数据接口返回地数据
  2. 降低前端程序员和后端程序员地沟通成本,按照某个格式实现就可以了,因为所有地接口都是这样返回地。
  3. 有利于项目统一数据的维护和修改
  4. 有利于后端技术部门的统一规范的标准指定,不会出现稀奇古怪的返回内容。

3、统一异常处理

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

代码如下:

java 复制代码
@ResponseBody
@ControllerAdvice
@Slf4j
public class ExceptionAdvice {
    /**
     * 统一异常处理
     * @param e
     * @return
     */
    @ExceptionHandler
    public Result handleException(Exception e){
        //建议打印日志
        log.error("e: "+e);
        return Result.fail("发生内部错误");
    }
}

因为接口返回的是数据,所以需要加上@ResponseBody注解。

以上代码表示,如果代码出现Exception异常(包括它的子类),就返回一个Result对象,Result类代码:

java 复制代码
@Data
public class Result<T> {
    private ResultStatus code;//业务码     不是HTTP状态码   200------成功   -2 失败    -1未登录
    private String errMsg;//错误信息        如果业务成功,errMsg为空
    private T data;

    public static<T> Result success(T data){
        Result result = new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setData(data);
        return result;
    }
    public static <T> Result noLogin(){
            Result result = new Result<>();
            result.setCode(ResultStatus.NOLOGIN);
            result.setErrMsg("用户未登录");
            return result;
        }

    public  static Result fail(String msg){
        Result result = new Result<>();
        result.setCode(ResultStatus.FAIL);
        result.setErrMsg(msg);
        return result;
    }
    public  static Result fail(String msg,ResultStatus resultStatus){
        Result result = new Result<>();
        result.setCode(resultStatus);
        result.setErrMsg(msg);
        return result;
    }

  }

我们还可以针对不同的异常返回不同的结果:

java 复制代码
@ResponseBody
@ControllerAdvice
@Slf4j
public class ExceptionAdvice {
    /**
     * 统一异常处理
     * @param e
     * @return
     */
    @ExceptionHandler
    public Result handleException(Exception e){
        //建议打印日志
        log.error("e: "+e);
        return Result.fail("发生内部错误");
    }

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

模拟异常的代码

java 复制代码
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        return "string";
    }

    @RequestMapping("/t2")
    public Integer t2(){
        int a = 10/0;//抛出算数异常
        return 1;
    }

    @RequestMapping("/t3")
    public Boolean t3(){
        String a = null;
        System.out.println(a.length());//抛出空指针异常
        return true;
    }
}

t2:

t3:

完!!!

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei5 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github