关于异常的理解
首先要清楚,一切异常对系统来说,都是不正常的表现,尽管有时由于业务处理的需要我们会主动抛出一些异常,但也不意味对这些异常可以不管不顾,总有一个地方需要对自己抛出的异常进行特殊处理。 在日常开发中,我们应该尽量提高系统可用性,最大限度的避免任何异常的出现,而不是去指望完善异常处理来完善系统。 异常处理是异常无法避免的出现后而采取的一种应急措施,主要目的是对外增加友好性,对内提供补救措施。 异常处理很重要,但不要因此就认为完善的异常处理是系统核心,不要指望异常处理尽善尽美,不要指望异常处理来给系统缺陷擦屁股; 如果系统异常过多,那么我们要做的不是去尽可能的完善异常处理机制,而是要好好去反思:系统架构设计是否合理,系统逻辑设计是否合理。
Spring Boot 中全局异常处理
在spring boot中提供了默认的异常处理机制:Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。 默认返回的视图或Json消息内容如下:
{
"timestamp": "2018-05-12T06:11:45.209+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/index.html"
}
很显然,把这样的响应结果返回给前端或者浏览器不太友好。在实际开发中,如果需要返回页面,我们也是希望返回公司自定义的统一的错误页面,而不是“Whitelabel Error Page”;如果要返回json也最好是公司统一规定格式的json,这样有利于前后端分离更好的协作。幸运的是,在Spring boot中我们可以实现这样的愿望。
@ControllerAdvice+ @ExceptionHandler
这种组合可以实现全局的异常统一处理,既可以返回json格式的结果,也可以返回指定的错误页。事实上,@ControllerAdvice+ @ExceptionHandler这种组合方式并不是spring boot特有的,而是spring提供的异常处理方式,使用如下:
@ControllerAdvice
public class GlobalExceptionController {
//返回json格式的响应结果
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public BaseResp exceptionHandler(RuntimeException e, HttpServletResponse response) {
BaseResp resp = new BaseResp();
resp.setCode(300);
resp.setMsg("未知错误");
return resp;
}
//返回页面 ,这里有多种实现方式,错误页面如何放置取决于我们使用什么样的模板引擎或者是否使用模板引擎,这里不多讲述,网上有大把的参考样例,这里想说的是,现在基本都是前后端分离的开发模式,所以这种直接返回页面的方式其实很少使用
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e); //异常内容(页面展示)
mav.addObject("url", req.getRequestURL()); //请求的url地址(页面展示)
mav.setViewName("error"); //设置视图名称
return mav;
}
}
注解解释:
- @ControllerAdvice:属于类注解,被此注解标识的类可以处理全局的异常
- @ExceptionHandler:属于方法注解,用于指定此方法可以处理的异常类型,如:@ExceptionHandler(value = Exception.class)
注意事项: @ControllerAdvice+ @ExceptionHandler虽然可以处理全局的异常,但是这种方式只能处理应用级别的异常,有一些容器级别的异常它就无能为力了,例如Filter抛出异常,若需要处理这类容器级别的异常,我们只须要提供自定义的ErrorController便可,自定义的ErrorController有两种方式:一种是实现ErrorController接口,另外一种是直接继承BasicErrorController,因为接口只提供一个待实现的方法,而BasicErrorController已实现了不少功能,所以我们可以选择继承BasicErrorController来实现自己的ErrorController。
有时还会遇到@RestontrollerAdvice注解,它相当于@ResponseBody + @ControllerAdvice,如果我们的全局异常处理类中的方法返回的都是Json格式的结果,那就可以在类上使用@RestontrollerAdvice注解,同时可以省去每个方法上的@ResponseBody注解。
另外,如果我们希望全局异常处理类只特定的处理某些Controller的异常,可以通过@ControllerAdvice的basePackages属性来指定范围。
除了全局异常处理,还有局部异常处理 @Controller + @ExceptionHandler 局部异常处理就是在@Controller类中定义一个被@ExceptionHandler注解的异常处理方法,当此Controller中的方法抛出异常且异常类型符合@ExceptionHandler指定的异常类型时,就会被异常处理方法捕获并处理,但是这个异常处理方法作用的范围仅限于此Controller。
@Controller
public class GlobalExceptionController {
@Autowired
private UserService userService;
@RequestMapping("/test1")
@ResponseBody
public User test1(int userId) {
return userService.getUser(userId);
}
@ExceptionHandler(value = Exception.class)
public BaseResp defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
BaseResp resp = new BaseResp();
resp.setCode(300);
resp.setMsg("未知错误");
return resp;
}
}
ErrorController接口实现全局异常处理
上面说过@ControllerAdvice只能统一处理应用级别的异常,而对于容器级别的异常,如Filter异常,@ControllerAdvice就无能为力了,这时就需要我们自定义ErrorController来处理全局异常,具体方式有两种:一种是实现ErrorController接口,另外一种是直接继承BasicErrorController。因为BasicErrorController本身就实现了ErrorController接口,并提供了额外的功能,所以这里使用继承BasicErrorController的方式来实现全局异常处理。 事实上,通过观察 BasicErrorController 可以发现,它处理的就是 /error 请求,所以我们只需要继承 BasicErrorController 之后,重写 /error方法,然后在 error() 方法里面对全局异常进行统一处理即可。代码如下:
@RestController
public class GlobalExceptionController extends BasicErrorController {
private static final Logger log = LoggerFactory.getLogger(ErrorController.class);
public ErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
/**
* produces 设置返回的数据类型:application/json
*
* @param request 请求
* @return 自定义的返回实体类
*/
@Override
@RequestMapping(value = "", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
// 获取错误信息
String message = body.get("message").toString();
int code = EnumUtil.getCodeByMsg(message, ResultEnum.class);
HttpStatus httpStatus;
if (code == 500) {
// 服务端异常,状态码为500
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
} else {
// 其余异常(手动throw)为逻辑校验,状态码为200
httpStatus = HttpStatus.OK;
}
return new ResponseEntity(Result.failed(code, message), httpStatus);
}
}
|