Spring Boot统一异常处理 && Spring拦截器

小编在前文中向大家描述了Spring AOP的相关内容:Spring AOP-CSDN博客感兴趣的各位老铁可查看一下!!

那么,我们本文主要是代理搭建来实现一个Spring Boot统一功能处理模块了,当然,这个也是Spring AOP的实战环节,因此,不知道Spring AOP是啥的铁汁,请先看一下前篇博客Spring AOP-CSDN博客做一个简单了解在来研究本文内容!!

本文要实现的目标大概有3个:

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

用户登录权限校验:

用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统一的用户登录验证处理,它是一个逐渐完善和逐渐优化的过程。

最初的用户登录验证:

复制代码
@RestController
@RequestMapping("/user")
public class User1Controller {

    /**
     * 某方法1
     * @param request
     * @return
     */
    @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;
        }
    }

    /**
     * 某方法2
     * @param request
     * @return
     */
    @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;
        }
    }

    /**
     * 其他方法
     */
}

从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:

  1. 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。
  2. 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。

所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫

Spring AOP用户统一登录验证的问题:

说到统一的用户登录验证,我们想到的第一个方案是Spring AOP前置通知或者环绕通知来实现,具体的实现代码如下:

复制代码
@Aspect
@Component
public class User1Aspect {

    //定义切点方法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 e) {
            e.printStackTrace();
        }
        System.out.println("Around方法执行结束!");
        return obj;
    }


}

如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:

  1. 没办法获取到 HttpSession 对象。
  2. 我们要对⼀部分⽅法进⾏拦截,⽽另⼀部分⽅法不拦截,如注册⽅法和登录⽅法是不拦截的,这样的话排除⽅法的规则很难定义,甚⾄没办法定义。

那么,这个问题该如何解决呢??

Spring拦截器:

对于以上问题Spring中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

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

那么有了上述的两个步骤,我们便可以自定义拦截器了!

1.自定义拦截器:

接下来使用代码来实现一个用户登录的权限校验,自定义拦截器是一个普通类,具体的实现代码如下:

复制代码
@Component //注入Spring框架中
public class loginInterceptor implements HandlerInterceptor {

    //调用目标方法之前执行的方法
    //此方法返回Boolean类型的只,若返回true,表示(拦截器)验证成功,继续走后续流程,执行目标方法
    //                       若返回false,表示(拦截器)执行失败,后续流程和目标方法都不要在执行了

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        //用户登录判断业务
        HttpSession session=request.getSession(false);
        if (session != null && session.getAttribute("session_userinfo") !=null){
            //getAttribute用户的身份信息
            //用户已经登录
            return true;
        }
       // response.sendRedirect("https://www.baidu.com");
        //sendRedirect跳转
        //response.setStatus(401);//401没有权限
        response.setContentType("application/json'charset=utf8");//指定字符集为utf8
       // response.setCharacterEncoding("utf8");
        response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");
        //getWriter()拿到输出流
        //code状态码
        //msg报错信息
        //另一种方式:使用ObjectMapper给这个对象里面设置属性,然后再将JSON对象转化为字符串就OK🆗了!
        return false;

    }
}

2.将自定义拦截器加入到系统配置:

将上一步中的自定义拦截器加入到系统配置信息中,具体实现代码如下:

复制代码
@Configuration
public class WebMvcConfigurer implements org.springframework.web.servlet.config.annotation.WebMvcConfigurer {
    @Autowired
    private loginInterceptor loginInterceptor;
    //自定义拦截器

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(loginInterceptor) //将自定义的拦截器添加到系统配置项中
                .addPathPatterns("/**") //拦截所有Url
                .excludePathPatterns("user/login")//排除url/user/login不拦截
                .excludePathPatterns("user/reg")
                .excludePathPatterns("/image/**");//排除image文件夹下所有文件
    }
}

其中:

复制代码
addInterceptor:表示需要拦截的URL,“**”表示拦截任意方法(也就是所有方法)
复制代码
excludePathPatterns:表示需要排除的URL

说明:以上的拦截规则可以拦截此次昂木中的使用的URL,包括静态文件(图片文件,JS和CSS等文件)

练习:

  1. 登录,注册页面不拦截,其他页面都拦截
  2. 当登录成功写入session之后,拦截的页面可正常访问

统一异常处理:

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

  1. 创建一个异常处理类:

    复制代码
    @ControllerAdvice //作用:随着Spring的启动而启动,接收Controller中的异常
    public class MyExceptionAdvice {
        
    }
  2. 创建异常检测的类和处理业务方法

    复制代码
    @ControllerAdvice //作用:随着Spring的启动而启动,接收Controller中的异常
    @ResponseBody
    public class MyExceptionAdvice {
        //处理空指针异常
        @ExceptionHandler(NullPointerException.class)
        //   @ExceptionHandler异常管理器    NullPointerException.class空指针异常
        public HashMap<String ,Object> doNullPointExcepyion(NullPointerException e){  //企业中不允许这样写
            //e:异常种类
            HashMap<String,Object> result=new HashMap<>();
            result.put("code",-300);//状态码
            result.put("msg","空指针:"+e.getMessage());//状态描述信息
            result.put("data",null);//异常的详细信息
            return result;
        }
        
        //默认的异常处理(当具体的异常匹配不到时,会执行此方法
        @ExceptionHandler(Exception.class)
        //Exception.class所有异常的父类
        public HashMap<String,Object> doException(Exception e){
            HashMap<String,Object> result=new HashMap<>();
            result.put("code",-300);
            result.put("msg","Exception:"+e.getMessage());
            //e.getMessage()异常的详细信息
            result.put("data",null);
            return result;
        }
    }
  3. 统一数据返回格式的实现(强制性统一数据返回):在返回数据之前进行数据重写 统一数据返回格式可以用@ControllerAdvice + ResponseBodyAdvice的方式实现

    复制代码
    @ControllerAdvice
    public class ResponseAdvice implements ResponseBodyAdvice {
        @Autowired
        private ObjectMapper objectMapper;
    
        //是否执行beforeBobyWrite方法;true=执行,重写返回结果
        @Override
        public boolean supports(MethodParameter returnType,Class converterType){
            return true;
        }
    
        //返回数据之前进行数据重写
        @Override
        @SneakyThrows
        public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                      MediaType selectedContentType, Class selectedConverterType,
                                      ServerHttpRequest request, ServerHttpResponse response) {
            //规定:标准返回格式
            //HashMap<String,Object>-->code msg  data
            //判断是否为统一的数据格式
            if (body instanceof HashMap) {
                //判断body的类型是否为HashMap
                return body;
            }
            //重写返回结果,让其返回一个统一的数据格式
            HashMap<String,Object> result=new HashMap<>();
            result.put("code",200);
            result.put("data",body);
            result.put("msg"," ");//状态码的描述
    
            if (body instanceof String){
                //返回一个将对象转化为JSON String字符串
                return objectMapper.writeValueAsString(result);
                //一样的写法
                //return "{\"code\":200,\"msg\":\",\"data\":\""+body+"\"}";
                //在统一数据重写时,单独处理String类型,让其返回一个Spring字符串,而非HashMap
            }
    
            return result;
        }
    }

综合练习:用户登录+拦截器

  1. 实现注册和登录功能
  2. 添加统一的错误处理(@ControllerAdvice)
  3. 添加登录拦截器( WebMvcConfigurer + HandlerInterceptor)
  4. 用户登录之后能到欢迎页面,未登录直接跳到登录页面
  5. 添加统一的返回格式(包含:status,data,msg等字段)

为什么需要统一数据返回格式??

  1. 方便前端程序员更好的接收和解析后端接口返回的数据
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就OK🆗了,因为所有接口都是这样返回的
  3. 有利于项目统一数据的维护和修改
  4. 有利于后端技术部门的统一规范的标准的指定,不会出现稀奇古怪的内容
  5. ..................
相关推荐
BillKu1 小时前
Java + Spring Boot + Mybatis 插入数据后,获取自增 id 的方法
java·tomcat·mybatis
全栈凯哥1 小时前
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
java·算法·leetcode·链表
chxii1 小时前
12.7Swing控件6 JList
java
全栈凯哥1 小时前
Java详解LeetCode 热题 100(27):LeetCode 21. 合并两个有序链表(Merge Two Sorted Lists)详解
java·算法·leetcode·链表
YuTaoShao1 小时前
Java八股文——集合「List篇」
java·开发语言·list
PypYCCcccCc1 小时前
支付系统架构图
java·网络·金融·系统架构
华科云商xiao徐1 小时前
Java HttpClient实现简单网络爬虫
java·爬虫
扎瓦1 小时前
ThreadLocal 线程变量
java·后端
BillKu2 小时前
Java后端检查空条件查询
java·开发语言
jackson凌2 小时前
【Java学习笔记】String类(重点)
java·笔记·学习