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

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

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

一、HandlerExceptionResolver全局异常处理

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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">
    <bean id="exceptionResolver" class="com.example.ExceptionResolver"/>
</beans>

编写ExceptionResolver

package com.example;
/**
 * 全局异常处理
 * @Order(-1000) 为了使优先级最高
 * @Component 把普通pojo实例化到spring容器中,
 * 相当于配置文件中的 <bean id="" class=""/>
 */
@Order(-1000)
@Component
public class ExceptionResolver implements HandlerExceptionResolver {

    private static Logger logger = LoggerFactory.getLogger(ExceptionResolver.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response, Object handler, Exception ex) {
        ResultVO result = new ResultVO();
        StringBuilder sb = new StringBuilder();
        System.out.println("执行全局异常处理-----------------------");
        //处理异常
        if(ex instanceof BussinessException) {
            resolverBussinessException(ex, sb, result);
        } else if (ex instanceof BindException) {
            resolverBindException(ex, sb, result);
        } else {
            resolverOtherException(ex, sb, result);
        }

        result.setCode(0);
        result.setResult(sb);
        result.setTime(new Date());

        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();
        return new ModelAndView();
    }

    /*
     * 处理业务层异常
     */
    private void resolverBussinessException(Exception ex, StringBuilder sb, ResultVO result) {
        BussinessException businessException = (BussinessException) ex;
        sb.append(businessException.getMsg());
        addResult(result, "业务异常");
    }

    /*
     * 处理参数绑定异常
     */
    private void resolverBindException(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, "参数传递异常");
    }

    /*
     * 处理其他异常
     */
    private void resolverOtherException(Exception ex, StringBuilder sb, ResultVO result) {
        sb.append(ex.getMessage());
        addResult(result, "其他异常");
    }

    /*
     * 封装code和msg
     */
    private void addResult(ResultVO result, String msg) {
        result.setMsg(msg);
    }
}

编写HelloWorld

  @RequestMapping("/error")
    public String error() {
        int a = 11 / 0;
        return "11 / 0 = " + a;
    }

    @GetMapping(value = "testAdvice")
    public String testAdvice(@ModelAttribute("user") String user, Date date) throws Exception {
        throw new Exception("模拟测试直接抛出异常");
    }

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

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

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

package com.example.controller;

@RestController
public class TestControllerException {

	//单个controller进行异常处理
    @RequestMapping("/testError")
    public String testError() {
        int a = 10 / 0;
        return "this is testError" + a;
    }

    /**
     * 处理其他异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String exceptionHandler(Exception e) {
        System.out.println(e);
        return "this is a controller exception method!";
    }
    
    /**
     * 处理空指针异常
     */
    @ExceptionHandler(value =NullPointerException.class)
    public String  exceptionHandler(NullPointerException e){
        System.out.println("发生空指针异常!原因是:"+e);
        return "null";
    }
}

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

三、@ControllerAdvice

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

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

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

package com.example;

@ControllerAdvice
public class ControllerExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    @InitBinder
    public void initMyBinder(WebDataBinder binder) {
        // 添加对日期的统一处理
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
    }

    @ModelAttribute
    public void addMyAttribute(Model model) {
        model.addAttribute("user", "zfh"); // 在@RequestMapping的接口中使用@ModelAttribute("name") Object name获取
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody // 如果使用了@RestControllerAdvice,这里就不需要@ResponseBody了
    public Map handler(Exception ex) {
        logger.error("这里定义统一异常处理", ex);
        Map<String, Object> map = new HashMap<>();
        map.put("code", 400);
        //判断异常的类型,返回不一样的返回值
        if(ex instanceof MissingServletRequestParameterException){
            map.put("msg","方法缺少必需参数:"+((MissingServletRequestParameterException) ex).getParameterName());
        }
        else if(ex instanceof BussinessException){
            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")
    public String testException() throws Exception{
        throw new MissingServletRequestParameterException("name","String");
    }

    @RequestMapping("testBussinessException")
    public String testMyException() throws BussinessException {
        throw new BussinessException("抛出了BussinessException");
    }

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

package com.aac.common.exception;

import com.aac.common.api.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 异常处理器
 */
@RestControllerAdvice
@Slf4j
public class SkyBootExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)
    public Result<?> handleAuthorizationException(AuthorizationException e) {
        log.error(e.getMessage(), e);
        return Result.error("没有权限,请联系管理员授权");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result<?> handleDuplicateKeyException(DuplicateKeyException e) {
        log.error(e.getMessage(), e);
        return Result.error("数据库中已存在该记录");
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.error(e.getMessage());
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(SkyBootException.class)
    public Result<?> handleRRException(SkyBootException e) {
        log.error(e.getMessage(), e);
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<?> handlerNoFoundException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.error(404, "路径不存在,请检查路径是否正确");
    }
}

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

package com.aac.common.exception;

import com.aac.common.api.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 异常处理器
 */
@RestControllerAdvice
@Slf4j
public class SkyBootExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)
    public Result<?> handleAuthorizationException(AuthorizationException e) {
        log.error(e.getMessage(), e);
        return Result.error("没有权限,请联系管理员授权");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result<?> handleDuplicateKeyException(DuplicateKeyException e) {
        log.error(e.getMessage(), e);
        return Result.error("数据库中已存在该记录");
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.error(e.getMessage());
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(SkyBootException.class)
    public Result<?> handleRRException(SkyBootException e) {
        log.error(e.getMessage(), e);
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<?> handlerNoFoundException(Exception e) {
        log.error(e.getMessage(), e);
        return Result.error(404, "路径不存在,请检查路径是否正确");
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * 全局异常处理
 */
@Slf4j
@ControllerAdvice
public class RestExceptionHandler {

    /**
     * 处理参数验证失败异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    private String methodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.warn("MethodArgumentNotValidException", e);
        FieldError fieldError = e.getBindingResult().getFieldError();
        //这里可以进行封装返回值
        return fieldError.getDefaultMessage();
    }
}

详细代码地址
https://gitee.com/jike11231/study-spring.git

参考文章
https://www.jianshu.com/p/47aeeba6414c
https://blog.csdn.net/sinat_38649523/article/details/107050013
https://blog.csdn.net/qq_22172133/article/details/82147630
https://blog.csdn.net/lkforce/article/details/98494922
https://www.cnblogs.com/junzi2099/p/7840294.html

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值