0


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

本篇文章讲解

1.拦截器

2.统一数据返回格式

3.统一异常处理的操作

一、拦截器

前言

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

我们可以想到。

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

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

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

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

1.1什么是拦截器

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

在指定方法前后。

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

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

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

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

1.2拦截器的基本使用步骤

  1. 定义拦截器

  2. 注册配置拦截器

1.2.1自定义拦截器

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

  1. @Slf4j
  2. @Component
  3. public class LoginInterceptor implements HandlerInterceptor {
  4. @Override
  5. public boolean preHandle(HttpServletRequest request, HttpServletResponse res
  6. log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
  7. return true;
  8. }
  9. @Override
  10. public void postHandle(HttpServletRequest request, HttpServletResponse respo
  11. log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
  12. }
  13. @Override
  14. public void afterCompletion(HttpServletRequest request, HttpServletResponse
  15. log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
  16. }
  17. }

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

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

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

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

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

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

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

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

可以看到

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就行了.

  1. /**
  2. * slf4j接⼝
  3. */
  4. interface Slf4jApi{
  5. void log(String message);
  6. }
  7. /**
  8. * log4j 接⼝
  9. */
  10. class Log4j{
  11. void log4jLog(String message){
  12. System.out.println("Log4j打印:"+message);
  13. }
  14. }
  15. /**
  16. * slf4j和log4j适配器
  17. */
  18. class Slf4jLog4JAdapter implements Slf4jApi{
  19. private Log4j log4j;
  20. public Slf4jLog4JAdapter(Log4j log4j) {
  21. this.log4j = log4j;
  22. }
  23. @Override
  24. public void log(String message) {
  25. log4j.log4jLog(message);
  26. }
  27. }
  1. /**
  2. * 客⼾端调⽤
  3. */
  4. public class Slf4jDemo {
  5. public static void main(String[] args) {
  6. Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
  7. slf4jApi.log("使⽤slf4j打印⽇志");
  8. }
  9. }

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

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

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

二、统一数据返回格式

2.1定义数据返回格式

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

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

  1. /**
  2. * 统一返回结果
  3. * 我们先设定返回的结果
  4. * 为了让其他地方方便调用。我们统一给方法加上static
  5. */
  6. @Data
  7. public class Result {
  8. private int code; //200成功, -1失败 -2未登录
  9. private String errMsg;
  10. private Object data;
  11. public static Result success(Object data){
  12. Result result = new Result();
  13. result.setCode(Constant.SUCCESS_CODE);
  14. result.setErrMsg("");
  15. result.setData(data);
  16. return result;
  17. }
  18. public static Result fail(String errMsg){
  19. Result result = new Result();
  20. result.setCode(Constant.FAIL_CODE);
  21. result.setErrMsg(errMsg);
  22. result.setData(null);
  23. return result;
  24. }
  25. public static Result fail(String errMsg,Object data){
  26. Result result = new Result();
  27. result.setCode(Constant.FAIL_CODE);
  28. result.setErrMsg(errMsg);
  29. result.setData(data);
  30. return result;
  31. }
  32. public static Result unLogin(String errMsg){
  33. Result result = new Result();
  34. result.setCode(Constant.UNLOGIN_CODE);
  35. result.setErrMsg("用户未登录");
  36. result.setData(null);
  37. return result;
  38. }
  39. }

2.2 自定义类实现ResponseBodyAdvice接口

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

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

eg:

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

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

  1. @ControllerAdvice //注意加上这个注解才有效。
  2. public class ResponseAdvice implements ResponseBodyAdvice {
  3. @Autowired
  4. private ObjectMapper objectMapper;
  5. @Override
  6. public boolean supports(MethodParameter returnType, Class converterType) {
  7. /**
  8. * 设定哪些方法统一返回结果
  9. * 哪个接口执行统一结果返回
  10. */
  11. return true;
  12. }
  13. @SneakyThrows
  14. //这个注解帮我们对
  15. // return objectMapper.writeValueAsString(Result.success(body));
  16. //进行try catch。异常处理
  17. @Override
  18. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  19. //统一结果返回的具体逻辑
  20. if(body instanceof Result){
  21. return body;
  22. }
  23. //对String 类型单独处理.否则会报错
  24. if (body instanceof String){
  25. return objectMapper.writeValueAsString(Result.success(body));
  26. }
  27. return Result.success(body);
  28. }
  29. }

从returnType获取类名和方法名

  1. //获取执⾏的类
  2. Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
  3. //获取执⾏的⽅法
  4. 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 会自动处理返回值的转换和响应。
  1. @ControllerAdvice
  2. @ResponseBody
  3. public class ErrorAdvice {
  4. @ExceptionHandler
  5. public Object handler(Exception e) {
  6. return Result.fail(e.getMessage());
  7. }
  8. }

eg:

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

  1. //捕获空指针异常代码的两种写法
  2. @ExceptionHandler(NullPointerException.class)
  3. public Result handler(Exception e){
  4. return Result.fail(e.getMessage());
  5. }
  6. @ExceptionHandler
  7. public Result handler(NullPointerException e){
  8. return Result.fail(e.getMessage());
  9. }

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

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

  1. public static Result fail(String msg) {
  2. Result result = new Result();
  3. result.setStatus(ResultStatus.FAIL);
  4. result.setErrorMessage(msg);
  5. result.setData("");
  6. return result;
  7. }

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

  1. @ResponseBody
  2. @ControllerAdvice
  3. public class ErrorAdvice {
  4. @ExceptionHandler
  5. public Object handler(Exception e) {
  6. return Result.fail(e.getMessage());
  7. }
  8. @ExceptionHandler
  9. public Object handler(NullPointerException e) {
  10. return Result.fail("发⽣NullPointerException:"+e.getMessage());
  11. }
  12. @ExceptionHandler
  13. public Object handler(ArithmeticException e) {
  14. return Result.fail("发⽣ArithmeticException:"+e.getMessage());
  15. }
  16. }

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

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

还没有评论