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.