Spring、SpringBoot统一异常处理的3种方法

2022-10-10 12:29:16

Spring 统一异常处理有 3 种方式,分别为:
使用 @ExceptionHandler 注解
实现 HandlerExceptionResolver 接口
使用 @ControllerAdvice注解

官方推荐的是使用@ExceptionHandler注解去捕获固定的异常
使用统一异常处理,将这些重复的try-catch块抽取出来,这样使我们可以更专注于业务逻辑的处理,同时能够使得异常的处理有一个统一的控制。这里总结了网上常用的三种方式,详细代码演示效果分别介绍如下。

一、HandlerExceptionResolver全局异常处理

使用全局异常处理器只需要两步:
1.实现HandlerExceptionResolver接口。
2.将实现类作为Spring Bean,这样Spring就能扫描到它并作为全局异常处理器加载。
实例如下:
配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><beanid="exceptionResolver"class="com.example.ExceptionResolver"/></beans>

编写ExceptionResolver

packagecom.example;/**
 * 全局异常处理
 * @Order(-1000) 为了使优先级最高
 * @Component 把普通pojo实例化到spring容器中,
 * 相当于配置文件中的 <bean id="" class=""/>
 */@Order(-1000)@ComponentpublicclassExceptionResolverimplementsHandlerExceptionResolver{privatestaticLogger logger=LoggerFactory.getLogger(ExceptionResolver.class);@OverridepublicModelAndViewresolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){ResultVO result=newResultVO();StringBuilder sb=newStringBuilder();System.out.println("执行全局异常处理-----------------------");//处理异常if(exinstanceofBussinessException){resolverBussinessException(ex, sb, result);}elseif(exinstanceofBindException){resolverBindException(ex, sb, result);}else{resolverOtherException(ex, sb, result);}

        result.setCode(0);
        result.setResult(sb);
        result.setTime(newDate());

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control","no-cache, must-revalidate");try{
            response.getWriter().write(JSON.toJSONString(result));}catch(IOException e){
            logger.error("异常:"+ e.getMessage(), e);
            e.printStackTrace();}

        logger.debug("异常:"+ ex.getMessage(), ex);
        ex.printStackTrace();returnnewModelAndView();}/*
     * 处理业务层异常
     */privatevoidresolverBussinessException(Exception ex,StringBuilder sb,ResultVO result){BussinessException businessException=(BussinessException) ex;
        sb.append(businessException.getMsg());addResult(result,"业务异常");}/*
     * 处理参数绑定异常
     */privatevoidresolverBindException(Exception ex,StringBuilder sb,ResultVO result){BindException be=(BindException) ex;List<FieldError> errorList= be.getBindingResult().getFieldErrors();for(FieldError error: errorList){
            sb.append(error.getObjectName());
            sb.append("对象的");
            sb.append(error.getField());
            sb.append("字段");
            sb.append(error.getDefaultMessage());}addResult(result,"参数传递异常");}/*
     * 处理其他异常
     */privatevoidresolverOtherException(Exception ex,StringBuilder sb,ResultVO result){
        sb.append(ex.getMessage());addResult(result,"其他异常");}/*
     * 封装code和msg
     */privatevoidaddResult(ResultVO result,String msg){
        result.setMsg(msg);}}

编写HelloWorld

@RequestMapping("/error")publicStringerror(){int a=11/0;return"11 / 0 = "+ a;}@GetMapping(value="testAdvice")publicStringtestAdvice(@ModelAttribute("user")String user,Date date)throwsException{thrownewException("模拟测试直接抛出异常");}

输入http://localhost:8080/StudySpring/testAdvice,结果如下:
在这里插入图片描述
输入

二、使用 @ExceptionHandler 注解对Controller局部异常处理

@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,比如这里添加了Exception参数。

packagecom.example.controller;@RestControllerpublicclassTestControllerException{//单个controller进行异常处理@RequestMapping("/testError")publicStringtestError(){int a=10/0;return"this is testError"+ a;}/**
     * 处理其他异常
     */@ExceptionHandler(Exception.class)@ResponseBodypublicStringexceptionHandler(Exception e){System.out.println(e);return"this is a controller exception method!";}/**
     * 处理空指针异常
     */@ExceptionHandler(value=NullPointerException.class)publicStringexceptionHandler(NullPointerException e){System.out.println("发生空指针异常!原因是:"+e);return"null";}}

前端执行结果:
在这里插入图片描述
后台打印结果:
在这里插入图片描述

三、@ControllerAdvice

如果单使用2中的@ExceptionHandler,只能在当前Controller中处理异常。但当配合@ControllerAdvice一起使用的时候,则可以全局捕获。

@ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被@RequestMapping注解的方法加一些逻辑处理,最常用的就是异常处理。

需要配合@ExceptionHandler使用。
当将异常抛到controller时,可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面。
编写ControllerExceptionHandler

packagecom.example;@ControllerAdvicepublicclassControllerExceptionHandler{privateLogger logger=LoggerFactory.getLogger(ControllerExceptionHandler.class);@InitBinderpublicvoidinitMyBinder(WebDataBinder binder){// 添加对日期的统一处理
        binder.addCustomFormatter(newDateFormatter("yyyy-MM-dd HH:mm:ss"));}@ModelAttributepublicvoidaddMyAttribute(Model model){
        model.addAttribute("user","zfh");// 在@RequestMapping的接口中使用@ModelAttribute("name") Object name获取}@ExceptionHandler(value=Exception.class)@ResponseStatus(HttpStatus.OK)@ResponseBody// 如果使用了@RestControllerAdvice,这里就不需要@ResponseBody了publicMaphandler(Exception ex){
        logger.error("这里定义统一异常处理", ex);Map<String,Object> map=newHashMap<>();
        map.put("code",400);//判断异常的类型,返回不一样的返回值if(exinstanceofMissingServletRequestParameterException){
            map.put("msg","方法缺少必需参数:"+((MissingServletRequestParameterException) ex).getParameterName());}elseif(exinstanceofBussinessException){
            map.put("msg","抛出自定义异常。。。error。。");}return map;}/**
     * 如果不需要返回json数据,而要渲染某个页面模板返回给浏览器,那么可以这么实现:
     * @param ex
     * @return
     *///    @ExceptionHandler(value = BussinessException.class)//    public ModelAndView myErrorHandler(BussinessException ex) {//        ModelAndView modelAndView = new ModelAndView();//        //指定错误页面的模板页//        modelAndView.setViewName("error");//        modelAndView.addObject("code", ex.getCode());//        modelAndView.addObject("msg", ex.getMsg());//        return modelAndView;//    }}

编写TestAdvice

@RequestMapping("testException")publicStringtestException()throwsException{thrownewMissingServletRequestParameterException("name","String");}@RequestMapping("testBussinessException")publicStringtestMyException()throwsBussinessException{thrownewBussinessException("抛出了BussinessException");}

输入http://localhost:8080/StudySpring/testException,结果如下:
在这里插入图片描述
在这里插入图片描述
输入http://localhost:8080/StudySpring/testBussinessException,结果如下:
在这里插入图片描述
在这里插入图片描述
也可以用@RestControllerAdvice,其中
@RestControllerAdvice = @ControllerAdvice + @ResponseBody

packagecom.aac.common.exception;importcom.aac.common.api.vo.Result;importlombok.extern.slf4j.Slf4j;importorg.apache.shiro.authz.AuthorizationException;importorg.springframework.dao.DuplicateKeyException;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.RestControllerAdvice;importorg.springframework.web.servlet.NoHandlerFoundException;/**
 * 异常处理器
 */@RestControllerAdvice@Slf4jpublicclassSkyBootExceptionHandler{@ExceptionHandler(AuthorizationException.class)publicResult<?>handleAuthorizationException(AuthorizationException e){
        log.error(e.getMessage(), e);returnResult.error("没有权限,请联系管理员授权");}@ExceptionHandler(DuplicateKeyException.class)publicResult<?>handleDuplicateKeyException(DuplicateKeyException e){
        log.error(e.getMessage(), e);returnResult.error("数据库中已存在该记录");}@ExceptionHandler(Exception.class)publicResult<?>handleException(Exception e){
        log.error(e.getMessage(), e);returnResult.error(e.getMessage());}/**
     * 处理自定义异常
     */@ExceptionHandler(SkyBootException.class)publicResult<?>handleRRException(SkyBootException e){
        log.error(e.getMessage(), e);returnResult.error(e.getMessage());}@ExceptionHandler(NoHandlerFoundException.class)publicResult<?>handlerNoFoundException(Exception e){
        log.error(e.getMessage(), e);returnResult.error(404,"路径不存在,请检查路径是否正确");}}

项目中实际使用,RestControllerAdvice+ExceptionHandler,举例如下:

packagecom.aac.common.exception;importcom.aac.common.api.vo.Result;importlombok.extern.slf4j.Slf4j;importorg.apache.shiro.authz.AuthorizationException;importorg.springframework.dao.DuplicateKeyException;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.RestControllerAdvice;importorg.springframework.web.servlet.NoHandlerFoundException;/**
 * 异常处理器
 */@RestControllerAdvice@Slf4jpublicclassSkyBootExceptionHandler{@ExceptionHandler(AuthorizationException.class)publicResult<?>handleAuthorizationException(AuthorizationException e){
        log.error(e.getMessage(), e);returnResult.error("没有权限,请联系管理员授权");}@ExceptionHandler(DuplicateKeyException.class)publicResult<?>handleDuplicateKeyException(DuplicateKeyException e){
        log.error(e.getMessage(), e);returnResult.error("数据库中已存在该记录");}@ExceptionHandler(Exception.class)publicResult<?>handleException(Exception e){
        log.error(e.getMessage(), e);returnResult.error(e.getMessage());}/**
     * 处理自定义异常
     */@ExceptionHandler(SkyBootException.class)publicResult<?>handleRRException(SkyBootException e){
        log.error(e.getMessage(), e);returnResult.error(e.getMessage());}@ExceptionHandler(NoHandlerFoundException.class)publicResult<?>handlerNoFoundException(Exception e){
        log.error(e.getMessage(), e);returnResult.error(404,"路径不存在,请检查路径是否正确");}}
importlombok.extern.slf4j.Slf4j;importorg.springframework.http.HttpStatus;importorg.springframework.validation.FieldError;importorg.springframework.web.bind.MethodArgumentNotValidException;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.
  • 作者:懒虫虫~
  • 原文链接:https://blog.csdn.net/jike11231/article/details/111995827
    更新时间:2022-10-10 12:29:16