【 Spring AOP学习二】统一功能处理:拦截器异常返回数据格式

news2024/9/24 17:17:23

目录

一、用户登录权限效验

🍑1、Spring拦截器实现用户统一登录验证(重要)

(1)定义一个拦截器

(2)将自定义拦截器加入到系统配置中

🍑2、拦截器实现原理

🍑3、统一前缀加api访问

🍑4、统一异常处理

🍑5、统一数据返回格式


一、用户登录权限效验

(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 方法来进⾏统⼀的用户登录权限验证。 

(2)中等版方法:利用上一节学习的 Spring AOP的前置通知或者环绕通知实现:

缺点:

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

(1) 没办法获取到 HttpSession 对象。
(2)我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,这样的话规则很难去定义。比如我们要求注册方法和登录方法不拦截,这个时规则的定义就比较麻烦。

(3)最终版方法:基于Spring AOP的Spring 拦截器

Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为两步:
(1)创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
(2)将自定义拦截器加入WebMvcConfigurer addInterceptors 方法中。

接下来我们通过Spring 拦截器来实现一下用户登录验证问题吧~


🍑1、Spring拦截器实现用户统一登录验证(重要)

@Slf4j
@RequestMapping("/aop")
@RestController
public class UserController {
    //模拟写博客
    @RequestMapping("/writeBlog")
    public String writeBlog(){
        log.info("write blog success...");
        return "write blog success...";
    }
    //编辑博客
    @RequestMapping("/editBlog")
    public String editBlog(){
        log.info("edit blog success...");
        return "edit blog success...";
    }
    //删除博客
    @RequestMapping("/deleteBlog")
    public String deleteBlog(){
        log.info("deleteBlog success...");
        return "deleteBlog success...";
    }

    //注册博客
    @RequestMapping("/regBlog")
    public String regBlog(){
        log.info("regBlog success...");
        return "regBlog success...";
    }
    //登录博客
     @RequestMapping("/loginBlog")
     public boolean loginBlog1(HttpServletRequest request,String username,String password)                    
         {
        log.info("login ing...");
        //判断username和password是否为空
        //为空
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
            return false;
        }
        //如果账号和密码不为admin,就返回false
        if(!"admin".equals(username) || !"admin".equals(password)){
            return false;
        }
        //不为空:传入true,如果没有就创建session
        HttpSession session = request.getSession(true);
        //设置session
        session.setAttribute("username",username);
        return true;
    }
}

(1)定义一个拦截器

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否登录:如果session为空,不去创建
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("username")!=null){
            //说明是登录状态,不进行拦截
            return true;
        }
        //否则说明不是登录状态,直接返回:给出提示:没有权限登录
        response.setStatus(401);
        return false;
    }
}

(2)将自定义拦截器加入到系统配置中

//实现WebMvcConfigurer
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    //重写addInterceptors方法
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        //添加拦截器
        //先要有一个拦截器对象:方式1:直接new出来
//        LoginInterceptor loginInterceptor = new LoginInterceptor();
        //方式2:通过注解的方式
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//表示在未登录状态下拦截所有
                .excludePathPatterns("/aop/loginBlog")//表示在未登录状态下loginBlog方法仍然可以访问。(也就是排除掉一些方法)
                .excludePathPatterns("/aop/regBlog");//表示排除掉一些方法
    }
}

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

2、关于对象:

3、我们上方的代码主要干了什么?

(1)首先我们在定义LoginInterceptor拦截器的前提是:session中没有值,那么除loginBlog和regBlog之外的所有方法都要被拦截成功。

(2)之后我们在UserController 类的loginBlog方法中,模拟用户登录,输入用户名和密码为admin,然后将username值设置到session中。那么此时session中就有值了,此时不进行拦截,理论上所有的方法都应该被访问成功。

   那么我们测试一下,是不是成功了呢?

测试效果:

 

 

 🍑2、拦截器实现原理

正常的访问路径

有了拦截器之后,会在调用 Controller 之前就进行相应的业务处理:

 实现原理源码分析 

        Spring 中的拦截器也是通过动态代理和环绕通知的思想实现。(拦截器是基于AOP实现的,Spring是基于Servlet实现的。)
        所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出。而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,从上述源码可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle 方法,这样就会我们之前自定义的拦截器对应上,此时用户登录权限的验证方法就会执行 ,这就是拦截器的实现原理。

🍑3、统一前缀加api访问

@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接⼝添加 api 前缀
@Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

🍑4、统一异常处理

@Slf4j
@ResponseBody
@RestController
@RequestMapping("/web")
public class ErrorController {
    //算数异常
    @RequestMapping("/test01")
    public boolean test01(){
        int a = 10/0;
        return true;
    }

    //空指针异常
    @RequestMapping("/test02")
    public boolean test02(){
        String str = null;
        System.out.println(str.length());
        return true;
    }

    //手动抛出异常
    @RequestMapping("/test03")
    public boolean test03(){
        throw new RuntimeException("test03手动抛出异常");
    }
}

此时访问test01/test02/test03都访问出错,统一报错如下。 

此时,我想要针对不同的错误显示不同的异常信息,就用到了统一异常处理。

(1)统一异常处理使用@ControllerAdvice和@ExceptionHandler实现;

(2)@ControllerAdvice表示控制器通知类

(3)@ExceptionHandler表示异常处理器

(4)两个注解结合表示出现异常的时候执行某个通知,也就是某个方法事件;

(5)注意最上方加@ResponseBoby,表示返回的是数据格式。

(6)方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。

//@ControllerAdvice 表示控制器通知类
@ControllerAdvice
@ResponseBody
public class ErrorHandler {
    //@ExceptionHandler 是异常处理器
    //两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件

    //当没有具体的异常时,就匹配这个异常
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);//业务码
        result.put("msg","Exception...");
        return result;
    }
    //(1)表示捕获算数异常
    @ExceptionHandler
    public Object error(ArithmeticException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);//业务码
        result.put("msg","ArithmeticException...");
        return result;
    }
    //(2)表示捕获空指针异常
    @ExceptionHandler
    public Object error(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);//业务码
        result.put("msg","NullPointerException...");
        return result;
    }
}

 

🍑5、统一数据返回格式

一般我们前后端交互,都需要知道这几点:

(1)业务处理是否成功;

(2)如果成功,业务执行的结果 是什么;

(3)如果失败,失败原因是什么。

对应下面三点:

(1)HTTP状态码

(2)code:业务状态码;

(3)data:业务处理成功,执行返回的结果

(4)msg:业务失败,返回的结果是什么。

统一数据返回格式的好处:方便前端更好的接受和解析后端数据接口返回的数据。

@Slf4j
@RequestMapping("/web")
@RestController
public class UserController {
    //测试boolean类型
    @RequestMapping("/test01")
    public boolean test01(){
        log.info("test01 ...");
        return true;
    }
    //测试String类型
    @RequestMapping("/test02")
    public String test02(){
        log.info("test02 ...");
        return "test02...";
    }
}
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {

    @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("success",1);
        result.put("data",body);
        result.put("errMsg","");
        return result;
    }
}

 (1)如果方法返回的类型是boolean成功。

(2) 如果方法返回的类型是String,报错。 

处理方案:

小Tips:

(1)统一数据返回格式用 @ControllerAdvice + ResponseBodyAdvice 实现;

(2)@ResponseBodyAdvice 要重写两个方法,其中supports方法表示内容是否需要重写,返回true表示需要重写;

(3)在处理正常数据时,使用hashMap返回;

(4)处理String类型的数据时,直接手动封装返回对象转成json字符串返回;

(5)@SneakThrows相当于tryCatch用来处理异常的。

SpringBoot统一返回处理出现cannot be cast to java.lang.String异常_伏加特遇上西柚的博客-CSDN博客后端服务使用Restful API的形式,前后端得规范一般是json格式,如果返回的是字符串直接手动封装返回对象转成json字符串返回即可。当返回的数据是字符串时,此处得方法是要去循环遍历。异常,返回其他类型就无任何问题。当返回的数据是非字符串时使用的。会先被遍历到,这时会认为。默认会注册一些自带的。_cannot be cast to java.lang.stringhttps://blog.csdn.net/weixin_43811057/article/details/127655041

SpringBoot实现统一功能处理的教程详解_java_脚本之家这篇文章主要为大家详细介绍了SpringBoot如何实现统一功能处理,文中的示例代码讲解详细,对大家学习或工作有一定借鉴价值,感兴趣的同学可以参考阅读下icon-default.png?t=N6B9https://www.jb51.net/program/28588440v.htm


 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/810507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

car tire

汽车轮胎规则参数 小车、轿车轮胎规格参数图解-有驾 半挂车轮胎尺寸多少 货车轮胎尺寸对照表【汽车时代网】

二叉树的最大深度和最小深度(两种方法:递归+迭代)

二叉树的最大深度&#xff1a; class Solution { public:int maxDepth(TreeNode* root) {//DFS 深度优先搜索if(rootNULL) return 0;//深度等于max&#xff08;左子树的深度&#xff0c;右子树的深度&#xff09;1&#xff1b;return max(maxDepth(root->left),maxDepth(roo…

QT自定义控件实现并导入

QT自定义控件 介绍 QT Creator自定义控件和designer控件导入 1.安装QT5.7.1 2.将QT编译器目录、lib目录、include目录导入path 使用说明 使用说明按照 1.创建QtDesigner自定义控件工程&#xff0c;打开Qt Creator,创建一个Qt 设计师自定义控件&#xff0c;如下图所示&#xf…

靠着AI自动生成视频撸自媒体收益,赚了包辣条~

友友们&#xff0c;小卷今天给大家分享下如何通过AI自动生成视频&#xff0c;只需要3分钟就能做出一个视频&#xff0c;把视频发到B站、抖音、西瓜上&#xff0c;还能赚包辣条哦~ 文末给大家准备了AI变现的案例及AIGC知识库&#xff0c;记得领取哦&#xff01; 1.收益 先看看收…

手写SpringBoot模拟核心流程

首先&#xff0c;SpringBoot是基于的Spring&#xff0c;所以我们要依赖Spring&#xff0c;然后我希望我们模拟出来的SpringBoot也支持Spring MVC的那一套功能&#xff0c;所以也要依赖Spring MVC&#xff0c;包括Tomcat等&#xff0c;所以在SpringBoot模块中要添加以下依赖&…

13. Mybatis-Plus

目录 1. MyBatis-Plus 简介 2. 新建项目 3. 添加依赖 4. 配置数据库 5. 编码 1. MyBatis-Plus 简介 通过官网&#xff1a;MyBatis-Plus MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyB…

浅谈 AI 大模型的崛起与未来展望:马斯克的 xAI 与中国产业发展

文章目录 &#x1f4ac;话题&#x1f4cb;前言&#x1f3af;AI 大模型的崛起&#x1f3af;中国 AI 产业的进展与挑战&#x1f3af;AI 大模型的未来展望&#x1f9e9;补充 &#x1f4dd;最后 &#x1f4ac;话题 北京时间 7 月 13 日凌晨&#xff0c;马斯克在 Twiiter 上宣布&am…

【MTI 6.S081 Lab】networking

【MTI 6.S081 Lab】networking BackgroudYour Job (hard)实验任务 解决方案 这个实验中&#xff0c;在dns解析时&#xff0c;去请求其dns服务器失败&#xff0c;所以将nettest中的dns服务器改为我自己的。修改的位置大概在nettest.c的235行。 这个实验的设备的具体使用就没去看…

行为型:迭代器模式

定义 迭代器模式提供一种方法按顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示。迭代器模式是目的性极强的模式&#xff0c;它主要是用来解决遍历问题。 es6 中的迭代器 JS原生的集合类型数据结构&#xff0c;有Array&#xff08;数组&#xff09;和…

【算法基础:动态规划】5.2 线性DP

文章目录 例题列表898. 数字三角形895. 最长上升子序列&#xff08;n^2两重循环dp&#xff09;896. 最长上升子序列 II&#xff08;贪心二分查找&#xff09;897. 最长公共子序列902. 最短编辑距离899. 编辑距离⭐⭐⭐⭐⭐ 例题列表 898. 数字三角形 每个数字可以从它上面的左…

【雕爷学编程】MicroPython动手做(15)——掌控板之AB按键

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

怎么查看gcc的安装路径

2023年7月29日 很简单&#xff0c;通过在命令行输入如下命令就可以了&#xff1a; gcc -print-search-dirs在Windows中 在Linux中 ​​​

Github Copilot在JetBrains软件中登录Github失败的解决方案

背景 我在成功通过了Github Copilot的学生认证之后&#xff0c;在VS Code和PyCharm中安装了Github Copilot插件&#xff0c;但在PyCharm中插件出现了问题&#xff0c;在登录Github时会一直Retrieving Github Device Code&#xff0c;最终登录失败。 我尝试了网上修改DNS&…

❤️创意网页:能量棒页面 - 可爱版(加载进度条)

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

电动汽车集群并网的分布式鲁棒优化调度模型(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 文献来源&#xff1a; 随着可再生能源&#xff08;Renewable Energy Source, RES&#xff09;的渗透率不断提高&#xff0c;RES 固有的间歇性对…

【RabbitMQ】Linux系统服务器安装RabbitMQ

一、下载 首先应该下载erlang&#xff0c;rabbitmq运行需要有erland环境。 官网地址&#xff1a;https://www.erlang.org/downloads 下载rabbitmq 官网环境&#xff1a;https://www.rabbitmq.com/download.html 注意&#xff1a;el7对应centos7&#xff0c;el8对应centos8…

机器学习:混合高斯聚类GMM(求聚类标签)+PCA降维(3维降2维)习题

使用混合高斯模型 GMM&#xff0c;计算如下数据点的聚类过程&#xff1a; Datanp.array([1,2,6,7]) 均值初值为: μ1,μ21,5 权重初值为: w1,w20.5,0.5 方差: std1,std21,1 K2 10 次迭代后数据的聚类标签是多少&#xff1f; 采用python代码实现&#xff1a; from scipy import…

iOS开发-CABasicAnimation实现小球左右摆动动画效果

iOS开发-CABasicAnimation实现小球左右摆动动画效果 之前开发中遇到需要实现小球左右摆动动画效果&#xff0c;这里作下记录。 一、效果图 二、实现代码 2.1 CABasicAnimation CABasicAnimation基础动画&#xff0c;包括duration、repeatCount、repeatDuration、beginTime、…

Java中对Redis的常用操作

目录 数据类型五种常用数据类型介绍各种数据类型特点 常用命令字符串操作命令哈希操作命令列表操作命令集合操作命令有序集合操作命令通用命令 在Java中操作RedisRedis的Java客户端Spring Data Redis使用方式介绍环境搭建配置Redis数据源编写配置类&#xff0c;创建RedisTempla…

JavaEE—— Callable接口、JUC的常见类、线程按安全的集合类(八股)

文章目录 一、Callable 接口二、JUC的常见类1. ReentrantLock2. 原子类(简单知晓)3.信号量 Semaphore4.CountDownLatch(简单了解) 三、线程安全的集合类1.多线程环境使用 ArrayList2.多线程使用哈希表 一、Callable 接口 Callable 接口类似于 Runnable 接口 Runnable 接口用来…