• SpringBoot 统一功能处理


    Spring Boot 统一功能处理模块也是 AOP 的实战环节,要实现的目标有以下 3 个:

    1. 统一用户登录权限验证;
    2. 统一数据格式返回;
    3. 统一异常处理。

    1. 用户登录权限效验

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

    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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

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

    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;
        }
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

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

    1.3 Spring 拦截器

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

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

    1.3.1 自定义拦截器

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

    package com.example.demo.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * 1.先创建一个登陆的拦截器
     */
    public class LoginInterceptor implements HandlerInterceptor {
        //返回true表示验证通过,可以执行后面的方法;
        //返回false表示验证失败,后面的代码就不能执行;
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HttpSession session=request.getSession(false);
            if(session!=null&&session.getAttribute("username")!=null){
                //表示用户登录
                return true;
            }
            //表示验证未通过,自动跳转到登录页面
            response.sendRedirect("/login.html");
            return false;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    1.3.2 将自定义拦截器加入到系统配置并设置相应的拦截规则

    package com.example.demo.config;
    
    import com.example.demo.interceptor.LoginInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class AppConfig implements WebMvcConfigurer {
    
        //注入
        @Autowired
        private LoginInterceptor loginInterceptor;
    
        /**
         * 给当前项目添加拦截器
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //两种实现方法
    //        registry.addInterceptor(new LoginInterceptor());
            registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**")//拦截所有的url
            .excludePathPatterns("/user/reg")//不拦截/user/reg
            .excludePathPatterns("/**/*.html");//不拦截所有的html页面
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述
    其中:

    • addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意方法(也就是所有方法)。
    • excludePathPatterns:表示需要排除的 URL。

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

    1.4 拦截器实现原理

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

    1.4.1 拦截器小结

    Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,大体的调用流程如下:

    在这里插入图片描述

    1.5 扩展:统一访问前缀添加

    所有请求地址添加 api 前缀:

        /**
         * 给所有的服务添加api前缀
         * @param configurer
         */
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.addPathPrefix("api",c->true);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中第二个参数是一个表达式,设置为 true 表示启动前缀。

    2.统一异常处理

    统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件,具体实现代码如下:

    2.1创建统一的异常类,并标识当前的类

    创建统一的异常类,并标识当前的类为@ControllerAdvice

    //对控制器进行增强(当前类为统一异常封装类)
    @ControllerAdvice
    public class MyExceptionResult {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    2.2实现异常处理的方法,在方法上标识

    实现异常处理的方法(可以定义多个),在方法上标识@ExceptionHandler(异常类)

    /**
         * 算术异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(ArithmeticException.class)
        public HashMap<String, Object> myArithmeticException(ArithmeticException e) {
            HashMap<String, Object> result = new HashMap<String, Object>();
            result.put("state", -1);
            result.put("msg", "算术异常:" + e.getMessage());
            result.put("data", null);
            return result;
        }
    
        /**
         * 空指针异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(NullPointerException.class)
        public HashMap<String, Object> myNullPointerException(NullPointerException e) {
            HashMap<String, Object> result = new HashMap<String, Object>();
            result.put("state", -1);
            result.put("msg", "空指针异常:" + e.getMessage());
            result.put("data", null);
            return result;
        }
    
        /**
         * 默认异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(Exception.class)
        public HashMap<String, Object> myException(Exception e) {
            HashMap<String, Object> result = new HashMap<String, Object>();
            result.put("state", -1);
            result.put("msg", "默认异常:" + e.getMessage());
            result.put("data", null);
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    3.统一数据返回格式

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

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

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

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

    1.标识当前类为@ControllerAdvice ,并重写 ResponseBodyAdvice接口
    2.重写里面的类,封装统一返回方法

    具体实现代码如下:

    1. 标识当前类为@ControllerAdvice ,并重写 ResponseBodyAdvice接口
      在这里插入图片描述
    2. 重写里面的类,封装统一返回方法
    package com.example.demo.config;
    
    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;
    
    import java.util.HashMap;
    
    /**
     * 统一数据返回封装
     */
    @ControllerAdvice
    public class MyResponseBodyAdvice implements ResponseBodyAdvice {
    
        // 是否进行重写的方法改为 true,true 表示在返回数据之前,进行统一的格式封装
        @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) {
            HashMap<String, Object> result = new HashMap<>();
            result.put("state", 1);
            result.put("data", body);
            result.put("msg", "");
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

  • 相关阅读:
    二叉树的操作大全
    json转对象
    web前端期末大作业——贵州山地旅游介绍网页1页 HTML旅游网站设计与实现
    基于k8s、rancher的集群kubectl容器命令行的功能实现
    新手初学课,Python入门体验之九九乘法表
    【数据库】实验一 openGauss数据库管理系统
    Java_网络多线程
    就在刚刚 Kubernetes 1.25 正式发布,包括这些重大变化
    Linux 中安装MySQL
    Nanoprobes多组氨酸 (His-) 标签标记:重组蛋白检测方案
  • 原文地址:https://blog.csdn.net/mzpqq/article/details/126093564