Spring Boot 统一处理功能

编程入门 行业动态 更新时间:2024-10-25 06:23:14

Spring Boot 统一处理<a href=https://www.elefans.com/category/jswz/34/1771378.html style=功能"/>

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.demomon.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.demomon;
//全局变量
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.demomon.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.demomon;
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.demomon.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.demomon.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 比较特殊,既不属于对象,也不属于基本数据类型,在返回值的时候也是需要单独判断

更多推荐

Spring Boot 统一处理功能

本文发布于:2023-11-16 02:28:46,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1611734.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:功能   Spring   Boot

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!