Spring中的@ControllerAdvice和@ExceptionHandler注解处理全局异常

 更新时间:2024年01月09日 09:23:19   作者:Java架构何哥  
这篇文章主要介绍了Spring中的@ControllerAdvice和@ExceptionHandler注解处理全局异常,@ControllerAdvice ,@ControllerAdvice是一个非常有用的注解,顾名思义,这是一个增强的 Controller,一般配合@ExceptionHandler使用来处理全局异常,需要的朋友可以参考下

前言

开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。

这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了。

一、@ControllerAdvice和@ExceptionHandler简介

在构建RestFul接口的今天,我们一般会限定好返回数据的格式,有利于前端调用解析,比如:

{
  "code": 0,
  "data": {},
  "msg": "操作成功"
}

但有时却往往会产生一些bug,这时候就破坏了返回数据的一致性,导致调用者无法解析。所以我们常常会定义一个全局的异常拦截器。

1.1、@ControllerAdvice

@ControllerAdvice 是Spring 3.2提供的新注解,可以对Controller中使用到@RequestMapping注解的方法做逻辑处理。

@ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上这是一个非常有用的注解。顾名思义,这是一个增强的 Controller,一般配合@ExceptionHandler使用来处理全局异常。注意不能自己try和catch异常,否则就不会被全局异常处理捕获到。

使用这个注解 ,可以实现三个方面的功能:

  • 全局异常处理
  • 全局数据绑定
  • 全局数据预处理

灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用,这里只介绍全局异常处理,需要其他功能可以访问参考链接。

1.2、全局异常处理

Springboot对于全局异常的处理做了不错的支持,它提供了两个可用的注解。

@ControllerAdvice:用来开启全局的异常捕获

@ExceptionHandler:说明捕获哪些异常,对哪些异常进行处理。

使用 @ControllerAdvice结合@ExceptionHandler 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:

//可以使Spring自动把要返回的对象转化成json文本写入到响应体中,比如自定义的ResultBean
@ResponseBody
@ControllerAdvice
public class MyGlobalExceptionHandler 
{
    // 专门用来捕获和处理Controller层的异常
    @ExceptionHandler(Exception.class)
    public ModelAndView customException(Exception e) 
    {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");
        return mv;
    }
    // 专门用来捕获和处理Controller层的空指针异常
    @ExceptionHandler(NullPointerException.class)
    public ModelAndView nullPointerExceptionHandler(NullPointerException e)
    {
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了空指针异常,请稍后再试");
        return mv;
   }
}

在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法...,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。

@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。

二、为什么要做Controller层的异常统一处理以及统一结果返回

不知道你平时在写Controller层接口的时候,有没有注意过抛出异常该怎么处理,是否第一反应是想着用个try-catch来捕获异常?但是这样地处理只适合那种编译器主动提示的检查时异常,因为你不用try-catch就过不了编译检查,所以你能主动地抓获异常并进行处理。但是,如果存在运行时异常且你没有来得及想到去处理它的时候会发生什么呢?我们可以来先看看下面的这个没有处理运行时异常的例子:

@RestController
public class ExceptionRest {
    @GetMapping("getNullPointerException")
    public Map<String,Object> getNullPointerException(){
        throw new NullPointerException("出现了空指针异常");
    }
}

以上代码在基于maven的SpringMVC项目中,使用tomcat启动后,浏览器端发起如下请求:

//localhost:8080/zxtest/getNullPointerException

访问后得到的结果是这样的,浏览器收到的报错信息:

 可以看到,我们在Controller接口层抛出了一个空指针异常,然后没有捕获,结果异常堆栈就会返回给前端浏览器,给用户造成了非常不好的体验。

除此之外,前端从报错信息中能看到后台系统使用的服务器及中间件类型、所采用的框架信息及类信息,甚至如果后端抛出的是SQL异常,那么还可以看到SQL异常的具体查询的参数信息,这是一个中危安全漏洞,是必须要修复的。

三、使用@ExceptionHandler和@ControllerAdvice做到统一处理

当出现这种运行时异常的时候,我们想到的最简单的方法也许就是给可能会抛出异常的代码加上异常处理,如下所示:

@RestController
public class ExceptionRest {
    private Logger log = LoggerFactory.getLogger(ExceptionRest.class);
    @GetMapping("getNullPointerException")
    public Map<String,Object> getNullPointerException(){
        Map<String,Object> returnMap = new HashMap<String,Object>();
        try{
            throw new NullPointerException("出现了空指针异常");
        }catch(NullPointerException e){
            log.error("出现了空指针异常",e);
            returnMap.put("success",false);
            returnMap.put("mesg","请求发生异常,请稍后再试");
        }
        return returnMap;
    }
}

因为我们手动地在抛出异常的地方加上了处理,并妥善地返回发生异常时该返回给前端的内容,因此,当我们再次在浏览器发起相同的请求时得到就是以下内容:

{
  success: false,
  mesg: "请求发生异常,请稍后再试"
}

貌似问题得到了解决,但是你能确保你可以在所有可能会发生异常的地方都正好捕获了异常并处理吗?你能确保团队的其他人也这么做?

很明显,你需要一个统一的异常捕获与处理方案。

Spring3.2以后,SpringMVC引入了ExceptionHandler的处理方法,使得对异常的处理变得更加简单和精确,你唯一需要做的就是新建一个Controller,然后再里面加上两个注解即可完成Controller层所有异常的捕获与处理。

3.1、@ExceptionHandler和@ControllerAdvice基本使用

新建一个Controller如下:

@ControllerAdvice
public class ExceptionConfigController {
    @ExceptionHandler
    public ModelAndView exceptionHandler(Exception e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了异常,请稍后再试");
        return mv;
    }
}

我们在如上的代码中,类上加了@ControllerAdvice注解,表示它是一个增强版的controller,然后在里面创建了一个返回ModelAndView对象的exceptionHandler方法,其上加上@ExceptionHandler注解,表示这是一个异常处理方法,然后在方法里面写上具体的异常处理及返回参数逻辑即可,如此就完成了所有的工作,真的是太方便了。

我们在浏览器发起调用后就返回了如下的结果:

{
success: false,
mesg: "请求发生了异常,请稍后再试"
}

3.2、@ExceptionHandler具体异常的处理

相比与HandlerExceptionResolver而言,使用@ExceptionHandler更能灵活地对不同的异常进行分别的处理。并且,当抛出的异常是指定异常的子类,那么照样能够被捕获和处理。

我们改变下controller层的代码如下:

@RestController
public class ExceptionController {
 
    @GetMapping("getNullPointerException")
    public Map<String, Object> getNullPointerException() {
        throw new NullPointerException("出现了空指针异常");
    }
 
    @GetMapping("getClassCastException")
    public Map<String, Object> getClassCastException() {
        throw new ClassCastException("出现了类型转换异常");
    }
 
    @GetMapping("getIOException")
    public Map<String, Object> getIOException() throws IOException {
        throw new IOException("出现了IO异常");
    }
}

已知NullPointerException和ClassCastException都继承RuntimeException,而RuntimeException和IOException都继承Exception。

我们在ExceptionConfigController做这样的处理:

@ControllerAdvice
public class ExceptionConfigController 
{
    // 专门用来捕获和处理Controller层的空指针异常
    @ExceptionHandler(NullPointerException.class)
    public ModelAndView nullPointerExceptionHandler(NullPointerException e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了空指针异常,请稍后再试");
        return mv;
    }
 
    // 专门用来捕获和处理Controller层的运行时异常
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView runtimeExceptionHandler(RuntimeException e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了运行时异常,请稍后再试");
        return mv;
    }
 
    // 专门用来捕获和处理Controller层的异常
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Exception e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了异常,请稍后再试");
        return mv;
    }
}

当我们在Controller层抛出NullPointerException时,就会被nullPointerExceptionHandler进行处理,然后拦截。

{
success: false,
mesg: "请求发生了空指针异常,请稍后再试"
}

当我们在Controller层抛出ClassCastException时,就会被runtimeExceptionHandler进行处理,然后拦截。

{
success: false,
mesg: "请求发生了运行时异常,请稍后再试"
}

当我们在Controller层抛出IOException时,就会被exceptionHandler进行处理,然后拦截。

{
success: false,
mesg: "请求发生了异常,请稍后再试"
}

SpringMVC为我们提供的Controller层异常处理真的是太方便了,尤其是@ExceptionHandler,推荐大家使用。

四、自定义异常处理类CustomUserException

4.1、自定义异常处理类

在程序中,可能会遇到JDK提供的任何标准异常类都无法充分描述清楚我们想要表达的问题,又或者捕捉数据库异常时候不知道具体异常名字,这种情况下可以创建自己的异常类,即自定义异常类,在需要的时候手动throw出,然后再全局异常统一处理。

自定义异常类只需从Exception类或者它的子类派生一个子类即可。自定义异常类如果继承Exception类,则为受检查异常,必须对其进行处理;如果不想处理,可以让自定义异常类继承运行时异常RuntimeException类。习惯上,自定义异常类应该包含2个构造器:一个是默认的构造器,另一个是带有详细信息的构造器。

/**
*   自定义异常的步骤:
*  (1)继承 Exception 或 RuntimeException
*  (2)定义构造方法
*/
public class CustomUserException extends RuntimeException {
    private Integer code;
    public CustomUserException(UserResponseEnum userResponseEnum){
        super(userResponseEnum.getDescription());
        this.code = userResponseEnum.getCode();
    }
    public Integer getCode() {
        return code;
    }
}

在构造自定义业务异常对象时使用了枚举的方式,将常见的业务错误提示语对应的错误代码进行映射,枚举类如下所示:

public enum UserResponseEnum {
    USER_NOT_FOUND(50001,"用户不存在"),
    USER_AUTHENTICATION_ERROR(50002,"用户密码不正确");
    private Integer code;
    private String description;
    UserResponseEnum(Integer code, String description) {
        this.code = code;
        this.description = description;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

4.2、通过@ControllerAdvice和@ExceptionHandler注解,实现统一异常捕获

@RestController
@ControllerAdvice(basePackages = {"com.hs.controller"})
public class CustomExceptionAdvice 
{
    private static final Logger logger = LoggerFactory.getLogger(CustomExceptionAdvice.class);
    /**
     * 处理与用户相关的业务异常
     * @return
     */
    @ExceptionHandler(CustomUserException.class)
    public BaseResult UserExceptionHandler(HttpServletRequest request,CustomUserException e){
        logger.error("用户信息异常:Host:{} invoke URL:{},错误信息:{}",request.getRemoteHost(),request.getRequestURL(),e.getMessage());
        return new BaseResult(e.getCode(),false,e.getMessage());
    }
}
 

4.3、在业务代码中在需要抛出异常的地方抛出对应的异常即可

	/**
     * 根据主键获取用户实体
     * @param id
     * @return
     */
    public User selectById(String id) 
    {
        User user = userMapper.selectByPrimaryId(id);
        if (user == null) {
            throw new CustomUserException(UserResponseEnum.USER_NOT_FOUND);
        }
        return user;
    }
 

    返回信息如下:
    {
        "code": 50001,
        "data": false,
        "message": "用户不存在"
    }

前端即可根据返回信息对用户进行友好的提示。

到此这篇关于Spring中的@ControllerAdvice和@ExceptionHandler注解处理全局异常的文章就介绍到这了,更多相关@ControllerAdvice和@ExceptionHandler注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入Java Robot实现控制鼠标和键盘的方法详解

    深入Java Robot实现控制鼠标和键盘的方法详解

    本篇文章是对Java中用Robot实现控制鼠标和键盘的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • Java RabbitMQ的工作队列与消息应答详解

    Java RabbitMQ的工作队列与消息应答详解

    这篇文章主要为大家详细介绍了Python实现学生成绩管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Java模拟死锁发生之演绎哲学家进餐问题案例详解

    Java模拟死锁发生之演绎哲学家进餐问题案例详解

    这篇文章主要介绍了Java模拟死锁发生之演绎哲学家进餐问题,结合具体演绎哲学家进餐问题的案例形式详细分析了死锁机制与原理,需要的朋友可以参考下
    2019-10-10
  • Spring Cloud 专题之Sleuth 服务跟踪实现方法

    Spring Cloud 专题之Sleuth 服务跟踪实现方法

    这篇文章主要介绍了Spring Cloud 专题之Sleuth 服务跟踪,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • Spring中@Autowired自动注入map详解

    Spring中@Autowired自动注入map详解

    这篇文章主要介绍了Spring中@Autowired自动注入map详解,  spring是支持基于接口实现类的直接注入的,支持注入map,list等集合中,不用做其他的配置,直接注入,需要的朋友可以参考下
    2023-10-10
  • Java中的notyfy()和notifyAll()的本质区别

    Java中的notyfy()和notifyAll()的本质区别

    很多朋友对java中的notyfy()和notifyAll()的本质区别不了解,今天小编抽空给大家整理一篇教程关于Java中的notyfy()和notifyAll()的本质区别,需要的朋友参考下吧
    2017-02-02
  • Java基于NIO实现群聊系统

    Java基于NIO实现群聊系统

    这篇文章主要为大家详细介绍了Java基于NIO实现群聊系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Java检测网络是否正常通讯

    Java检测网络是否正常通讯

    在网络应用程序中,检测IP地址和端口是否通常是必要的,本文主要介绍了Java检测网络是否正常通讯,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11
  • java编程实现两个大数相加代码示例

    java编程实现两个大数相加代码示例

    这篇文章主要介绍了java编程实现两个大数相加代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-12-12
  • 基于java的opencv开发过程详解

    基于java的opencv开发过程详解

    这篇文章主要介绍了基于java的opencv开发过程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04

最新评论