目录
接下来就是Spring Boot中的统一功能处理模块了,也就是AOP的实战环节,这节中我们主要学习的有以下三点:1.统一用户登录权限验证。2.统一数据格式返回。3.统一异常处理。话不多说我们一起来了解吧!
用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在的统一的用户登录验证处理,它是一个逐渐完成和逐渐优化的过程。
最初我们实现用户登录功能的时候会有很多缺点,在每个方法中都有相同的用户登录验证权限:
在我们之前学习过Spring AOP之后,如果再次提到要实现统一用户登录验证的时候,我们的第一想法应该就是Spring AOP的前置通知或通过环绕通知来实现。
但是使用我们之前的的方法来实现的话,会发现存在两个问题:
所以这针对于上述的问题我们就提出一个新的解决方案使用Spring中的拦截器。
对于上述的问题Spring中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两步:
具体的实现如下所示:
代码展示:
config层代码:
- 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
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- System.out.println("do UserInterceptor");
- //业务方法
- HttpSession session = request.getSession(false);
- if (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {
- //用户已经登录
- return true;//继续执行后续的流程
- }
- response.sendRedirect("https://www.baidu.com");
- return false;
- }
- }
common代码:
- package com.example.demo.common;
-
- /**
- * 全局变量
- */
- public class AppVar {
- //Session Key
- public static final String SESSION_KEY = "SESSION_KEY";
- }
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("/reg")
- public String reg() {
- System.out.println("do reg()");
- Object obj = null;
- System.out.println(obj.hashCode());
- return "reg";
- }
-
- @RequestMapping("/login")
- public String login() {
- System.out.println("do login()");
- return "login";
- }
- }
结果展示:
如果已经登录:
如果没有登录:
这里我们使用以下的方式来模拟没有登录的场景:
上述自定好了一个拦截器之后接下来我们就需要将自定义的拦截器设置到当前项目的配置文件中,并设置拦截的规则。
- package com.example.demo.config;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- public class AppConfig implements WebMvcConfigurer {
- @Autowired
- private UserInterceptor userInterceptor;
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(userInterceptor)
- .addPathPatterns("/**") //拦截所有的请求
- .excludePathPatterns("user/reg") //除去拦截reg注册请求
- .excludePathPatterns("user/login"); //除去拦截login登录请求
- }
- }
接下来我们来实现一下面的这个登录拦截器的练习题:
首先我们先来导入一个博客系统的静态页面:
大家可以从我的Gitee中进行下载☞博客系统静态页面: 博客系统的静态页面
导入静态页面之后我们就可以开始编写拦截器的规则了。
- 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;
-
- import javax.annotation.Resource;
-
- @Configuration
- public class AppConfig implements WebMvcConfigurer {
- @Resource
- private UserInterceptor userInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(userInterceptor)
- .addPathPatterns("/**") //拦截所有的请求
- .excludePathPatterns("/user/reg")
- .excludePathPatterns("/reg.html")
- .excludePathPatterns("/login.html")
- .excludePathPatterns("/css/**")
- .excludePathPatterns("/editor.md/**")
- .excludePathPatterns("/img/**")
- .excludePathPatterns("/js/**")
- .excludePathPatterns("/user/login");
- }
- }
接下来就可以正常访问页面了。
正常情况下的调用顺序:
然而有了拦截器之后,会在调用Controller之前进行相应的业务处理,执行的流程如下所示:
首先所有的Controller执行都会通过一个调度器DispathcherServlet来实现,这一点可以在Spring Boot的控制台打印信息中看出来。
而所有的方法都会执行 DispathcherServlet 中的doDispatch调度方法,通过分析doDispatch源码我们可以看出在开始执行Controller之前,会先调用预处理方法applyPreHandle,在applyPreHandle中会获取所有的拦截器HandleInterceptor并执行拦截器中的preHandle方法,这样就和我们之前定义的拦截器可以对应上了。这就是拦截器的实现原理。
统一异常处理使用的是@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
代码如下所示:
- package com.example.demo.common;
-
- import lombok.Data;
-
- /**
- * 统一对象
- */
- @Data
- public class ResultAjax {
- private int code; // 状态码
- private String msg; // 状态码的描述信息
- private Object data; // 返回数据
-
- /**
- * 返回成功对象
- * @param data
- * @return
- */
- public static ResultAjax succ(Object data) {
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg("");
- resultAjax.setData(data);
- return resultAjax;
- }
-
- public static ResultAjax succ(String msg, Object data) {
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg(msg);
- resultAjax.setData(data);
- return resultAjax;
- }
-
- /**
- * 返回失败对象
- * @param code
- * @param msg
- * @return
- */
- public static ResultAjax fail(int code,String msg){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(code);
- resultAjax.setMsg(msg);
- resultAjax.setData(null);
- return resultAjax;
- }
-
- public static ResultAjax fail(int code,String msg,Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(code);
- resultAjax.setMsg(msg);
- resultAjax.setData(data);
- return resultAjax;
- }
-
- }
- 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 //统一返回的对象
- public ResultAjax doNullPointerException(NullPointerException e) {
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(-1);
- resultAjax.setMsg("空指针异常:" + e.getMessage());
- resultAjax.setData(null);
- return resultAjax;
- }
- }
这里需要将Controller里面的返回值进行修改。
结果如下所示:
这样当出现异常的时候前端拿到的就不是之前报一大堆的错误了,就是可以直接在页面中显示出错误的信息了。
当然上述过程中我们只是处理了空指针异常,那么异常那么多难道要将没一种情况都写进去吗?
当然不是我们可以使用Exception来进行异常处理。如下代码所示:
为什么我们需要统一数据的返回格式呢?统一数据的返回格式优点有以下几点:
统一的数据返回格式可以使用@ControllerAdvice + ResponseBodyAdvice的方式来实现,具体实现代码如下所示:
- 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.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 javax.annotation.Resource;
-
- @ControllerAdvice
- public class ResponseAdvice implements ResponseBodyAdvice {
- @Resource
- private ObjectMapper objectMapper;
-
- /**
- * true -> 才会调用 beforeBodyWrite 方法,
- * 反之则永远不会调用 beforeBodyWrite 方法
- *
- * @param returnType
- * @param converterType
- * @return
- */
- @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) {
- //已经包装好的对象
- //instanceof - 判断类型
- 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);
- }
- }
好了这节小编就给大分享到这里啦,希望这节对大家有关于Spring中的统一处理的基础知识的了解有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)