• 【SSM】SpringBoot 统一功能处理(重点:Spring 拦截器实现与原理)


    【大家好,我是爱干饭的猿,本文重点介绍用户登录权限效验之Spring 拦截器、统一异常处理、统一数据返回格式。

    后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关注一下吧】

    上一篇文章:《【SSM】Bean 作用域和生命周期(重点:Bean 的生命周期5大部分)》


    🤞目录🤞

    ✨1.用户登录权限效验

    1.1 最初用户登录验证

    1.2 Spring AOP 用户统一登录验证的问题

    1.3 Spring 拦截器

    1. 自定义拦截器

    2. 将自定义拦截器加入到系统配置

    1.4 拦截器实现原理

    1. 实现原理源码分析

    2. 拦截器小结

    ✨2. 统一异常处理

    ✨3. 统一数据返回格式

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

    3.2 统一数据返回格式的实现


    ✨1.用户登录权限效验

    用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统一的用户登录验证处理,它是一个逐渐完善和逐渐优化的过程。

    1.1 最初用户登录验证

    最初用户登录验证的实现方法:

    1. @RestController
    2. public class IndexController {
    3. @RequestMapping("/method1")
    4. public String method1(HttpServletRequest request){
    5. User user = null;
    6. HttpSession session = request.getSession();
    7. if(session != null){
    8. user = (User) session.getAttribute("currentUser");
    9. }
    10. if(user == null){
    11. return "redirect:/login.html";
    12. }
    13. return "redirect:/";
    14. }
    15. @RequestMapping("/method2")
    16. public String method2(HttpServletRequest request){
    17. User user = null;
    18. HttpSession session = request.getSession();
    19. if(session != null){
    20. user = (User) session.getAttribute("currentUser");
    21. }
    22. if(user == null){
    23. return "redirect:/login.html";
    24. }
    25. return "redirect:/";
    26. }
    27. }

    每个方法中都有相同的用户登录验证权限,它的缺点是:

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

    1.2 Spring AOP 用户统一登录验证的问题

    我们想到的另一个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

    1. @Aspect
    2. @Component
    3. public class UserAspect {
    4. @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    5. public void pointCut(){}
    6. // 前置方法
    7. @Before("pointcut()")
    8. public void doBefore(){
    9. }
    10. // 环绕方法
    11. @Around("pointCut()")
    12. public Object doAround(ProceedingJoinPoint joinPoint){
    13. User user = null;
    14. System.out.println("Around 方法开始执行");
    15. try {
    16. // 执行拦截方法
    17. user = (User) joinPoint.proceed();
    18. } catch (Throwable throwable) {
    19. throwable.printStackTrace();
    20. }
    21. System.out.println("Around 方法结束执行");
    22. return user;
    23. }
    24. }

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

    1. 没办法获取到 HttpSession 对象。
    2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样 的话排除方法的规则很难定义,甚至没办法定义。 

    1.3 Spring 拦截器

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

    1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理) 方法。
    2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。

    1. 自定义拦截器

    接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下: 

    1. @Slf4j
    2. @Component // 拦截器,如果用户未登录,则直接重定向到登录页,不会走到后续操作
    3. public class MyInterceptor implements HandlerInterceptor {
    4. @Override
    5. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    6. log.info("MyInterceptor.preHandle()");
    7. User currentUser = null;
    8. HttpSession session = request.getSession();
    9. if(session != null){
    10. currentUser = (User)session.getAttribute("currentUser");
    11. }
    12. if(currentUser == null){
    13. // 说明用户未登录
    14. response.sendRedirect("/login.html");
    15. return false;
    16. }
    17. return true;
    18. }
    19. @Override
    20. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    21. log.info("MyInterceptor.postHandle()");
    22. }
    23. @Override
    24. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    25. log.info("MyInterceptor.afterCompletion()");
    26. }
    27. }

    2. 将自定义拦截器加入到系统配置

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

    1. // 1. 必须是一个 Spring bean(否则没有机会调用)
    2. // 2. 必须实现了 WebMvcConfigurer 接口
    3. @Configuration
    4. public class WebConfig implements WebMvcConfigurer {
    5. private final MyInterceptor myInterceptor;
    6. @Autowired
    7. public WebConfig(MyInterceptor myInterceptor) {
    8. this.myInterceptor = myInterceptor;
    9. }
    10. @Override
    11. public void addInterceptors(InterceptorRegistry registry) {
    12. // 可以添加多个拦截器,先添加的拦截器,先被应用
    13. registry.addInterceptor(myInterceptor)
    14. .addPathPatterns("/**") // 应用到所有 URL 上
    15. .excludePathPatterns("/error") // 只要有错误,都会到这
    16. .excludePathPatterns("/login.html") // 但不包括login.html
    17. .excludePathPatterns("/login.do"); // 但不包括login.do
    18. }
    19. }
    • addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意方法(也就是所有方法)。
    • excludePathPatterns:表示需要排除的 URL。

    说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。

    1.4 拦截器实现原理

    正常情况下的调用顺序:

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

     

    1. 实现原理源码分析

    1. 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现 。
    2. 在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle。
    3. 在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中 的 preHandle 方法。

    2. 拦截器小结

    通过上面的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的, 大体的调用流程如下:


    ✨2. 统一异常处理

    统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的

    • @ControllerAdvice 表示 控制器通知类
    • @ExceptionHandler 是异常处理器

    两个结合表示当出现异常的时候执行某个通知,也 就是执行某个方法事件,具体实现代码如下: 

    1. @ControllerAdvice
    2. @ResponseBody
    3. public class ExceptionAdvice {
    4. @ExceptionHandler(Exception.class)
    5. public Object exceptionAdvice(Exception e){
    6. HashMap result = new HashMap<>();
    7. result.put("success", -1);
    8. result.put("message", "总的异常信息:" + e.getMessage());
    9. result.put("data", null);
    10. return result;
    11. }
    12. @ExceptionHandler(NullPointerException.class)
    13. public Object NullPointerExceptionAdvice(NullPointerException e) {
    14. HashMap result = new HashMap<>();
    15. result.put("success", -1);
    16. result.put("message", "空指针异常:" + e.getMessage());
    17. result.put("data", null);
    18. return result;
    19. }
    20. }
    • 如果出现了异常就返回给前端一个 HashMap 的对象,其中包含的字段如代码中定义的那样。
    • 我们可以针对不同的异常,返回不同的结果。
    • 当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配。

    ✨3. 统一数据返回格式

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

    统一数据返回格式的优点有很多,比如以下几个:

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

    3.2 统一数据返回格式的实现

    统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,具体实现代码如下: 

    1. @ControllerAdvice
    2. public class ResBodyAdvice implements ResponseBodyAdvice {
    3. @Override
    4. public boolean supports(MethodParameter returnType, Class converterType) {
    5. // 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)
    6. // 返回 true 表示重写
    7. return false;
    8. }
    9. @Override
    10. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    11. // 构造统一返回对象
    12. HashMap result = new HashMap<>();
    13. result.put("success", 1);
    14. result.put("message", "");
    15. result.put("data", body);
    16. return result;
    17. }
    18. }

    分享到此,感谢大家观看!!!

    如果你喜欢这篇文章,请点赞关注吧,或者如果你对文章有什么困惑,可以私信我。

    🏓🏓🏓

  • 相关阅读:
    GFS分布式文件系统
    计算机网络 - 物理层 选择复习题
    MySQL之事务
    C++指针传递和引用传递(函数参数中&和*的区别)
    A Cooperative Approach to Particle Swarm Optimization
    LeetCode 1422. 分割字符串的最大得分
    OS X(MACOS) C/C++ 遍历系统所有的IP路由表配置。
    运动用品品牌排行榜,2022年值得买的运动用品推荐
    浅谈游戏机制
    C++异步调用方法
  • 原文地址:https://blog.csdn.net/m0_62218217/article/details/127432308