统一功能处理

目录

一、拦截器

自定义拦截器:

注册配置拦截器:

拦截器执行流程

二、统一数据返回格式

三、统一异常处理


一、拦截器

拦截器是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
}
相关推荐
程序员鱼皮3 小时前
我造了个程序员练兵场,专治技术焦虑症!
java·计算机·程序员·编程·自学
n8n4 小时前
SpringAI 完全指南:为Java应用注入生成式AI能力
java·后端
不爱编程的小九九4 小时前
小九源码-springboot082-java旅游攻略平台
java·开发语言·旅游
只是懒得想了4 小时前
用C++实现一个高效可扩展的行为树(Behavior Tree)框架
java·开发语言·c++·design-patterns
码农阿树4 小时前
Java 离线视频目标检测性能优化:从 Graphics2D 到 OpenCV 原生绘图的 20 倍性能提升实战
java·yolo·目标检测·音视频
夫唯不争,故无尤也4 小时前
Maven创建Java项目实战全流程
java·数据仓库·hive·hadoop·maven
weixin_404551244 小时前
openrewrite Maven plugin configuration
java·maven·configuration·openrewrite
我是华为OD~HR~栗栗呀4 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
yan8626592464 小时前
于 C++ 的虚函数多态 和 模板方法模式 的结合
java·开发语言·算法