JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

2年前 (2022) 程序员胖胖胖虎阿
168 0 0

文章目录

  • 前言
  • 1.⽤户登录权限效验
    • 1.1、最初⽤户登录验证
    • 1.2、Spring AOP ⽤户统⼀登录验证的问题
    • 1.3、Spring 拦截器
      • 了解 创建一个 Spring 拦截器 的流程
        • 1、 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的preHandle(执⾏具体⽅法之前的预处理)⽅法。
        • 2、将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则。
      • 实践:实现一个自定义的拦截器,使其在项目中生效。
        • 预备工作:创建一个 Spring AOP 的项目。
        • 1、 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的preHandle(执⾏具体⽅法之前的预处理)⽅法。
        • 2、将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则。
        • 3、验收成果
  • 拦截器实现原理
    • 实现原理源码分析
    • 拦截器小结
  • 扩展:统⼀访问前缀添加
  • 2.统⼀异常处理
  • 3.统⼀数据返回格式
    • 为什么需要统⼀数据返回格式?
    • 统⼀数据返回格式的实现
  • 总结
    • 最后,补充一点:@ControllerAdvice 源码分析 - 了解

前言

接下来是 Spring Boot 统⼀功能处理模块了,也是 AOP 的实战环节,要实现的⽬标有以下 3 个:

1、统⼀⽤户登录权限验证;
2、统⼀数据格式返回;
3、统⼀异常处理。

接下我们⼀个⼀个来看。


1.⽤户登录权限效验

⽤户登录权限的发展从之前每个⽅法中⾃⼰验证⽤户登录权限,到现在统⼀的⽤户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。

1、最初的用户登录效验:在每个方法里面获取 session 和 session 中的 用户信息,如果用户信息存在,那么就登录成功了,否则就登录失败了。

2、第二版用户登录效验:提供了统一的方法,在每个需要验证的方法中调用统一的用户登录身份效验方法来判断。

3、第三版用户登录效验: 使用 Spring AOP 来使用统一的用户登录效验。
就是说:不再需要我们敲代码去调用统一方法了,Spring AOP 会帮我们自动调用。


1.1、最初⽤户登录验证

@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 某⽅法 1
     */
    @RequestMapping("/m1")
    public Object method(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null)
        {
// 说明已经登录,业务处理
            return true;
        } else {
// 未登录
            return false;
        }
    }
    /**
     * 某⽅法 2
     */
    @RequestMapping("/m2")
    public Object method2(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null)
        {
// 说明已经登录,业务处理
            return true;
        } else {
// 未登录
            return false;
        }
    }
// 其他⽅法...
}

从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:

1、 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。
2、 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
3、 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫。


1.2、Spring AOP ⽤户统⼀登录验证的问题

说到统⼀的⽤户登录验证,我们想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现,实现模板代码如下:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
    // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut(){ }
    // 前置⽅法
    @Before("pointcut()")
    public void doBefore(){
    }
    // 环绕⽅法
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏拦截⽅法
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏");
        return obj;
    }
}

如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:

1.、没办法获取到 HttpSession 对象。
2、我们要对⼀部分⽅法进⾏拦截,⽽另⼀部分⽅法不拦截,
如用户的注册⽅法和登录⽅法是不拦截的,这样的话切点方法的拦截规则很难定义,甚⾄没办法定义。

那这样如何解决呢?
接下来,我们就使用 Spring 提供的方案来解决 原始 AOP 所带来的问题。
这个解决方案,也就是 第四版用户登录效验,也是下面要讲的内容:Spring 拦截器。


1.3、Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

1、 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
2、 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。

有的人可能会有疑问:Spring 拦截器 和 AOP 有什么关系呢?

Spring 拦截器是基于 AOP 实现的。

因为 原生 AOP 没有办法获取 httpSession 和 Request 对象。而且,拦截规则也非常难以定义。
所以,官方就做了一个拦截器。
然后,把我们之前最基础的 原生 AOP 做了一个封装
得到了一个新东西 “ Spring拦截器 ”。

我们也可以将 Spring 拦截器 称为是 Spring AOP 的一种实现。

Spring 拦截器中,封装了很多对象,对象里面就提供了专门的方法 来解决 原生 AOP 所带来的两个问题。


了解 创建一个 Spring 拦截器 的流程

1、 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的preHandle(执⾏具体⽅法之前的预处理)⽅法。

HandlerInterceptor 的 中文意思 就是 拦截器。

重写 的 preHeadle 方法,返回值的类型是 布尔类型。

返回是 true,则表示通过了 拦截器的验证,可以继续 执行,调用 目标方法了。
反之,验证没有通过,直接返回一个错误信息。

总的来说:拦截器 运行的模式 和 Spring AOP 是一样的。
起着一个 代理的作用。

现在是 前端先访问 拦截器,执行里面的 preHandle 方法,如果方法返回一个 true,则继续执行后面的层序。
反之如果返回的是 false,直接返回一个 错误信息,后面的代码就不用执行了。

下面我们来看个具体的代码

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null)
        {
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

我们可以发现: preHandle方法中提供了 HttpServletRequest 和HttpServletResponse 的 对象!!!

有了请求对象,我们就可以获取到 Session 对象,从而获取到里面的用户信息。
也就意味着我们可以进行用户登录状态的检测了!
写法 和 Servlet 是 一样的,这个你看上面的代码就知道了。

而且,我们可以通过响应对象,直接向前端返回一个 JSON 格式(或者 HTML)的数据。
甚至,我还可以实现一些业务。
假设 用户处于未登录 状态,我们可以通过 HttpServletResponse 发送重定向。
让用户直接跳转到 登录界面,让他先去登录。

需要注意的是:
虽然我们实现了拦截器,但是 preHandle 方法 是一个普通方法。
没有程序去调用它。
因为我们没有给它加上任何注解。
没有加注解,意味着 框架的 启动 和 这个没有任何关系。
另外,这个自定义拦截器的方法,只是具有拦截器的业务逻辑,但是没有拦截的规则!

所以,才会有第二步:将⾃定义拦截器加⼊到系统配置。
或者说:配置到当前项目中。并且设置拦截的规则


2、将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则。

将上一步中的自定义拦截器加入到框架配置中,具体代码实现如下:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有接⼝
                .excludePathPatterns("/art/param11"); // 排除接⼝
    }
}

大家需要注意一点:

不管是 Spring,还是 Spring Boot。
它们默认的配置文件都是叫做 Spring MVC Configurer 的一个文件。

我们要设置当前的一些配置信息的时候,我们是一定要去实现WebMvcConfigurer 接口的。
然后,去重写里面的 addInterceptors(添加多个拦截器)方法。
只有这样,系统才会认为 当前设置的东西 是给 当前项目去设置的配置文件。

所以说:光加上 @Configuration 注解是不行,还必须实现 WebMvcConfigurer 接口,重写里面的方法,当前这个类才是一个配置文件。
这是一种固定写法,注解 和 实现接口,都不能少。
所有的当前项目的配置文件,都是 来自于实现 WebMvcConfigurer 接口 。
所以,这个接口是一定要实现的。

实现它之后,
在当前类上, 加上@Configuration 注解,是为了让 当前这个类 作为 系统的一个配置类。
此时,类里面设置的东西(在这里指的是拦截器),才能生效。

而 addInterceptors - 添加多个拦截器方法,表示 拦截器,可以添加(支持)多个自定义拦截器。
即:一个项目中可以设置多个拦截器。
比如:
1、一个拦截器 去做登录的效验
2、一个拦截器 去验证 前端数据 的正确性 合法性。
等等。。。

并且,不同的拦截器可以配置 不同的路由规则。
这个先暂且方法,后面会细讲

⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则。
实现步骤:
1、在类上,添加 Configuration 注解,使其成系统的配置类
2、当前类实现 WebMvcConfigurer 接口
3、重写 WebMvcConfigurer 接口中的 addInterceptors 方法。
上面讲述了 实现 Spring 拦截器的流程。
下面,我们可以开始实践了!


实践:实现一个自定义的拦截器,使其在项目中生效。

预备工作:创建一个 Spring AOP 的项目。

我以专业版为准,跟社区版都一样的。
只是 社区版需要安装 插件 Spring Assistant 插件,我不就再赘述。

在上一篇 AOP 理论知识博文中,就讲过了 如何创建一个 aop 项目。
但是!现在有点不同!
就是 这个 Spring 拦截器,Spring Boot 项目 本身就支持!
所以,就不用引入 AOP 框架的支持了。

如果你们想了解 原生 AOP 的 操作模式,可以前面的 aop 文章。
其实,你们可以点击文章左下角
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
就能查看与之相关的文章。
而且,手机也有这个功能。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
废话不多说,先创建好项目。
Maven 中央仓库的链接https://mvnrepository.com/
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔


1、 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的preHandle(执⾏具体⽅法之前的预处理)⽅法。

先在根目录下 创建一个 config 子包,毕竟拦截类使用的是 @configuration 注解。属于它的一部分。
并且,在里面创建一个拦截类(拦截器)LoginIntercept。

因为我们要做的是 登录验证嘛,就取名 Login,后面的 Intercept 是拦截器的意思。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

接下来开始实现 拦截器。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
此时,我们就实现了一个最简单的 验证用户登录状态的 拦截器(自定义)。


2、将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则。

JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

你们说难吗?
其实也不难,就是制定拦截规则需要过细一点。
不要把不该拦截的url,忘记写,就行了。


3、验收成果

我们先来给它加一个页面资源。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
页面有了,下面我们来创建 controller 层的代码。【负责与前端交互的代码】
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

下面,我们先在没有登录的情况,访问那些被拦截的资源。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
还有一个 index 的 静态页面对吧,我们也来试一下,效果与 访问 index 方法是一样的效果。因为没有登录嘛
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

再来访问那些 没有被拦截的资源。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
我们在这里在拓展一下业务。
在未登录的情况下访问其他访问方法,或者页面,让其跳转到登录页面。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

此时,我们先去访问 login方法,登录成功后,再去访问 index 方法
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
效果就出来了,非常的nice!!!


拦截器实现原理

拦截器实现原理
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔


实现原理源码分析

所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

【启动项目访问某个映射方法之后,再去看 idea 控制台打印内容,你就能看到】
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
那么,问题来了:为什么执行每一条请求的时候,都会先去调用 DispatcherServlet 呢?
先从字面意思来看: Dispatcher 的 中文意思是 调度器(分发器)。
也就是说:所有的请求进来之后,会先进入调度器,由调度器进行分发。

举个例子:
上学的时候,每个老师都有一个课代表,对不对?
没次发作业本的时候,不是直接由老师发到你手里的。
一般都是把作业全部交给课代表,有课代表分法作业。
原因很简单!
1、方便,不用自己动手
2、老师不止带一个班,所以对每个学生坐的位置不是了解。分发的效率低。
3、课代表也班级中一员。对每个人的位置很熟悉,发作业效率要高一些。
放在程序里,也是一样的理由。
DispatcherServlet 就是 “课代表”,对方每个映射方法都“轻车熟路”,执行的效率极高。

⽽所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法.
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
另外,为什么Spring 封装的拦截器的 preHandle 方法中会封装了 HttpServletRequest 和HttpServletResponse 呢?
也是因为 它执行目标方法之前,回调用 DispatcherServlet 中 doDispatch 方法。
进行预处理的时候,就会使用 这两参数,
顺水推舟,你要用,我就给你传嘛。


拦截器小结

通过上⾯的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,⼤体的调⽤流程如下:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
这个时候,原本 Spring AOP 的 切面类(代理对象) 换成了 DispatcherServlet 。
一个是我们自定义的,一个 Spring 框架 自带的。

不过执行原理并没有变。
用户 想要与 目标对象 直接交互,必须要通过 代理对象(拦截器)的验证。
验证通过,才有资格访问目标对象。


扩展:统⼀访问前缀添加

所有请求地址添加 api 前缀:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
index 方法也是一样的。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
为了不影响后面的操作,我把 添加前缀的代码给注销了


2.统⼀异常处理

对于 统一异常处理,提出一个问题:如果不统一处理异常会是什么效果?

本来安道理来说:是返回一个 JSON 格式的信息,因为前后端都是通过JSON 来交流的。
但是!我 “ 不小心 ” 写出了一个 bug
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
解决的办法:我们就需要对所有的异常做一个拦截。

有的人可能会说:我可以给那些可能会出现异常的带啊,使用 tryCatch 包裹。但是这些导致代码太长了!阅读性还低!
尤其是涉及到 事务,如果你乱使用 tryCatch 会导致 事务 无法正常进行回滚。
从而出现 预料之外的错误。

为了解决这个问题,我们在 Spring 中 使用的是 统一异常处理的方式来解决。

对所有的异常进行一个拦截。
拦截之后,你 “ 想干嘛就干嘛 ”.
你想返回一个什么形式的代码给前端,都可以。

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的.

@ControllerAdvice 表示:控制器通知类

@ExceptionHandler 是异常处理器

两个结合表示:
当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件。

统⼀异常处理 的实现有两步:

1、给当前统一处理的类上加上 @ControllerAdvice 注解。
@ControllerAdvice 注解,你可以认为该类是 controller 的通知类,或者 说:这是对 Controller 里面方法的增强。
Controller的通知,指的是 controller 里面出现什么问题之后,我能走到这个通知的方法里执行相应的处理。

2、给方法上添加一个 @ExceptionHandler 注解。
ExceptionHandler : Exception(异常) + Handler (处理器 / 管理器)
然后呢, @ExceptionHandler 注解后面 需要带有参数,表明处理异常的类型。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
进一步认证一下:我们使用 fiddler 抓个包。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
现在放心了吧!
前端在拿到这个响应,知道它是一个算术异常,他就可以对其进行“包装”,然后返回给用户,比如:非法操作,此接口异常,稍后再试、

也就是说:后端一般返回的错误信息,是给 前端程序员去使用的。
前端程序员会根据错误信息,比如:对错误码,进行“包装”,让用户看的懂。
比如 404 未找到对应的资源。
我们来访问一个不存在的资源,以b栈为例。因为它的页面做的确实很好!
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

这里需要注意的是 没有定义拦截的异常。
是不会被拦截的。
来看下面的例子

JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
此时,我们丢出一个想法:
上面写的异常拦截方法,是具有很强的针对性的,一个方法针只能针对一种错误类型。
那么,存不存在一种 “ 灵活 ” 写法。
简单来说:对于 异常类型,我们不写死。
我们一个方法,就能拦截多种类型的异常,甚至所有的异常,都可以拦截?

当然自定义的异常类型,另说。不过差别也不大。

我们来一个暴力解法,直接 异常的源头 Exception 作为 @ExceptionHandler 参数对象。
这样我们不就可以拦截所有的异常类型了嘛!
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔


3.统⼀数据返回格式

为什么需要统⼀数据返回格式?

统⼀数据返回格式的优点有很多,⽐如以下⼏个:

1、 ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。

2、 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回的。

3、 有利于项⽬统⼀数据的维护和修改。

4、 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。

我们之前是使用一个对象,来作为 返回格式的标准。
但是!还是有那么一点丁麻烦的!
我们还有更简单的做法!
举个例子:
就是开惯手动挡的车,觉得还行。
但是,开过自动挡车,很容易就回不去了。
因为 自动挡,实在是香!
再往后看,N年以后,出现自动驾驶的汽车,我们也就该彻底 “堕落” 了。。。。

程序也是一样的,它也在不断的进化!
之前。我们刚学的时候,比较菜嘛。
老老实实的每个方法都去自己实现一个 HashMap,搞得自己很累,很憔悴。
然后呢,后面能力得到的提升,把 它给封装起来,只需要去new一个对象就行了。不再需要我们去手动实现了。
但是!依然觉得不是那么爽。
学到当前这一块,之前是怎么做的,现在还是这么做的。【“ 大道至简!”】
就比如说:登录的时候,本来就只需要返回 true,或者 false。
那么,我就只返回 true / false,就行了。
我呢,会拦截这个 true 和 false,并将这个 true / false 填充到 data 里面。
再去封装好,让前端能够识别 的 统一的对象,这是不是很爽?
这样做,开发者就不需要关注,到底返回的是一个什么格式的数据给前端。
对于前端来说,都一样。我按到的数据格式是不变的。
中间的处理,就是由统一功能去处理的。


统⼀数据返回格式的实现

统⼀的数据返回格式可以使⽤ @ControllerAdvice + ResponseBodyAdvice 的⽅式实现。

类上加 @ControllerAdvice 。
并且,该类实现 ResponseBodyAdvice 接口。
必须重写 里面的 supports 和 beforeBodyWrite 方法。
supports(支持):当它返回 true,就表示要对 返回的数据,进行 格式的重写。反之,当它返回 false,就不需要重写了。

当需要 到 重写,就会进入 beforeBodyWrite 方法。
beforeBodyWrite 方法的意义:就是将数据返回之前,要不要进行重写 响应数据。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

下面来验证一下效果。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
抓包的结果,显示 返回的响应是 JSON 类型的。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
我们再来试一下,用户名密码 不匹配的情况
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
非常滴舒服!

我们再来写一个极其简单的伪代码。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔

等等!我好像还没有演示 supports 方法 返回 false ,是否还会触发重写。
搞起!
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔


总结

经过上述的练习。
我们可以发现 统一功能处理 是存在缺陷的,就是 返回数据的信息描述被写死了。
因为都是统一的格式,所以返回的键值对 的 键值 是无法改变的。
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
换句话来说:统一带来的问题就是:牺牲了 “ 灵活 ”!

所以,关于统一功能的使用,还是需要根据实际情况来决定!


最后,补充一点:@ControllerAdvice 源码分析 - 了解

大家其实回想一下 第二个功能(异常) 和 第三个功能(返回数据的格式),其实都是事件啊!
我们代码出现一个异常,这就是一个异常事件啊!
事件发生了,我们是可以感知到的。
感知到了之后,就可以根据感知到的事件的处理方法,敲自己的业务代码。
这样的话,如果感知到事件,我们写代码,就能起到作用了。
异常是这样,返回统一的数据格式,也是这样的!
每一次返回一个数据给前端的时候,它也是一个事件(数据传输事件)。
事件发生了,我们的程序是能感知到的。所以才能做统一额处理。

说白了:@ControllerAdvice注解,就是针对项目中发生的事件,做拦截的。

通过对 @ControllerAdvice 源码的分析我们可以知道上⾯统⼀异常和统⼀数据返回的执⾏流程,我们先从 @ControllerAdvice 的源码看起,点击 @ControllerAdvice 实现源码如下:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
从上述源码可以看出 @ControllerAdvice 派⽣于 @Component 组件,⽽所有组件初始化都会调⽤ InitializingBean 接⼝。
所以接下来我们来看 InitializingBean 有哪些实现类?
在查询的过程中我们发现了,
其中 Spring MVC 中的实现⼦类是 RequestMappingHandlerAdapter,
它⾥⾯有⼀个⽅法 afterPropertiesSet() ⽅法,表示所有的参数设置完成之后执⾏的⽅法。
如下图所示:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
⽽这个⽅法中有⼀个 initControllerAdviceCache ⽅法,查询此⽅法的源码如下:
JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
我们发现这个⽅法在执⾏是会查找使⽤所有的 @ControllerAdvice 类,这些类会被容器中,但发⽣某个事件时,调⽤相应的 Advice ⽅法。
⽐如
返回数据前调⽤统⼀数据封装,⽐如发⽣异常是调⽤异常的Advice ⽅法实现。

相关文章

暂无评论

暂无评论...