前言🍭 ❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️ Spring + Spring MVC + MyBatis_冷兮雪的博客-CSDN博客 本章是讲Spring Bo
❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️
本章是讲Spring Boot 统⼀功能处理模块,也是 aop 的实战环节,要实现的目标有以下 3 个:
用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。
@RestController@RequestMapping("/user")public class UserController { @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; } } @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; } } // 其他⽅法。。。}
从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:
说到统⼀的用户登录验证,我们想到的第⼀个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Componentpublic 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; }}
如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:
在切面类中拿到 HttpSession 比较难
对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
具体实现如下:
目录结构:
关键步骤:
a.实现 HandlerInterceptor 接口
b.重写 preHeadler 方法,在方法中编写自己的业务代码
package com.example.demo.config;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;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) { // 说明用户已经登录 return true; } // 可以调整到登录页面 or 返回一个 401/403 没有权限码 response.sendRedirect("/login.html");// response.setStatus(403); return false; }}
package com.example.demo.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorReGIStry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class AppConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("*.js") .excludePathPatterns("*.CSS") .excludePathPatterns("*.jpg") .excludePathPatterns("/login.html") .excludePathPatterns("login"); // 排除接⼝ }
UserController:
package com.example.demo.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;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("执行了 get User~"); return "get user"; } @RequestMapping("/login") public String login() { System.out.println("执行了 login~"); return "login~"; } @RequestMapping("/reg") public String reg() { System.out.println("执行了 reg~"); return "reg~"; }}
不拦截:
拦截:http://localhost:8080/user/getuser
为什么会显示重定向次数过多?
这是因为这个login.html页面也被拦截了,所以它去访问时候就会一直重定向重定向去访问。
解决方法:
正常情况下的调用顺序:
然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:
所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:
而所有方法都会执行 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); try { try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle,而applyPreHandle 方法的实现源码如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {// 获取项⽬中使⽤的拦截器 HandlerInterceptor 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 方法,这样就和咱们前面定义的拦截器对应上了,如下图所示:
此时用户登录权限的验证方法就会执行,这就是拦截器的实现原理。
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知, 也就是执行某个方法事件,具体实现代码如下:
package com.example.demo.config;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;@ControllerAdvice@ResponseBodypublic class MyExHandler { @ExceptionHandler(NullPointerException.class) public HashMap nullException(NullPointerException e) { HashMap result = new HashMap<>(); result.put("code", "-1"); result.put("msg", "空指针异常:" + e.getMessage()); // 错误码的描述信息 result.put("data", null); return result; } @ExceptionHandler(Exception.class)//保底的默认异常 public HashMap exception(Exception e) { HashMap result = new HashMap<>(); result.put("code", "-1"); result.put("msg", "异常:" + e.getMessage()); // 错误码的描述信息 result.put("data", null); return result; }}
UserController
@RequestMapping("/login") public String login() { Object obj = null; obj.hashCode(); System.out.println("执行了 login~"); return "login~"; } @RequestMapping("/reg") public String reg() { int num = 10 / 0; System.out.println("执行了 reg~"); return "reg~"; }
login:
reg:
方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
以上方法表示,如果出现了异常就返回给前端⼀个 HashMap 的对象,其中包含的字段如代码中定义的那样。 我们可以针对不同的异常,返回不同的结果。
创建一个类,并添加 @ControllerAdvice
2.实现ResponseBodyAdvice接口,并重写supports和beforeBodywrite (统一对象就是此方法中实现的)
统一数据返回格式的优点有很多,比如以下几个:
统⼀的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现。
package com.example.demo.config;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;import java.util.HashMap;@ControllerAdvicepublic class ResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; @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 result = new HashMap<>(); result.put("code", 200); result.put("msg", ""); result.put("data", body); // 需要特殊处理,因为 String 在转换的时候会报错 if (body instanceof String) { try { return objectMapper.writeValueAsString(result); } catch (JsonProcessingException e) { e.printStackTrace(); } } return result; }}
如果没有对Sting进行特殊处理,就会有异常出现:
UserController
@RequestMapping("/getnum") public Integer getNumber() { return new Random().nextInt(10); } @RequestMapping("/getuser") public String getUser() { System.out.println("执行了 get User~"); return "get user"; }
上面只是以一种简单的方式来向大家介绍如何去统一数据返回格式,在实际工作中并不会这样去使用
而是将统⼀的返回格式(包含:status、data、msg 字段)进行封装成一个类:
根据不同操作成功和操作失败的情况,重写了不同的方法:
package com.example.demo.common;import lombok.Data;import java.io.Serializable;@Datapublic class ajaxResult implements Serializable {//支持序列化 // 状态码 private Integer code; // 状态码描述信息 private String msg; // 返回的数据 private Object data; public static AjaxResult success(Object data) { AjaxResult result = new AjaxResult(); result.setCode(200); result.setMsg(""); result.setData(data); return result; } //重载success public static AjaxResult success(int code, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(""); result.setData(data); return result; } public static AjaxResult success(int code, String msg, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(data); return result; } public static AjaxResult fail(int code, String msg) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(null); return result; } public static AjaxResult fail(int code, String msg, Object data) { AjaxResult result = new AjaxResult(); result.setCode(code); result.setMsg(msg); result.setData(data); return result; }}
来源地址:https://blog.csdn.net/m0_63951142/article/details/132246523
--结束END--
本文标题: SpringBoot统一功能处理
本文链接: https://lsjlt.com/news/392791.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0