Zuul统一异常处理

今天研究了一下Zuul,看书上的例子,最后发现异常处理的和书上的并不一样。
书上使用的是1.x,我做的时候用的最新的2.x的版本。这里也做一下记录。

首先是SendErrorFilter这个类,现在filterType已经是error的了:

@Override
public String filterType() {
	return ERROR_TYPE;
}

所以,现在异常都会到这个filter里面,而这个filter里面最后直接是forward到了/error这个链接上面。

所以这里就出现了一个很奇怪的现象,如果进入到Zuul的filter链里面来后,在SendErrorFilter之前发生了异常,那么我们会发现请求数据已返回后,会再次进入到我们自定义的postfilter里面。下面就是SendErrorFilter里面的处理:

RequestDispatcher dispatcher = request.getRequestDispatcher(
		this.errorPath);
if (dispatcher != null) {
	ctx.set(SEND_ERROR_FILTER_RAN, true);
	if (!ctx.getResponse().isCommitted()) {
		ctx.setResponseStatusCode(exception.nStatusCode);
		dispatcher.forward(request, ctx.getResponse());
	}
}

所以现在我们不需要自定义errorfilter,最后都会进入到/error映射的方法里面。

然后我们看/error,对应的文件是BasicErrorController,里面有两个方法:errorHtmlerror,看方法名就知道一个是返回html一个是返回jsonxml对象的。
他们是根据客户端请求是的accept进行选择具体返回内容的。这里面返回的参数可以这样进行配置:

@Component
public class ErrorAttributeConfig extends DefaultErrorAttributes {
	
	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//		Map<String, Object> result = super.getErrorAttributes(webRequest, includeStackTrace);
		Map<String, Object> result = new HashMap<String, Object>();
		result.put("code", 9999);
		result.put("message", "系统错误");
		result.put("sign", "系统签名");
		return result;
	}
	
}

当然如果你想自己重新/error这个映射也是可以的,我们可以搜索一下BasicErrorController在哪里定义的,可以看到:

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
	return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
			this.errorViewResolvers);
}

所以BasicErrorController只有在我们没有定义自己的ErrorController时才会生效,所以我们可以实现ErrorController接口就可以定义/error了:

@Controller
public class GlobalErrorController implements ErrorController {

	@Override
	public String getErrorPath() {
		return "/error";
	}
	
	@ResponseBody
	@RequestMapping("/error")
	public String error() {
		return "自定义错误信息";
	}

}

这里再多说一下,上面的均是会进入到filter的异常处理,如果是请求Zuul本身项目的一些异常需要使用@ControllerAdvice来实现,这个和普通的Spring MVC项目就一样了:

@ControllerAdvice
public class GlobalExceptionHandler {

	@ResponseBody
	@ResponseStatus(HttpStatus.NOT_FOUND)
	@ExceptionHandler(value = Exception.class)
//	public ResponseEntity<Map<String, String>> defaultErrorHandler(HttpServletRequest request, Exception e)throws Exception {
	public Map<String, String> defaultErrorHandler(HttpServletRequest request, Exception e)throws Exception {
		Map<String, String> data = new HashMap<String, String>();
		data.put("code", "9999");
		data.put("message", "全局异常处理");
		return data;
//		return ResponseEntity.status(200).contentType(MediaType.APPLICATION_JSON_UTF8).body(data);
	}

}

自定义404500等页面:

@Configuration
public class ErrorPageConfig {

	@Bean
	public ErrorPageRegistrar errorPageRegistrar() {
		return new ErrorPageRegistrar() {
			@Override
			public void registerErrorPages(ErrorPageRegistry registry) {
//				registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"),
				registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"),
						new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html"));
			}
		};
	}

}

DEMO地址:https://github.com/acgist/spring/tree/master/esc-eureka-gateway