文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Boot 优雅捕捉异常的几种姿势!

2024-11-29 20:26

关注

其实在 Spring Boot 中,针对controller层的异常处理有很多种办法。今天通过这篇文章,我们就一起来总结一下相关异常处理的实现方式。

02、方案实践

在 Spring Boot 中针对controller层的异常处理,有两种常用实现方式,都可以达到简化代码逻辑的效果。

下面我们一起来看看具体实现。

2.1、全局异常处理方式一

通过@ControllerAdvice和@ExceptionHandler注解实现全局异常拦截,在之前的文章中我们有多次介绍过,它可以拦截controller层请求方法抛出的异常信息,同时外加@ ResponseBody注解,可以实现响应类型为json格式。

例如,现在有两种异常类型NullPointerException和Exception,分别对其进行捕捉,具体实现如下!

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public Object nullPointerExceptionHandler(HttpServletRequest request, NullPointerException e){
        LOGGER.error("发生空指针异常,请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage());
        return ResultMsg.fail(500, e.getMessage());
    }


    
    @ExceptionHandler(value = {Exception.class})
    @ResponseBody
    public Object exceptionHandler(HttpServletRequest request, Exception e){
        LOGGER.error("未知异常,请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage());
        return ResultMsg.fail(999, e.getMessage());
    }
}

测试代码,如下:

@RestController
public class HelloController {

    @GetMapping(value = "/add")
    public String hello(){
        if(1 ==1){
            throw new NullPointerException("空指针测试");
        }
        return "hello world";
    }

    @GetMapping(value = "/delete")
    public String delete(){
        if(1 ==1){
            throw new RuntimeException("其它测试");
        }
        return "hello world";
    }
}

启动服务后,在浏览器中请求http://localhost:8080/add,结果如下:

图片

请求http://localhost:8080/delete,结果如下:

图片

结果与预期一致。

2.1.1、自定义异常类实现

很多场景下,我们希望通过自定义异常类来返回相关错误信息,如何实现呢?

首先自定义一个异常类CustomerException。

public class CustomerException extends RuntimeException {

    private Integer code;

    public Integer getCode() {
        return code;
    }

    public CustomerException(String message) {
        super(message);
        this.code = 500;
    }

    public CustomerException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

然后,在全局异常处理器中增加相关的捕捉方法。


@ExceptionHandler(value = {CustomerException.class})
@ResponseBody
public Object customerExceptionHandler(HttpServletRequest request, CustomerException e){
    LOGGER.error("发生业务异常,请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage());
    return ResultMsg.fail(e.getCode(), e.getMessage());
}

测试代码,如下:

@GetMapping(value = "/update")
public String update(){
    if(1 ==1){
        throw new CustomerException(4003, "请求ID不能为空");
    }
    return "hello world";
}

启动服务后,在浏览器中请求http://localhost:8080/update,结果如下:

图片

结果与预期一致!

2.1.2、404 异常特殊处理

默认情况下,@ExceptionHandler注解无法捕捉到 404 异常,比如请求一个无效的地址,返回信息如下:

图片

如果想要捕捉到这种异常,可以在application.properties文件中添加如下配置来实现。

# 如果没有找到请求地址,抛异常
spring.mvc.throw-exception-if-no-handler-found=true
# 关闭默认的静态资源路径映射
spring.resources.add-mappings=false

启动服务后,再次发起地址请求,结果如下:

图片

对于前后端分离开发的情况,这种方式非常实用;但是如果前后端不分离的项目,比如访问项目/static目录下的静态资源,可能前端无法正常访问。

此时,我们可以手动添加资源映射,比如如下操作,前端就能正常访问静态资源了。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 允许访问localhost:8080/static/目录下的静态资源
        registry.addResourceHandler("/static
    @ExceptionHandler(value = {Exception.class})
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e){
        LOGGER.error("未知异常,请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage());
        ModelAndView mv = new ModelAndView();
        // 添加错误信息对象
        mv.addObject("message", e.getMessage());
        // 要跳转的页面视图
        mv.setViewName("error");
        return mv;
    }
}

启动服务后,在浏览器中再次请求http://localhost:8080/update,结果如下:

图片

结果与预期一致!

2.1.4、RestControllerAdvice和ControllerAdvice的区别

很多同学喜欢用@RestControllerAdvice来代替@ControllerAdvice和@ResponseBody。

例如如下示例!

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    
    @ExceptionHandler(value = {Exception.class})
    public Object exceptionHandler(HttpServletRequest request, Exception e){
        LOGGER.error("未知异常,请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage());
        return ResultMsg.fail(999, e.getMessage());
    }
}

实现效果,与上文等价。

打开@RestControllerAdvice的源码,你会发现它将@ControllerAdvice和@ResponseBody注解组合在一起了,因此同时具备两者效果,部分源码如下:

package org.springframework.web.bind.annotation;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class[] basePackageClasses() default {};

    Class[] assignableTypes() default {};

    Class[] annotations() default {};
}

2.2、全局异常处理方式二

在 Spring Boot 中,除了通过@ControllerAdvice和@ExceptionHandler注解实现全局异常处理外,还有一种通过实现HandlerExceptionResolver接口来完成全局异常的处理。

具体实现示例如下:

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionResolver.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        LOGGER.error("接口请求出现异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        if(e instanceof RuntimeException){
            // 设置响应类型为json格式
            ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
            mv.addObject("code", 500);
            mv.addObject("msg", e.getMessage());
            return mv;
        } else {
            // 设置响应类型为错误页面
            ModelAndView mv = new ModelAndView();
            mv.addObject("message", e.getMessage());
            mv.setViewName("error");
            return mv;
        }
    }
}

当出现异常的时候,结果会以json格式响应给客户端。

启动服务后,发起地址请求,结果如下:

这种思路的实现原理,主要是通过 SpringMVC 的异常处理链路器来完成异常的全局处理。

SpringMVC 支持用户自定义异常处理类(需要实现HandlerExceptionResolver),当发生异常时,默认异常处理类无法处理时,就会交给自定义异常处理类来完成。实现方面比较灵活,即可以实现以json格式响应,也可以以页面视图的方式响应。

虽然这种方式能够处理全局异常,但是 Spring 官方不推荐使用它;同时实测过程中发现它无法拦截 404 错误,当请求错误地址时,会优先被DefaultHandlerExceptionResolver默认异常处理类拦截,自定义的异常处理类无法捕捉。

03、小结

最后总结一下,虽然方式一和方式二都可以实现controller层接口请求异常的全局处理,但是在实际使用中,推荐方式一,简单好维护。

示例代码地址:

https://gitee.com/pzblogs/spring-boot-example-demo
来源:潘志的研发笔记内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯