在内嵌容器为tomcat的情况下,springboot一个请求发生内部错误需要调用错误页时,其实是在StandardHostValve里查找错误页,再触发一次错误页请求调用。具体代码如下
public final void invoke(Request request, Response response)
throws IOException, ServletException {
......
Throwable t = (Throwable)
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// Protect against NPEs if the context was destroyed during a
// long running request.
if (!context.getState().isAvailable()) {
return;
}
// Look for (and render if found) an application level error page
if (response.isErrorReportRequired()) {
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
......
}
private void status(Request request, Response response) {
int statusCode = response.getStatus();
// Handle a custom error page for this status code
Context context = request.getContext();
if (context == null) {
return;
}
/* Only look for error pages when isError() is set.
* isError() is set when response.sendError() is invoked. This
* allows custom error pages without relying on default from
* web.xml.
*/
if (!response.isError()) {
return;
}
//根据错误码查找已注册的错误页
ErrorPage errorPage = context.findErrorPage(statusCode);
if (errorPage == null) {
//注意这里,springboot默认只注册一个错误页/error,所以在默认情况下
//所有异常错误码都会跳转到/error
// Look for a default error page
errorPage = context.findErrorPage(0);
}
......
}
默认情况下,springboot只注册一个错误页/error,因此查找错误码页时不会找到任何匹配的,只能跳转到/error。springboot内注册错误页是通过添加ErrorPageRegistrar实现的
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
private Collection<ErrorPageRegistrar> getRegistrars() {
if (this.registrars == null) {
// Look up does not include the parent context
this.registrars = new ArrayList<>(this.beanFactory
.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);
this.registrars = Collections.unmodifiableList(this.registrars);
}
return this.registrars;
}
所以我们要在springboot环境内注册自己的错误码配置的话,添加一个自定义的ErrorPageRegistrar就可以了。为了方便起见,我把错误码定义和错误页配置放一起了,只要把下面的代码拷到任意一个Controller里就可以了
@Bean
public ErrorPageRegistrar errorPageRegistrar(){
return registry -> {
registry.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/500"));
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/404"));
registry.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN,"/403"));
registry.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED,"/401"));
};
}
@RequestMapping("/500")
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public RequestResult error500(HttpServletRequest request){
Map<String, Object> body = getErrorAttributes(request,true);
return new RequestResult(false,"服务器异常",body);
}
@RequestMapping("/404")
@ResponseStatus(HttpStatus.NOT_FOUND)
public RequestResult error404(HttpServletRequest request){
Map<String, Object> body = getErrorAttributes(request,true);
return new RequestResult(false,"无此路径:"+body.get("path"));
}
@RequestMapping("/403")
@ResponseStatus(HttpStatus.FORBIDDEN)
public RequestResult error403(HttpServletRequest request){
Map<String, Object> body = getErrorAttributes(request,true);
return new RequestResult(false,"无此资源访问权限:"+body.get("path"));
}
@RequestMapping("/401")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public RequestResult error401(HttpServletRequest request){
Map<String, Object> body = getErrorAttributes(request,true);
return new RequestResult(false,"未授权的访问:"+body.get("path"));
}