【全局异常处理】@ExceptionHandler()和@RestControllerAdvice

背景

在MVC架构的项目中,大量的业务代码写在service层,同时会向上抛出很多异常,此时作为面向返回的controller就不得用try…catch处理这些异常。大量的try…catch会降低代码的可读性和可维护性。

@GetMapping("/test")
public String test() {
	try {
		// 业务逻辑
		return "success";
	} catch (Exception e) {
		return "fail"; // 处理异常
	}
}

此外,很多异常信息会被前端用来提示用户的操作异常结果,所以也不能在controller层完全的catch掉。

一、为什么要用到全局异常处理?

由上可知,合适且统一的异常处理对于web项目来说是很重要的,一般会用到全局异常处理,其主要作用如下:

  • 作用:
    1.后端层面:避免在controller层产生复杂的try…catch结构
    2.前端层面:将合适的信息反馈给前端用户

二、全局异常处理的两种方式

SpringMVC提供了两种方式来处理异常:

  1. 使用HandlerExceptionResolver实现
  2. 使用@ExceptionHandler+@ControllerAdvice实现

1.实现HandlerExceptionResolver接口

该接口是Spring首个版本就提供的异常处理接口,其定义为

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}

参数解释:
handler:出现异常的对象,即controller层
exception:出现的异常信息,即各种exception

实现流程
  • 1.实现HandlerExceptionResolver 接口
    针对不同的异常类型,尤其是一些自定义的异常信息,进行定制化的异常处理
@Slf4j
public class HandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
        log.error("系统统一异常处理:", exception);
        // 类型1:没有登陆的异常会重定向到登录页面
		if (e instanceof NoLoginException) {
            //重定向到登录页面
            ModelAndView mv = new ModelAndView("redirect:/index");
            return mv;
        }
       
       	// 需要判断方法上是否有ResponseBody注解
        // 判断Handler对象是否是方法对象HandlerMethod
        if (handler instanceof HandlerMethod) {
            //类型转换
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //获取这个方法上面声明的ResponseBody注解对象
            ResponseBody responseBody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class);
            //判断ResponseBody对象是否为空(如果对象为空,则表示返回的是视图,否则为数据)
            if (responseBody == null) {
                /**
                 * 类型2:自定义异常,且返回的是视图modelAndView
                 * 
                 */
                //判断异常类型
                if (e instanceof ParamsException)
                {
                    ParamsException p = (ParamsException) e;
                    //设置视图的异常信息
                    modelAndView.addObject("code", p.getCode());
                    modelAndView.addObject("msg", p.getMsg());
                    //认证异常
                } else if (e instanceof AuthException) {
                    AuthException a = (AuthException) e;
                    //设置视图的异常信息
                    modelAndView.addObject("code", a.getCode());
                    modelAndView.addObject("msg", a.getMsg());
                }
                //return modelAndView;
            } else {
                /**
                 * 类型3:自定义异常,且返回的是json数据
                 * 返回json数据需要自定义json数据,并通过输出流输出
                 */
                //设置默认的异常处理
                ResultInfo resultInfo = new ResultInfo();
                resultInfo.setCode(500);
                resultInfo.setMsg("系统异常,请重试!");
                //判断异常类型是否是自定义异常
                if (e instanceof ParamsException) {
                    ParamsException p = (ParamsException) e;
                    resultInfo.setCode(p.getCode());
                    resultInfo.setMsg(p.getMsg());
                } else if (e instanceof AuthException)//认证异常
                {
                    AuthException a = (AuthException) e;
                    resultInfo.setCode(a.getCode());
                    resultInfo.setMsg(a.getMsg());
                }
                // 3.输出响应结果
		        // 注意:如果是由ResponseBody修饰的代表返回值是json格式,需要用字符输出流输出响应结果
		        // 否则只需要返回modelandView即可
		       	// 如果是restcontroller/ResponseBody修饰的接口
                // 此处为json数据的响应输出【大多数前后端分离项目】
                //设置响应类型及编码格式(响应JSON格式数据)【为了防止乱码】
                response.setContentType("application/json;charset=UTF-8");
                //得到字符输出流
                PrintWriter out = null;
                try {
                    //得到输出流
                    out = response.getWriter();
                    //将需要返回的对象转换成json格式的字符串
                    String json = JSON.toJSONString(resultInfo);
                    //输出数据
                    out.write(json);
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                } finally {
                    //如果对象不为空,则关闭
                    if (out != null) {
                        out.close();
                    }
                    return null;
                }

            }
        }
		// 如果是视图数据直接返回即可
        return modelAndView;                       
    }
}
  • 2.将该解析器注册到Spring容器中
    如果是spring版本,则需要在mvc的xml配置文件中注册
<!-- 全局异常处理器 -->
<bean class="com.zeta.exception.HandlerExceptionResolver "></bean>

如果是springboot版本,只需要在该实现接口上加上@Component

@Component
public class HandlerExceptionResolver implements HandlerExceptionResolver {
}

2.@ExceptionHandler+@ControllerAdvice注解实现

相比初代方法,利用注解的方式进一步将代码解耦合,注解+方法的形式提高了代码的可读性和操作性,代替了繁琐的if…else if语句。

实现流程
  • @Exceptionhandler(异常类型.class)注解
    注解应用在方法上,表示针对某种类型的异常处理,一般返回值是json格式的数据,同controller的返回值
@ExceptionHandler(Exception.class)
	public RespBean ExceptionHandler(Exception e) {
		if (e instanceof GlobalException) {
			GlobalException ex = (GlobalException) e;
			return RespBean.error(ex.getRespBeanEnum());
		} else if (e instanceof BindException) {
			BindException ex = (BindException) e;
			RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
			respBean.setMessage("参数校验异常:" + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
			return respBean;
		}
		return RespBean.error(RespBeanEnum.ERROR);
	}
  • @RestControllerAdvice注解
    字面意思:建议将下列方法应用在RestController接口方法上
    此处是将各类异常处理器@ExceptionHandler应用到接口上

启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。

@RestControllerAdvice
public class GlobalExceptionHandler {

   @ExceptionHandler(Exception.class)
   public RespBean ExceptionHandler(Exception e) {
   	if (e instanceof GlobalException) {
   		GlobalException ex = (GlobalException) e;
   		return RespBean.error(ex.getRespBeanEnum());
   	} else if (e instanceof BindException) {
   		BindException ex = (BindException) e;
   		RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
   		respBean.setMessage("参数校验异常:" + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
   		return respBean;
   	}
   	return RespBean.error(RespBeanEnum.ERROR);
   }

}

参考文献

Spring的@ExceptionHandler注解使用方法

@RestControllerAdvice在全局异常处理中的作用

@ControllerAdvice 拦截异常并统一处理

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值