Spring 统一功能处理(拦截器)

news2024/11/24 14:02:30

文章目录

  • Spring拦截器
    • 1.统一用户登录权限校验
      • 1) SpringAOP 用户统一验证的问题
      • 2) Spring拦截器
      • 3) 拦截器实现原理
      • 4)同一访问前缀添加
    • 2. 统一异常处理
    • 3. 统一数据返回格式
      • 1)统一数据返回的好处
      • 2)统一数据返回实现


Spring拦截器

SpringBoot统一功能处理。也就是AOP的具体实现。

1.统一用户登录权限校验

最原始的用户登录验证方法,我们通过封装了一个方法来判断用户是否登录,但如果实现的功能多了,那么每一个需要登录的功能都要在对应的接口中来调用这个函数来判读是否登录。

public class LoginStatus {
    public static User getStatus(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            //当前用户未登录
            return null;
        }

        User user = (User) session.getAttribute("user");
        if (user == null) {
            //当前用户未登录
            return null;
        }

        return user;
    }
}

上面的代码虽然已经封装成了方法,但是如果随着程序功能的增多,那么每一个控制器都要调用这个接口进行判断,就出现了代码的冗余,也增加了代码的维护成本。

这个时候就需要提供一个公共的方法来进行统一的用户登录权限验证了。

1) SpringAOP 用户统一验证的问题

统一验证我们可以使用SpringAOP的前置通知或者是环绕通知来实现

@Aspect // 说明该类为一个切面
@Component
public class UserAspect {

    // 定义切点,使用 AspectJ表达式语法,拦截UserController所有方法
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){}

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行Before前置通知");
    }

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        System.out.println("执行环绕通知的前置方法");

        try {
            // 执行(拦截的)业务方法
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("执行环绕通知的后置方法");

        return result;
    }
}

我们发现原生的SpringAOP的切面实现用户登录权限的校验功能,会有两个问题

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

那就可以使用Spring的拦截器

2) Spring拦截器

对于上面两个问题Spring中提供了解决方案,提供了具体实现的拦截器:Handlerlnterceptor,拦截器的实现分为两个步骤:

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

1.自定义拦截器

用户登录权限校验,自定义拦截器代码实现:

/**
 * 定义自定义拦截器实现用户登录校验
 */
@Configuration
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) {
            response.setStatus(200);
            return true;
        }
        response.setStatus(403);
        return false;
    }

}

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

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

@Configuration
public class AppConfig implements WebMvcConfigurer {

    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()) // 添加自定义拦截器
                .addPathPatterns("/**") //拦截所有接口
                .excludePathPatterns("/**/login")//排除的接口
    }
}
  • addPathPatterns:表示需要拦截的 URL,*”表示拦截任意⽅法(也就是所有⽅法)
  • excludePathPatterns:表示需要排除的 URL。
  • 以上的拦截规则可以拦截程序中使用的URL、静态文件(图片、前端文件等)

排除所有静态的资源

@Configuration
public class AppConfig implements WebMvcConfigurer {

    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()) // 添加自定义拦截器
                .addPathPatterns("/**")
                .excludePathPatterns("/**/*.html")//排除所有静态资源
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/img/*");
    }
}

3) 拦截器实现原理

原本正常的调用流程是这样的:

在这里插入图片描述

但是添加了拦截器后,在调用Controller之前会进行相对应业务处理

在这里插入图片描述

在这里插入图片描述

⽽所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 部分源码如下

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

       //此处省略上面代码
    // 调用预处理
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // 执行Controller中的业务
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    if (asyncManager.isConcurrentHandlingStarted()) {
        return;
    }
    // ......后面代码省略
                
}

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽
applyPreHandle ⽅法的实现源码如下

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            // 获取项⽬中使⽤的拦截器 HandlerIntercepto
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中
的 preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所示 :

在这里插入图片描述

只有当我们重写的方法放回true的时候才会继续走调用Controller的业务代码,否则就是直接放回给前端

在这里插入图片描述

拦截器的实现原理:

  • 从宏观上来讲的话,它是根据AOP的思想来去执行的,把统一的方法放到前置处理器来进行处理
  • 在Spring中的拦截器实现,就是在调度器类里的调度方法,调度方法在真正调用Controller之前,它会有一个方法先去扫描当前Spring中所有拦截器的一个列表,然后去执行这些拦截器,只有当拦截器执行通过的时候,它才会继续走后面的流程,才会去走Controller然后返回结果给前端。

4)同一访问前缀添加

该方法可以给所有接口添加一个访问前缀,让前端访问接口时都要加上blog,比如原来是/add,添加前缀后就是/blog/add

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("blog",pre->true);
    }
}

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

2. 统一异常处理

同一异常处理是通过@ControllerAdvice+@ExceptionHandler两个注解结合实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理,两个结合起来就表示出现异常的时候执行某一个通知,也就是执行某个方法,代码实现:

@ControllerAdvice
public class ErrorAdvice  {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handler(Exception e) {
        Map<String,Object> map = new HashMap<>();
        map.put("success",1);
        map.put("status",-1);
        map.put("message","服务器接口异常");
        return map;
    }
}

注意:方法名和返回值可以任意,重要的是注解。

这里的代码表示的是发生任何异常都给前端返回一个HashMap,也可以指定异常进行处理代码如下

@ControllerAdvice
public class ErrorAdvice  {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object exceptionAdvice(Exception e) {
        Map<String,Object> map = new HashMap<>();
        map.put("success",1);
        map.put("status",-1);
        map.put("message","服务器接口异常");
        return map;
    }

    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public Object nullPointerExceptionAdvice(NullPointerException exception) {
        Map<String,Object> map = new HashMap<>();
        map.put("success",1);
        map.put("status",-1);
        map.put("message",exception.toString());
        return map;
    }
}

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

3. 统一数据返回格式

1)统一数据返回的好处

统一数据返回格式有很多好处

  1. 方便前端程序员接收和解析后端接口返回的数据
  2. 降低前端程序员和后端程序员的沟通成本,只需要按照指定格式实现功能,所有接口放回值都是固定的
  3. 有利于项目整体的维护和修改,有利于后端统一标准规范。

2)统一数据返回实现

统一的数据返回格式可以使用@ControllerAdvice+ResponseBodyAdvice实现

  • supports方法返回true表示对返回内容进行重写,就会执行beforeBodyWrite方法
  • beforeBodyWrite方法中body就是Controller返回的内容
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    /**
     * 内容是否需要重写,此方法可以选择部分控制器和方法进行重写
     * 返回 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) {
        Map<String,Object> result = new HashMap<>();
        result.put("success",200);
        result.put("status",1);
        result.put("data",body);

        return result;
    }
}


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

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

相关文章

第13章 项目合同管理

文章目录 13.2.1 按信息系统 范围 划分的合同分类 4451、总承包合同2、单项工程承包合同3、分包合同 13.2.2 按项目 付款方式 划分的合同分类 4461、总价合同2、成本补偿合同&#xff08;卖方有利&#xff09;3、工料合同 13.3.1 项目合同的内容 44713.3.2 项目合同签订的注意事…

【设计模式】我终于读懂了迭代器模式。。。

看一个具体的需求 编写程序展示一个学校院系结构&#xff1a;需求是这样 要在一个页面中展示出学校的院系组成&#xff0c; 一个学校有多个学院&#xff0c; 一个学院有多个系。 如图&#xff1a; 传统的设计方案(类图) 传统的方式的问题分析 将学院看做是学校的子类&#xf…

深度学习(23):SmoothL1Loss损失函数

0. 基本介绍 SmoothL1Loss是一种常用的损失函数&#xff0c;通常用于回归任务中&#xff0c;其相对于均方差(MSE)损失函数的优势在于对异常值(如过大或过小的离群点)的惩罚更小&#xff0c;从而使模型更加健壮。 SmoothL1Loss的公式为&#xff1a; l o s s ( x , y ) { 0.5 …

机器人中的数值优化(三)—— 无约束最优化方法基础、线搜索准则

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

eureka自我保护模式详解(全网最全)

1. 什么叫自我保护模式&#xff1f; 当微服务客户端启动后&#xff0c;会把自身信息注册到Eureka注册中心&#xff0c;以供其他微服务进行调用。一般情况下&#xff0c;当某个服务不可用时&#xff08;一段时间内没有检测到心跳或者连接超时等&#xff09;&#xff0c;那么Eure…

spring3:更简单的读取和存入对象

目录 1.存储 Bean 对象 1.1 前置⼯作&#xff1a;配置扫描路径&#xff08;重要&#xff09; 1.2 添加注解存储 Bean 对象 1.2.1.Controller[控制器] 1.2.2 Service[服务] 1.2.3 repoistory[仓库] 1.2.4 Configuration[配置] 1.2.5 Component[组件] 1.3为什么要这么多类…

Spring 创建和使用

文章目录 什么是 Bean ?1. 创建 Spring 项目1.创建一个 Maven 项目2. 添加 Spring 依赖1.配置 Maven 国内源2.添加 Maven 依赖 3.添加启动项 2.存储Bean对象1.创建 Bean2.注册 Bean3.获取并使用 Bean 对象1. 得到 Spring 对象常见方式有两种: 2. 获取 Bean 对象Bean 的三种获取…

SpringMVC中使用LocalDateTime、LocalDate等参数作为入参数据转换问题

1、接收GET请求方式及POST请求表单方式时间类型传参&#xff0c;需要自定义参数转换器或者使用ControllerAdvice配合initBind&#xff0c;不设置的话表单方式会报以下错误&#xff1a; 这种情况要和时间作为Json字符串时区别对待&#xff0c;因为前端json转后端pojo底层使用的是…

什么是Auto GPT-4? OpenAI 最新语言模型概览

动动发财的小手&#xff0c;点个赞吧&#xff01; 人工智能正在快速发展&#xff0c;近年来最令人兴奋的发展之一是创建可以生成类似人类文本的语言模型。领先的人工智能研究机构 OpenAI 最近发布了其最新的语言模型 Auto GPT-4。 在什么是 Auto GPT-4&#xff1f; OpenAI 最新…

【Java笔试强训 29】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;求正数数…

AtCoder Beginner Contest 300(D-G)

D - AABCC (atcoder.jp) &#xff08;1&#xff09;题目大意 给你个数N&#xff0c;问你不超过N的三个质数abc组成的数有多少个。 &#xff08;2&#xff09;解题思路 考虑到枚举的数不会特别多&#xff0c;因此预处理出1e6的质因子&#xff0c;暴力枚举即可。 &#xff08;3&a…

在体育新闻文本中提取关键词可以使用什么技术

在体育新闻文本中提取关键词可以使用以下技术&#xff1a; 1. 领域词典&#xff1a; 通过构建体育领域的词汇表&#xff0c;将其中的词语作为关键词&#xff0c;可以较好地提取体育新闻中的关键词。 就当下的研究情况&#xff0c;国内外有哪些体育领域的词汇表http://t.csdn…

Reactive Streams介绍与应用分析

目录 一、Reactive Streams基本知识 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;反应式流的特点 基本特性1:事件驱动&变化传递 基本特性2:数据流 基本特性3:声明式 高级特性1:流量控制&#xff08;回压&#xff09; 高级特性2:异步边界 &…

ALBEF:基于动量蒸馏的视觉语言表示学习

Align before Fuse&#xff1a;Vision and Language Representation Learning with Momentum Distillation ALBEF&#xff1a;基于动量蒸馏的视觉语言表示学习 摘要 大规模的视觉和语言表征学习在各种视觉-语言任务上显示出有希望的改进。大多数现有的方法采用了基于Transform…

【平衡二叉搜索树(AVL)-- 旋转】

目录&#xff1a; 前言1、二叉搜索树的插入2、AVL树的旋转&#xff08;1&#xff09;右单旋&#xff08;LL&#xff09;&#xff08;2&#xff09;左单旋&#xff08;RR&#xff09;&#xff08;3&#xff09;右左双旋&#xff08;LR&#xff09;&#xff08;4&#xff09;左右…

第18章 项目风险管理

文章目录 18.1.2 风险的分类 54318.1.3 风险的性质 544项目风险管理6个过程&#xff08;风险管理、识别风险、实施定性风险分析、实施定量风险分析、规划风险应对、控制风险&#xff09;组织和干系人的风险态度影响因素18.3.3 规划风险管理的输出 550风险识别的原则18.4.2 识别…

vim编辑文件

目录 一、vi和vim &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;相同点 &#xff08;3&#xff09;不同点 二、使用vim打开文件 三、使用vim编辑文件 &#xff08;1&#xff09;vim的四个模式 &#xff08;2&#xff09;命令模式下的编辑命令 删除 复制 …

树莓派4:跑通Tensorflow的Sequential模型用于图片分类

重要提示&#xff1a;由于树莓派相对孱弱的性能&#xff0c;直接在其上训练模型可能花&#xff08;lang4&#xff09;费非常长的时间。本文仅作为示例性的可行性参考&#xff0c;请酌情考虑实验平台。 著名的Tensorflow框架也可以运行在树莓派上。理论还没吃透&#xff0c;但使…

【量化交易笔记】5.SMA,EMA 和WMA区别

股票中的SMA&#xff0c;EMA和WMA是常用的技术分析指标。这些指标基于历史股价计算得出&#xff0c;可以帮助投资者了解股票的趋势&#xff0c;为决策提供依据。虽然它们都是平均值算法&#xff0c;但它们之间还是有一些区别的。 SMA 简单移动平均线&#xff08;Simple Moving…

参与辅助服务的用户侧储能优化配置及经济分析(matlab代码)

目录 1 主要内容 目标函数 2 部分程序 3 程序结果 4 程序链接 1 主要内容 该程序方法复现《参与辅助服务的用户侧储能优化配置及经济分析》&#xff0c;首先&#xff0c; 建立了用户侧储能的全生命周期成本和考虑辅助服务的收益模型&#xff1b;其次&#xff0c;在两部…