当前位置: 首页 > web >正文

SpringBoot 统一功能处理

在我们进行项目编写时,有时相同的一段代码在不同的地方使用多次,对于这种情况,spring 帮我们实现了统一功能处理,下面介绍一些常用的统一功能处理。

一、拦截器

在有些项目中,是需要用户登录才能进行后续访问的。但如果这时有人拿到了后端接口的 url,这时就能跳过用户登录进而访问我们的网站,这就有可能对网站造成攻击,对于这种情况,我们可以将用户的部分信息存到 session 中,在每次访问接口前,都可以先获取到 session,再将用户信息取出,看看这个用户是否登录,若登录,就允许继续访问,若没有登陆,就强制登录。

但是,在我们的项目中,接口的数目可不小,若是在每个接口中都加上这段逻辑,就会显得我们的代码过于冗余,于是 Spring 就为我们构造了拦截器。

拦截器主要用来拦截用户的请求,在指定方法前后执行拦截器中预设的代码,也就是说,拦截器可以在用户的请求之前发挥作用,也可以在用户的请求之后发挥作用。

对于上述判断用户登录的业务,我们就可以在拦截器中写明判断用户是否登录的代码,这样在用户发送请求时,就会先执行这段代码,若用户没有登录,就会阻止用户进行访问,并跳转到登陆页面(这是前端干的事)。

下面介绍拦截器的具体用法。

在实现拦截器时,需要实现 HandlerInterceptor 类中的 preHandle,postHandle,afterCompletion 方法,preHandle 是在目标方法执行前执行的,postHandle 是在目标方法执行之后执行的,afterCompletion 是在试图渲染完成之后执行的,但现在后端开发基本上都不会涉及到试图,那么这个方法就不重点介绍了。

代码如下:

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {//在目标方法执行前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("在目标方法执行前执行");return true;}//在目标方法之后执行@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("在目方法执行后执行");}//试图渲染完成之后执行@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("视图渲染完成后执行");}
}

preHandle 有返回值,返回 true 表示不拦截请求,返回 false 表示拦截请求。

定义好拦截器的功能后,接下来就需要注册拦截器。注册拦截器需要实现 WebMvcConfigurer 中的 addInterceptors,代码如下:

@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/test/**");}
}

使用 addInterceptor 即表示需要注册的是哪个拦截器,使用 addPathPatterns 即表示需要拦截的路径,即拦截器对哪些请求生效,这里的 /test/** 表示对 test 路径下的所有方法均生效,还有其它的路径表示,如下:

/* : 表示一级路径,只能匹配 /test,不能匹配 /test/t1等;

/** : 表示任意级路径,能匹配 /test,/test/t1等;

/test/* : 表示 /test 下的一级路径,可以匹配 /test/t1,但不能匹配 /test/t1/t2等;

/test/** : 表示 /test 下的任意级路径,可以匹配 /test/t1,/test/t1/t2,但不能匹配 /user/login 等。

当我们配置完拦截器和注册完拦截器后,就可以开始运行代码,我们使用下面的代码来进行测试:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@RequestMapping("/t1")public void t1() {log.info("拦截器测试...");}
}

代码运行结果如下:

在这里我们可以看到,当访问被拦截的方法时,首先执行的是 preHandle,由于 postHandle 返回值为 true,就不会拦截用户请求,于是就会先执行 t1 方法,之后会顺序执行 postHandle 和 afterCompletion。

若 preHandle 返回值为 false,代码的运行结果如下:

从结果中可以看出,程序只执行了 preHandle,由于 t1 被拦截,于是 t1、postHandle、afterCompletion 就不会执行。

在上述的拦截路径中,我们将 /test 下的所有路径都给拦截了,但是有的请求我们不需要拦截,那么我们就可以使用 excludPathPatterns 去去除掉不需要拦截的路径,改动后的代码如下:

@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/test/**").excludePathPatterns("/test/t2");}
}@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@RequestMapping("/t1")public void t1() {log.info("t1拦截器测试...");}@RequestMapping("/t2")public void t2() {log.info("不拦截t2测试");}
}

TestController 中新增了 t2 方法,只拦截 t1,不拦截 t2,代码运行结果如下:

从图中可以看出,t1被拦截,但 t2 没有被拦截。

当我们知道如何使用拦截器之后,就可以在项目中进行运用。

当用户在进行访问时,就可以对其访问进行拦截,在拦截器中判断用户是否登录,若登录就放行,若没有登录就跳转到登录页面进行登录,代码如下:

package com.gjm.demo.interceptor;import com.gjm.demo.constant.Constants;
import com.gjm.demo.enums.ResultStatusEnums;
import com.gjm.demo.model.Result;
import com.gjm.demo.model.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;/*** 登录拦截器*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法前执行, 拦截器拦截到后,就会将response返回给前端,前端的error中就能就受到response* @param request* @param response* @param handler* @return true: 继续执行, false: 中断后续操作* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("登录之前执行");//用户未登录的处理if (!checkUserInfo(request.getSession())) {response.setContentType("text/html;character=utf-8");response.setStatus(401);String errorMessage = "用户未登录";response.getOutputStream().write(errorMessage.getBytes("UTF-8"));return false;}return true;}/*** 对检查用户信息进行封装* @param session* @return*/public boolean checkUserInfo(HttpSession session) {//用户未登录if (session == null || session.getAttribute(Constants.SESSION_USER_KEY) == null) {return false;}//获取用户信息UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);//用户信息不对if (userInfo == null || userInfo.getId() <= 0) {return false;}return true;}
}

 其中 check 的用处是检查用户是否登录。

二、统一数据返回格式

在进行项目开发时,我需要向前端返回一个统一的数据,这个数据是经过封装的,但是,在我们的接口中,有的返回的是 String,有的返回的是 boolean等,这就需要我们针对每一个接口都编写封装结果的代码,这样的代码就有点冗余了。于是,Spring 就封装了能够统一返回数据格式的方法。

现在有一个结果类 Result,对于接口中的每一种返回值,都需要将其封装为 Result 对象,实现的代码如下:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {//false为不处理,true为处理return true;}@SneakyThrows//异常处理@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String) {return objectMapper.writeValueAsString(Result.success(body));//将Result类型转化为String类型}//body已经是result类型,就不需要封装if (body instanceof Result) {return body;}return Result.success(body);}
}

该类需实现 ResponseBodyAdvice 接口,实现 support 和 beforeBodyWrite 方法,而且需要加上 @ControllerAdvice 注解。

support 的返回值为 boolean,返回 true 表示对各类型的返回值都进行处理,返回 false 表示不处理。

beforeBodyWrite 即具体的实现类。在这里需要强调,若接口中的返回值为 String,那么就需要对其进行单独处理,不然就会报错。

对于本来就是 Result 类型的对象,就不要进行处理,直接返回即可。

三、统一异常处理

在我们编写代码的时候,有时候可能判断的不准确,不知道哪里会出现异常,那么如果我们不进行处理,就会影响程序的运行。对于这种情况,Spring 为我们封装了一个能统一处理异常的方法,代码如下:

@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {@ExceptionHandlerpublic Result handler(Exception e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "内部错误,请联系管理员");}@ExceptionHandlerpublic Result handler(NullPointerException e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "空指针异常,请联系管理员");}@ExceptionHandlerpublic Result handler(IndexOutOfBoundsException e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "数组越界异常,请联系管理员");}}

ExceptionAdvice 需要使用 @ControllerAdvice 和 @ResponseBody 注解。

在各个方法上需要使用 @ExceptionHandler 注解。

在这个类中,一共处理了三个异常,也可以继续增加异常的种类。若代码中抛出了异常并且没有手动捕获,就会抛给 ExceptionAdvice 类,在这个类中寻找与之距离最近的异常,若没有,就会直接使用 Exception 代替。

上述的代码还有另一种写法,代码如下:

@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public Result handler1(Exception e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "内部错误,请联系管理员");}@ExceptionHandler(NullPointerException.class)public Result handler2(Exception e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "空指针异常,请联系管理员");}@ExceptionHandler(IndexOutOfBoundsException.class)public Result handler3(Exception e) {log.error("发生异常, e: ", e);return Result.fail(ResultStatusEnums.ERROR, "数组越界异常,请联系管理员");}}

在每个 @ExceptionHandler 注解中标明捕获的是什么类型的异常,这样在每个方法的参数中就能直接使用 Exception 代替。

http://www.xdnf.cn/news/581.html

相关文章:

  • 智谱开源新一代GLM模型,全面布局AI智能体生态
  • 墙面刷完乳胶漆之后就有裂缝,有根治的办法吗?
  • Java面向对象进阶
  • BEVDet: High-Performance Multi-Camera 3D Object Detection in Bird-Eye-View
  • 年化26.9%的稳健策略|polars重构因子计算引擎(python策略下载)
  • AI——神经网络以及TensorFlow使用
  • 《汽车理论》第四章作业MATLAB部分
  • 传统深度学习架构和Transformer结构的区别
  • 从0开始搭建一套工具函数库,发布npm,支持commonjs模块es模块和script引入使用
  • uniapp-商城-29-vuex 关于系统状态的管理
  • 嵌入式单片机开发问题:Undefined symbol _HAL_RCC_GPIOB_CLK_ENABLE
  • Matlab 基于模型参考自适应法和SVPWM的异步电机控制
  • Kubernetes(k8s)学习笔记(二)--k8s 集群安装
  • 机器学习(神经网络基础篇)——个人理解篇6(概念+代码)
  • 【实战中提升自己】内网安全部署之dot1x部署 本地与集成AD域的主流方式(附带MAC认证)
  • UE5的BumpOffset节点
  • C++选择排序原理及实现
  • Python带有else子句的循环语句
  • 动态内存管理
  • [dp20_完全背包] 介绍 | 零钱兑换
  • PSN港服跳过生日找回密码(需要英语对话,需要注册的id)
  • 超大文件处理——文件强制切割:突破存储传输限制,提升数据处理效能—星辰大文化术——未来之窗超算中心
  • 小样本学习和元学习
  • STM32学习笔记汇总
  • 图 - 最短路径算法 -- Dijkstra -- Bellman-Ford -- Floyd-Warshall
  • 每日OJ_牛客_最小差值_排序_C++_Java
  • 链表面试题
  • element-plus样式失效的原因总结
  • Linux 桌面环境 LXQt 2.2 发布
  • 放松大脑的方法