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. ..................
相关推荐
P.H. Infinity10 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天14 分钟前
java的threadlocal为何内存泄漏
java
caridle25 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^31 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋334 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花39 分钟前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端42 分钟前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan1 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈1 小时前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫