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

Spring Boot 统一处理功能

目录

1.用户登陆权限验证

1.1 每个方法验证

1.2 Spring AOP 用户统一登陆验证

1.3 拦截器

1.3.1 自定义拦截器

1.3.2 将自定义拦截器配置到系统设置中,并且设置拦截规则

1.3.3 排除所有的静态资源

1.4 登录拦截器(练习)

1.5 拦截器原理

2.统一异常处理

3.统一数据返回格式

3.1 统一数据处理(强制执行)


统一处理的功能包括:

  • 统一用户登陆权限验证
  • 统一数据格式返回
  • 统一异常处理

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;}}
}

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

  • 每个方法都要单独写一个用户登录验证的方法
  • 添加的控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
  • 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍

1.2 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;}
}

缺点:

  • 无法获取 HttpSession 对象
  • 我们要对⼀部分方法机型拦截,⽽另⼀部分方法不拦截,如注册方法和登录⽅法是不拦截的,这样的话排除⽅法的规则很难定义,甚⾄没办法定义。 

1.3 拦截器

Spring 提供了具体的实现拦截器:HandlerInterceptor

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

1.3.1 自定义拦截器

用户登陆拦截器

  • 实现 HandlerInterceptor 接口 的拦截器
  • 实现 preHandle(执行具体方法之前的预处理)方法:执行目标方法之前判断是否有登录
  • 重新 preHandle 方法:返回 true(拦截器验证成功,继续执行后续的方法);返回 false(拦截器验证失败,不会执行后续目标方法)
  • 写业务方法:得到 Session 对象,判断 Session 中是否有登陆用户,有——true;无——false
package com.example.demo.config;
import com.example.demo.common.AppVar;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/*** 自定义拦截器*/
public class UserInterceptor implements HandlerInterceptor {/*** 返回 true:表示拦截器验证成功,继续执行后续的方法*     false:拦截器验证失败,不会执行后续目标方法* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//业务方法//得到 Session 对象:判断 Session 中是否有登陆用户,有——true;无——falseHttpSession session = request.getSession(false);//默认 trueif (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {//用户已经登陆return true;}return false;}
}

定义 Session Key 值:

package com.example.demo.common;
//全局变量
public class AppVar {//Session Keypublic static final String SESSION_KEY = "SESSION_KEY";
}

1.3.2 将自定义拦截器配置到系统设置中,并且设置拦截规则

配置文件 AppConfig:

  • 实现 WebMvcConfigurer,表明是一个系统的配置文件
  • 重写 addInterceptors 方法(支持添加多个拦截器)

添加多个拦截器注入有两种方法:

1️⃣  

registry.addInterceptor(new UserInterceptor());

2️⃣ UserInterceptor 类中添加 @Component 注解,随着 spring 启动而启动;通过 @Autowired 注入进去

@Configuration 注解是随着 spring 启动而启动,将当前配置设置到系统中

package com.example.demo.config;
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 {@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//registry.addInterceptor(new UserInterceptor());registry.addInterceptor(userInterceptor).addPathPatterns("/**")//拦截规则:/**拦截所有的请求.excludePathPatterns("/user/reg")//不拦截.excludePathPatterns("/user/login");}
}

拦截规则:

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

新建 Controller 类(一个拦截、两个不拦截):

package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getuser")public String getUser() {System.out.println("do getUser()");return "user";}@RequestMapping("/reg")public String reg() {System.out.println("do reg()");return "reg";}@RequestMapping("/login")public String login() {System.out.println("do login()");return "login";}
}

 在拦截器里边执行一个 do UserInterceptor:

@Component
public class UserInterceptor implements HandlerInterceptor {/*** 返回 true:表示拦截器验证成功,继续执行后续的方法*     false:拦截器验证失败,不会执行后续目标方法* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("do UserInterceptor");//业务方法//得到 Session 对象:判断 Session 中是否有登陆用户,有——true;无——falseHttpSession session = request.getSession(false);//默认 trueif (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {//用户已经登陆return true;}return false;}
}

运行启动类,首先我们先看不拦截的:访问 localhost:8080/user/reg

被拦截的:访问 localhost:8080/user/getuser

1.3.3 排除所有的静态资源

// 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") // 拦截所有接⼝.excludePathPatterns("/**/*.js").excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.jpg").excludePathPatterns("/login.html").excludePathPatterns("/**/login"); // 排除接⼝
}
  • /*:一级目录
  • /**:所有目录

在这里有一个问题就是排除图片,图片的格式有很多,总不能一个一个排除,这样的情况我们可以在 resource 下的 static 创建一个 image 目录,把所有图片都放在这里边,排除 image 里边的所有东西

 .excludePathPatterns("/image/**");

1.4 登录拦截器(练习)

1.登录、注册页面不拦截,其他页面拦截

2.当登录成功写入 session 之后,拦截的页面也可以正常访问

1.拦截功能

package com.example.demo.config;
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 {@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//registry.addInterceptor(new UserInterceptor());registry.addInterceptor(userInterceptor).addPathPatterns("/**")//拦截规则:/**拦截所有的请求.excludePathPatterns("/login.html").excludePathPatterns("/reg.html").excludePathPatterns("/css/**").excludePathPatterns("/editor/**").excludePathPatterns("/img/**").excludePathPatterns("/js/**").excludePathPatterns("/**/login"); // 排除接⼝;}
}

2.拦截器

package com.example.demo.config;
import com.example.demo.common.AppVar;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/*** 自定义拦截器*/
@Component
public class UserInterceptor implements HandlerInterceptor {/*** 返回 true:表示拦截器验证成功,继续执行后续的方法*     false:拦截器验证失败,不会执行后续目标方法* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("do UserInterceptor");//业务方法//得到 Session 对象:判断 Session 中是否有登陆用户,有——true;无——falseHttpSession session = request.getSession(false);//默认 trueif (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {//用户已经登陆return true;}return false;}
}

1.5 拦截器原理

所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 空制台的打印信息看出

而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,源码一部分:

//调用预处理【重点】
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;
}

执行所有的拦截器:所有拦截器不等于 false,才会进行之后的代码

// 执⾏ Controller 中的业务
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {return;
}

所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法(其中会执行 applyPreHandle 方法,所有拦截器不等于 false,才会执行 Controller 方法)

applyPreHandle 源码:

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}    

 执行一个目标方法的时候,循环所有的拦截器,拿到容器中的所有拦截器,再去执行拦截器的预执行方法(preHandle 方法),如果有一个 preHandle 方法为 false,就返回 false,就不执行后续流程,直接返回;返回true,执行 controller 方法

2.统一异常处理

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

默认返回的是一个页面(需要的是一个数据不是页面),这个时候需要加 @ResponseBody ,返回一个数据,而 @ResponseBody 既可以加到方法上也可以加在类上,所以可以直接使用注解 @RestControllerAdvice

 假设构建一个空指针异常(UserController 类):

package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/reg")public String reg() {System.out.println("do reg()");Object obj = null;System.out.println(obj.hashCode());return "reg";}
}

定义统一的返回对象:

package com.example.demo.common;
import lombok.Data;
//统一的对象
@Data
public class ResultAjax {private int code;//状态码private String msg;//状态码的描述信息private Object data;//返回数据
}

空指针异常处理:

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

package com.example.demo.config;
import com.example.demo.common.ResultAjax;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(NullPointerException.class)public ResultAjax doNullPointerException(NullPointerException e) {ResultAjax resultAjax = new ResultAjax();resultAjax.setCode(-1);resultAjax.setMsg("空指针异常:" + e.getMessage());resultAjax.setData(null);return resultAjax;}
}

访问 localhost:8080/user/reg

上述为空指针异常,那么异常有很多,我们难道全要写这些异常?

这个时候我们可以设置一个默认异常,所有异常都是基于 Exception

@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(Exception.class)public ResultAjax doException(Exception e) {ResultAjax resultAjax = new ResultAjax();resultAjax.setCode(-1);resultAjax.setMsg("异常:" + e.getMessage());resultAjax.setData(null);return resultAjax;}
}

3.统一数据返回格式

  • 方便前端程序员更好的接收和解析后端数据接口返回的数据
  • 降低前端程序员和后端程序员的沟通成本
  • 有利于项⽬统⼀数据的维护和修改
  • 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容

3.1 统一数据处理(强制执行)

  • 使用 @ControllerAdvice
  • 实现 ResponseBodyAdvice 接口,并重写它的两个方法,supports 必须返回 true,beforeBodyWrite 方法中进行重新判断和重写操作
package com.example.demo.config;
import com.example.demo.common.ResultAjax;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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;
/*** 听译返回值的保底实现类*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;//spring 自带的/*** true -> 才会调用 beforeBodyWrite 方法* 反之则永远不会调用 beforeBodyWrite 方法* @param returnType* @param converterType* @return*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {// 已经包装好的对象if (body instanceof ResultAjax) {return body;}//对字符串进行判断和处理if (body instanceof String) {ResultAjax resultAjax = ResultAjax.succ(body);try {return objectMapper.writeValueAsString(resultAjax);} catch (JsonProcessingException e) {e.printStackTrace();}}return ResultAjax.succ(body);}
}
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getnum")public int getNum() {return 1;}@RequestMapping("/getstr")public String getStr() {return "hello";}
}

String 比较特殊,既不属于对象,也不属于基本数据类型,在返回值的时候也是需要单独判断

http://www.lryc.cn/news/221321.html

相关文章:

  • 香港金融科技周VERTU CSO Sophie谈Web3.0的下一个风口 手机虚拟货币移动支付
  • 分布式单元化
  • wvp-gb28181-pro接入海康摄像头
  • 近视眼选择什么台灯好?专家推荐的防近视台灯
  • 数据标注工具【LabelImg】安装使用 用VOC制作自己的数据集
  • Zeus IoT : 基于 SpringBoot 的分布式开源物联网大数据平台
  • 面试—如何介绍项目中的多级缓存?
  • PyTorch入门学习(十七):完整的模型训练套路
  • 《 Hello 算法 》 - 免费开源的数据结构与算法入门教程电子书,包含大量动画、图解,通俗易懂
  • 数据库之事务
  • NOIP2023模拟12联测33 B. 游戏
  • 代码随想录打卡第六十三天|84.柱状图中最大的矩形
  • python tempfile 模块使用
  • 【软件测试】接口测试实战详解
  • 轻量封装WebGPU渲染系统示例<20>- 美化一下元胞自动机之生命游戏(源码)
  • Nodejs的安装以及配置(node-v12.16.1-x64.msi)
  • 03【保姆级】-GO语言变量和数据类型和相互转换
  • mermaid学习第一天/更改主题颜色和边框颜色/《需求解释流程图》
  • SAP MASS增加PR字段-删除标识
  • 【手把手教你】训练YOLOv8分割模型
  • 物料主数据增强屏幕绘制器DUMP
  • vue 实现在线预览Excel-LuckyExcel/LuckySheet实现方案
  • AIGPT重大升级,界面重新设计,功能更加饱满,用户体验升级
  • Web逆向-mtgsig1.2简单分析
  • 【蓝桥杯省赛真题41】Scratch电脑开关机 蓝桥杯少儿编程scratch图形化编程 蓝桥杯省赛真题讲解
  • 第10章 Java常用类
  • Android 11 getPackageManager().getPackageInfo 返回null
  • 4、数据结构
  • qt5.15.2+vs2019源码调试开发环境搭建
  • 【数据结构】单链表之--无头单向非循环链表