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