项目中常常需要一个全局异常,防止未处理的异常信息直接暴露给用户,影响用户体验。springboot中可以使用ControllerAdvice和ExceptionHandler这两个注解来做全局异常,这种方式比较便捷,但是也有一个问题:
ContollerAdvice只能拦截控制器中的异常,换言之,只能拦截500之类的异常,但是对于404这样不会进入控制器处理的异常不起作用。所以我仿造springboot默认的全局处理类BasicController实现全局的异常处理,这样就能很好的按照自己的需求处理异常了。
我们先了解一下springboot默认的异常处理是怎样的:
springboot会将所有的异常发送到路径为server.error.path(application.properties中可配置,默认为”/error”)的控制器方法中进行处理,页面请求和ajax请求会分别打到对应的处理方法上。具体的处理可查看BasicErrorController的源代码:
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
public ResponseEntity
了解完springboot的默认全局异常处理后,开始仿造着写自定义的异常处理,BasicErrorController继承了AbstractErrorController,所以我们也继承AbstractErrorController。整体代码如下:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@Slf4j
public class GlobalExceptionController extends AbstractErrorController{
private final ErrorProperties errorProperties;
@Autowired
public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
super(errorAttributes);
this.errorProperties=serverProperties.getError();
}
@Override
public String getErrorPath() {
return errorProperties.getPath();
}
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
ModelAndView modelAndView=new ModelAndView("error");
Map errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
if(errorMap!=null) {
/*timestamp status error message path*/
modelAndView.addObject("msg",errorMap.get("error"));
modelAndView.addObject("statusCode",errorMap.get("status"));
logHandler(errorMap);
}
return modelAndView;
}
@RequestMapping
@ResponseBody
public ServiceResponse
代码讲解:
1.开头注解
@Controller
@RequestMapping(“${server.error.path:${error.path:/error}}”)
这里与BasicErrorControlelr一致,使用server.error.path的变量作为映射地址。
2.构造函数
@Autowired
public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
super(errorAttributes);
this.errorProperties=serverProperties.getError();
}
本来是抄BasicErrorController中的构造方法:
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
但是程序启动的室友报错了,原因是无法找到errorProperties的实体。于是,我便查看在自动注入BasicErrorController的类ErrorMvcAutoConfiguration,在里面发现errorPropertie属性原来是通过ServerProperties这个类注入的:
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
于是便在自定义的异常处理类构造方法中使用ServerProperties 注入errorProperties。
3.编写自定义的异常处理方法。
通过查看AbstractErrorController和BasicErrorController源码,我发现异常信息都是通过
protected Map getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
这个方法获取的。我还发现ErrorAttributes这个类中有一个接口可以直接获取异常:
/**
* Return the underlying cause of the error or {@code null} if the error cannot be
* extracted.
* @param webRequest the source request
* @return the {@link Exception} that caused the error or {@code null}
*/
Throwable getError(WebRequest webRequest);
但是,测试之后发现通过这个getError获取的异常都是null,一看上面的注释,才发现这个方法只能获取未被抽取出来的异常,而我们拦截的异常都被抽取出来了,所以获取不到。
所以还是仿造BasicErrorControler实现我们自己的异常处理:
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
ModelAndView modelAndView=new ModelAndView("error");
Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
if(errorMap!=null) {
/*timestamp status error message path*/
modelAndView.addObject("msg",errorMap.get("error"));
modelAndView.addObject("statusCode",errorMap.get("status"));
logHandler(errorMap);
}
return modelAndView;
}
@RequestMapping
@ResponseBody
public ServiceResponse
ServiceResponse是我自己定义的ajax返回实体类.getErrorAttributes会将异常消息封装成一个map,包含了异常的主要信息:
timestamp:异常发生的时间
status :http响应状态码
error:异常的名称(Exception.getName())
message:异常的消息(getmessage())
path:发生异常的路由。
这样做其实还是一点没有做好,那就是无法获取到Exception异常对象。后面有时间再想想怎么弄。
还有一点要特别注意的地方,那就是一定要保证自己的异常处理代码不能再发生异常,否则异常会还会被自定义的处理器接受,造成死循环。