0


22.<SpringBoot 统一功能处理(拦截器+统一返回结果+统一异常处理)>

本篇文章讲解

1.拦截器

2.统一数据返回格式

3.统一异常处理的操作

一、拦截器

前言

上一篇文章讲解了图书管理系统,我们没有实现强制登录功能。

我们可以想到。

我们可以在后端程序根据Session来判断用户是否登录。但是实现方法比较麻烦。

  • 需要修改每个接口的处理逻辑
  • 需要修改每个接口的返回
  • 接口定义需要修改,前端代码也需要修改

因此本篇文章我们讲解更简单的办法。

统一拦截所有的请求,并进行Session校验。这就是我们本文要讲到的拦截器。

1.1什么是拦截器

拦截器是Spring框架提供的核心功能之一。主要用来拦截用户的请求。

在指定方法前后。

根据业务需要执行,预先设定的代码。也就是说。允许开发人员提前预定义一些逻辑。

在用户的请求响应前后执行。也可以在用户请求前阻止其执行。

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

  • 如果有就可以放行。
  • 如果没有就进行拦截。

1.2拦截器的基本使用步骤

  1. 定义拦截器

  2. 注册配置拦截器

1.2.1自定义拦截器

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

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
 
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse res
 log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
 return true;
 }
 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse respo
 log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
 }
 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse 
 log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
 }
}

preHandle()方法:目标方法执行前执行。

  • 返回true:继续执行后续操作。
  • 返回false:中断后续操作。

postHandle()方法:目标方法执行后执行

afterCompletion()方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,暂时不了解)

1.2.2注册配置拦截器(配置拦截路径)

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

@Configuration
public class WebConfig implements WebMvcConfigurer {
 //⾃定义的拦截器对象 
 @Autowired
 private LoginInterceptor loginInterceptor;
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 //注册⾃定义拦截器对象 
 registry.addInterceptor(loginInterceptor)
 .addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所有
 }
}

我们启动服务,试试访问任意请求。观察后端日志。

可以看到

preHandle方法执行就放行了。开始执行目标方法。目标方法执行完成之后执行

postHandle和afterCompletion方法

我们把拦截器中preHandle方法的返回值改为false,再观察运行结果

可以看到,拦截器拦截了请求,没有进行响应.

1.3拦截器细节详解

1.3.1拦截路径

我在注册配置拦截器的时候,

通过 addPathPatterns() 方法指定要拦截哪些请求.

也可以

通过** excludePathPatterns()方法**指定不拦截哪些请求.

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

1.3.2拦截器执行流程

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

1.添加拦截器后, 执行 Controller的方法之前, 请求会先被拦截器拦截住.

执行preHandle()方法,这个方法需要返回⼀个布尔类型的值.

如果返回true, 表示放行本次操作, 继续访问controller中的方法.

如果返回false,则不会放行(controller中的方法也不会执行).

2.controller当中的方法执行完毕后,

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

1.4DispatcherServlet 源码分析(了解)

当Tomcat启动之后, 有一个核心的类DispatcherServlet, 它来控制程序的执行顺序.

所有请求都会先进到DispatcherServlet,执行doDispatch 调度方法. 如果有拦截器, 会先执行拦截器
方法的代码, 如果 preHandle() 返回true, 继续访问controller中的方法. controller当中的方法执行完毕后,再回过来执行postHandle()afterCompletion(),返回DispatcherServlet, 最终给浏览器响应数据.

1.5适配器模式

适配器模式也叫包装器模式。

将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。

简单来说就是目标类不能直接使用,通过⼀个新类进行包装一下,

适配调用方使用。把两个不兼容的接口,通过一定的方式使之兼容。

适配器模式角色

•Target:目标接口(可以是抽象类或接口),客户希望直接用的接口

•Adaptee:适配者,但是与Target不兼容

•Adapter:适配器类,此模式的核心,通过继承或者引用适配者的对象,把适配者转为目标接口

•client:需要使用适配器的对象。

适配器模式的实现

比如之前讲到的slf4j就使用了适配器模式。

slf4j提供了⼀系列打印日志的api,底层调用的是log4j或者logback来打日志,

我们作为调用者,只需要调用slf4j的api就行了.

/**
 * slf4j接⼝ 
 */
interface Slf4jApi{
 void log(String message);
}
/**
 * log4j 接⼝ 
 */
class Log4j{
 void log4jLog(String message){
     System.out.println("Log4j打印:"+message);
 }
}
/**
 * slf4j和log4j适配器 
 */
class Slf4jLog4JAdapter implements Slf4jApi{
 private Log4j log4j;
 public Slf4jLog4JAdapter(Log4j log4j) {
     this.log4j = log4j;
 }
 @Override
 public void log(String message) {
     log4j.log4jLog(message);
 }
}
/**
 * 客⼾端调⽤ 
 */
 public class Slf4jDemo {
     public static void main(String[] args) {
         Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
         slf4jApi.log("使⽤slf4j打印⽇志");
 }
}

适配器模式可以看作⼀种"补偿模式",用来补救设计上的缺陷。

应用这种模式算是"无奈之 举",

如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使⽤适配器模式了,所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进行改造,并且希望可以复用原有代码实现新的功能。如版本升级等.

二、统一数据返回格式

2.1定义数据返回格式

1.首先我们写一个Result类,用来当做返回结果

比如在博客系统中,我们就可以定义这样的一个类

/**
 * 统一返回结果
 * 我们先设定返回的结果
 * 为了让其他地方方便调用。我们统一给方法加上static
 */
@Data
public class Result {
    private int code; //200成功, -1失败  -2未登录
    private String errMsg;
    private Object data;

    public static Result success(Object data){
        Result result = new Result();
        result.setCode(Constant.SUCCESS_CODE);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }

    public static Result fail(String errMsg){
        Result result = new Result();
        result.setCode(Constant.FAIL_CODE);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }

    public static Result fail(String errMsg,Object data){
        Result result = new Result();
        result.setCode(Constant.FAIL_CODE);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

    public static Result unLogin(String errMsg){
        Result result = new Result();
        result.setCode(Constant.UNLOGIN_CODE);
        result.setErrMsg("用户未登录");
        result.setData(null);
        return result;
    }
}

2.2 自定义类实现ResponseBodyAdvice接口

统一的数据返回格式使用@ControllerAdvice 和ResponseBodyAdvice 的方式实现 @ControllerAdvice 表示控制器通知类。

添加类 ResponseAdvice ,实现 ResponseBodyAdvice 接口。并在类上添加 @ControllerAdvice 注解。

eg:

supports方法:判断是否要执行beforeBodyWrite方法。

true为执行,false不执行。通过该方法可以。选择哪些类或哪些方法的response要进行处理,其他的不进行处理。

@ControllerAdvice //注意加上这个注解才有效。
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        /**
         * 设定哪些方法统一返回结果
         * 哪个接口执行统一结果返回
         */
        return true;
    }
    @SneakyThrows
    //这个注解帮我们对
    // return objectMapper.writeValueAsString(Result.success(body));
    //进行try catch。异常处理
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //统一结果返回的具体逻辑

        if(body instanceof Result){
            return body;
        }
        //对String 类型单独处理.否则会报错
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

从returnType获取类名和方法名

//获取执⾏的类 
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执⾏的⽅法 
Method method = returnType.getMethod();

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

三、统一异常处理

3.1自定义ErrorHandler类加上@ResponseBody@ControllerAdvice注解

统一异常处理。@ControllerAdvice + @ExceptionHandler 来实现的,

@ControllerAdvice 表示控制器通知类,

@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。

注:关于加不加@ResponseBody

接口返回为数据时,需要加 @ResponseBody 注解。代表的是返回数据。

  • 如果你使用的是@RestController ,不需要在方法上加 @ResponseBody 注解,因为它已经隐式地加上了。
  • 如果是普通的@Controller,并且希望返回数据(如 JSON),则需要显式地加上 @ResponseBody注解。
  • 在 @ControllerAdvice 中,通常不需要加 @ResponseBody,因为 Spring 会自动处理返回值的转换和响应。
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
 @ExceptionHandler
 public Object handler(Exception e) {
 return Result.fail(e.getMessage());
 }
}

eg:

/捕获空指针异常代码的两种写法

    //捕获空指针异常代码的两种写法
    @ExceptionHandler(NullPointerException.class)
    public Result handler(Exception e){
        return Result.fail(e.getMessage());
    }
    @ExceptionHandler
    public Result handler(NullPointerException e){
        return Result.fail(e.getMessage());
    }

如果注解里面没有写捕获什么异常。那么就会以参数中为准。

以上代码表示,如果代码出现Exception异常(包括Exception的⼦类),就返回一个。Result的对象,Result 对象的设置参考Result.fail(e.getMessage()) 。参考统一返回结果那个result代码。

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

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

@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
 @ExceptionHandler
 public Object handler(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());
 }
}

本文转载自: https://blog.csdn.net/m0_73456341/article/details/143651671
版权归原作者 振兴祁门 所有, 如有侵权,请联系我们删除。

“22.<SpringBoot 统一功能处理(拦截器+统一返回结果+统一异常处理)>”的评论:

还没有评论