微服务 | zuul 的全局异常处理器@ControllerAdvice

博客引用处(以下内容在原有博客基础上进行补充或更改,谢谢这些大牛的博客指导):
@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

前提:
在zuul工程中创建了一个全局异常处理器:CommonExceptionHandler 用来处理全局的异常(一个自定义的异常CommonException),处理器代码如图所示:

@ControllerAdvice
public class CommonExceptionHandler {

    @ExceptionHandler(value = CommonException.class)
    @ResponseBody
    public Result exceptionHandler(CommonException e) {
        return new Result().setCode(e.getStatus()).setMessage(e.getMessage());
    }
}

主要在@ControllerAdvice这个注解上,其原本的作用在于解决controller层的异常处理问题,避免出现try-catch的处理模块,
比如: 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。

如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。例如以下 Controller 方法代码(非常难看且冗余):

/**
 * 手动处理 Service 层异常和数据校验异常的示例
 * @param dog
 * @param errors
 * @return
 */
@PostMapping(value = "")
AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors){
    AppResponse resp = new AppResponse();
    try {
        // 数据校验
        BSUtil.controllerValidate(errors);

        // 执行业务
        Dog newDog = dogService.save(dog);

        // 返回数据
        resp.setData(newDog);

    }catch (BusinessException e){
        LOGGER.error(e.getMessage(), e);
        resp.setFail(e.getMessage());
    }catch (Exception e){
        LOGGER.error(e.getMessage(), e);
        resp.setFail("操作失败!");
    }
    return resp;
}

关键点在于:它只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常以及过滤器filter层的异常还有Spring 框架层的异常,就无能为力了。所以并不能称为真正的全局异常处理。

注解的解释内容:
@ControllerAdvice 注解定义全局异常处理类

@ControllerAdvice
public class GlobalExceptionHandler {
}

请确保此 GlobalExceptionHandler 类能被扫描到并装载进 Spring 容器中。

@ExceptionHandler 注解声明异常处理方法

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    String handleException(){
        return "Exception Deal!";
    }
}

方法 handleException() 就会处理所有 Controller 层抛出的 Exception 及其子类的异常,这是最基本的用法了。

如果 @ExceptionHandler 注解中未声明要处理的异常类型,则默认为参数列表中的异常类型。所以上面的写法,还可以写成这样:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler()
    @ResponseBody
    String handleException(Exception e){
        return "Exception Deal! " + e.getMessage();
    }
}

这里面的参数对象 e 就是 Controller 层抛出的异常对象!

根据层面不同区分定义不同的异常类
比如:处理 Service 层上抛的业务异常
有时我们会在复杂的带有数据库事务的业务中,当出现不和预期的数据时,直接抛出封装后的业务级运行时异常,进行数据库事务回滚,并希望该异常信息能被返回显示给用户。

封装的业务异常类:

public class BusinessException extends RuntimeException {

    public BusinessException(String message){
        super(message);
    }
}

Service 实现类:

@Service
public class DogService {

    @Transactional
    public Dog update(Dog dog){

        // some database options

        // 模拟狗狗新名字与其他狗狗的名字冲突
        this.isTrue(false, "狗狗名字已经被使用了...");

        // update database dog info

        return dog;
    }

}

public static void isTrue(boolean expression, String error){
    if(!expression) {
        throw new BusinessException(error);
    }
}

在 GlobalExceptionHandler 类中声明该业务异常类,并进行相应的处理,然后返回给用户。更贴近真实项目的代码,应该长这样子:

/**
 * Created by kinginblue on 2017/4/10.
 * @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {

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

    /**
     * 处理所有不可知的异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    AppResponse handleException(Exception e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail("操作失败!");
        return response;
    }

    /**
     * 处理所有业务异常
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    AppResponse handleBusinessException(BusinessException e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail(e.getMessage());
        return response;
    }
}

Controller 层的代码,就不需要进行异常处理了:

@RestController
@RequestMapping(value = "/dogs", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public class DogController {

    @Autowired
    private DogService dogService;

    @PatchMapping(value = "")
    Dog update(@Validated(Update.class) @RequestBody Dog dog){
        return dogService.update(dog);
    }
}

代码说明
Logger 进行所有的异常日志记录。

@ExceptionHandler(BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。

@ExceptionHandler(Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。

备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值