本文将从@controllerAdvice和@ExceptionHandler两个注解到自定义HttpStatus来对控制层的异常进行优雅的处理。
说起惭愧,在前不久,我还悻悻然在控制层通过频繁的try catch语句来进行异常处理及对应model返回,这么做当然是没什么错,但频繁写一样的代码属实让人感觉厌烦,于是开始找寻一种可以对异常进行统一异常处理的方式。
本文意在做一套系统性相关整理笔记,方便以后查看。手懒,延迟了个把月。
几种处理方式
HandlerExceptionResolver接口
1.实现HandlerExceptionResolver接口,将实现类作为Spring Bean,这样Spring就能扫描到它并作为全局异常处理器加载
2.在resolveException中实现异常处理逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Component @Slf4j public class CustomHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Method method = null; if (handler != null && handler instanceof HandlerMethod) { method = ((HandlerMethod) handler).getMethod(); } log.error("[{}] system error", method, ex); ResponseDTO response = ResponseDTO.builder() .errorCode(ErrorCode.SYSTEM_ERROR) .build(); byte[] bytes = JSON.toJSONString(response).getBytes(StandardCharsets.UTF_8)); try { FileCopyUtils.copy(bytes, response.getOutputStream()); } catch (IOException e) { log.error("error", e); throw new RuntimeException(e); } return new ModelAndView(); } }
|
从参数上,可以看到,不仅能够拿到发生异常的函数和异常对象,还能够拿到HttpServletResponse对象,从而控制本次请求返回给前端的行为。
此外,函数还可以返回一个ModelAndView对象,表示渲染一个视图,比方说错误页面,不过,在前后端分离为主流架构的今天,这个很少用了。如果函数返回的视图为空,则表示不需要视图。
@ExceptionHandler局部处理
通过@ExceptionHandler注解可以对当前控制层进行异常处理,当前控制层抛出的异常都会通过该方法来进行返回,这里是一个简单的返回模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Slf4j @RestController @RequestMapping(value = "/api/test") public class MyTestController extends BaseController {
@Autowired private DiscoveryClient client; @Autowired private Registration registration;
@GetMapping(value = "/addValue") public Integer adValue(@RequestParam("a") Integer a,@RequestParam("b") Integer b){ Integer r = a + b; ServiceInstance instance = serviceInstance(); String result = new StringBuffer().append("/add: Host:") .append(instance.getHost()) .append(", service_id:") .append(instance.getServiceId()) .append(" result:") .append(a + b).toString(); log.info(result); return r; }
public ServiceInstance serviceInstance(){ List<ServiceInstance> instances = client.getInstances(registration.getServiceId()); if (!instances.isEmpty() && instances.size() > 0) { for (ServiceInstance instance : instances) { if (instance.getPort() == 7001) { return instance; } } } return null; }
@ExceptionHandler(Exception.class) public ResponseEntity exceptionHandler(Exception e) { log.error("[{}] system error", e); return error("出错了!"); }
}
|
可以通过浏览器来访问一番,比如把参数b写为c,看结果:

但是这种方法还是存在局促性,我们需要的是对全局controller进行一个异常处理,那么这个时候就需要通过@ControllerAdvice和@ExceptionHandler来结合使用
通过@ControllerAdvice和@ExceptionHandler来结合使用
两者结合之后可以对所有控制层就行异常控制,并且可以细粒到具体异常,当然,你也可以定义自定义异常,或者在@ExceptionHandler中放置多个异常,等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(ParmException.class) ResponseEntity parmExceptionHandler(Exception e){ return new ResponseEntity(false, RetCode.PARM_EXCEPTION,e.getMessage(),null); }
@ExceptionHandler(JSONException.class) ResponseEntity jsonExceptionHandler(Exception e){ return new ResponseEntity(false, RetCode.JSON_EXCEPTION,e.getMessage(),null); }
}
|
看起来是比较方便简单的,其中ParmException为自定义异常,在方法参数内可以通过Exception来接收并获取到异常信息
容器中通过HttpStatus定义异常路径,结合两注解
通过@ControllerAdvice和@ExceptionHandler的确可以对所有controller层进行相对不错的异常控制,但有没有想过这种场景,比如请求404,405,400… 这种异常最好还是通过本系统来进行自定义处理,这样才能提高用户体验。
那么如何设置呢?上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class CustomErrorServeletFactory implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override public void customize(ConfigurableWebServerFactory factory) { for (HttpStatus e : HttpStatus.values()) { if (e.value() == HttpStatus.NOT_FOUND.value()) { factory.addErrorPages(new ErrorPage(e,"/404")); }else if (e.value() == HttpStatus.METHOD_NOT_ALLOWED.value()) { factory.addErrorPages(new ErrorPage(e,"/405")); }else if (e.value() != HttpStatus.OK.value()) { factory.addErrorPages(new ErrorPage(e,"/500")); } } } }
|
首先通过实现WebServerFactoryCustomizer接口中的customize方法来对容器工厂进行配置,可以看到,这里这里我对404,405,500状态码的定义,并设置了一个他们对应的路径,接下来可以自定义这几个路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @RestController @Slf4j public class FlagController extends BaseController {
@Value("${my.git.version:尚未配置}") private String gitVersion; @Value("${my.project.buildtime:尚未配置}") private String buildTime;
@RequestMapping(value = {"/", "/version"}) Object version(){ Map<String, Object> map = new HashMap<>(); map.put("Build_Time",buildTime); map.put("Build_GIT_Version",gitVersion); return map; }
@RequestMapping(value = "404") public ResponseEntity notFound(){ int code = response.getStatus(); response.setStatus(HttpStatus.OK.value()); return error(RetCode.RET_ERROR,code + "Not Found!"); }
@RequestMapping(value = "405") public ResponseEntity notAllowed(){ int code = response.getStatus(); response.setStatus(HttpStatus.OK.value()); return error(RetCode.RET_ERROR,code + "Not Allowed!"); }
@RequestMapping(value = "500") public ResponseEntity apiError(HttpServletRequest request){ int code = response.getStatus(); response.setStatus(HttpStatus.OK.value()); Exception e = (Exception) request.getAttribute("javax.servlet.error.exception"); String errorMsg = ""; if (e != null) { errorMsg = e.getMessage(); } log.error("apiError: {}",errorMsg); return error(RetCode.RET_ERROR,code + errorMsg); } }
|
总结
个人认为,任何能够给Controller加切面的机制都能变相的进行统一异常处理。比如:
有没有感觉这种方式让你一看之后感觉可行呢,或许你有更好的处理方式可以通过下方评论区告诉我,不胜感激。