spring boot自定义错误页面 (springboot自定义异常怎么使用)

SpringBoot请求错误如404可能看到如下页面:

idea创建springboot项目错误,springboot自定义注解处理异常

有时可能需要自定义错误页面针对不同的http.status,如404/400。

【1】解决方法

① 注册错误页面

如下所示:

@Component
publicclassErrorPageConfigimplementsErrorPageRegistrar{
@Override
publicvoidregisterErrorPages(ErrorPageRegistryregistry){
ErrorPageerror400Page=newErrorPage(HttpStatus.BAD_REQUEST,"/error/404");
ErrorPageerror404Page=newErrorPage(HttpStatus.NOT_FOUND,"/error/404");
ErrorPageerror500Page=newErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error/500");
registry.addErrorPages(error400Page,error404Page,error500Page);
}
}


② controller进行拦截

然后你只需要写个controller拦截不同请求然后跳到不同的自定义错误页面即可,如下所示:

@RequestMapping("/error/{status}")
publicStringerrorPage(@PathVariableIntegerstatus){
switch(status){
case401:
case400:return"/error/404";
case500:return"/error/500";
default:return"/error/default";
}
}


那么原理呢?

【2】原理讲解

① 启动SpringBoot,注册错误页面

如下图所示,启动项目时候 onRefresh方法中会创建一个WebServer,继而获取ServletWebServerFactory。

idea创建springboot项目错误,springboot自定义注解处理异常

1.1AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization

在创建bean-tomcatServletWebServerFactory时会调用AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization,如下所示:

@Override
publicObjectapplyBeanPostProcessorsBeforeInitialization(ObjectexistingBean,StringbeanName)
throwsBeansException{

Objectresult=existingBean;
for(BeanPostProcessorprocessor:getBeanPostProcessors()){
Objectcurrent=processor.postProcessBeforeInitialization(result,beanName);
if(current==null){
returnresult;
}
result=current;
}
returnresult;
}


该方法会获取bean后置处理器,然后循环遍历调用每个bean后置处理器的postProcessBeforeInitialization方法。

1.2 ErrorPageRegistrarBeanPostProcessor.postProcessBeforeInitialization

当遍历到ErrorPageRegistrarBeanPostProcessor时会调用其postProcessBeforeInitialization方法,方法源码如下所示:

@Override
publicObjectpostProcessBeforeInitialization(Objectbean,StringbeanName)throwsBeansException{
if(beaninstanceofErrorPageRegistry){
postProcessBeforeInitialization((ErrorPageRegistry)bean);
}
returnbean;
}


方法会判断当前bean是否ErrorPageRegistry类型,如果是,则调用postProcessBeforeInitialization方法,源码如下所示:

privatevoidpostProcessBeforeInitialization(ErrorPageRegistryregistry){
for(ErrorPageRegistrarregistrar:getRegistrars()){
registrar.registerErrorPages(registry);
}
}


该方法会获取Registrars,然后循环遍历调用每一个注册器的registerErrorPages方法。获取注册其源码如下所示:

privateCollection<ErrorPageRegistrar>getRegistrars(){
if(this.registrars==null){
//Lookupdoesnotincludetheparentcontext
this.registrars=newArrayList<>(
this.beanFactory.getBeansOfType(ErrorPageRegistrar.class,false,false).values());
this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE);
this.registrars=Collections.unmodifiableList(this.registrars);
}
returnthis.registrars;
}


故而,当我们的ErrorPageConfig 实现了ErrorPageRegistrar时,会被检测到并执行registerErrorPages方法。

idea创建springboot项目错误,springboot自定义注解处理异常

idea创建springboot项目错误,springboot自定义注解处理异常

② 把错误页面放到StandardContext.errorPageSupport中

StandardContext是什么?我们可以看下如下类继承示意图。

idea创建springboot项目错误,springboot自定义注解处理异常

在①中我们提到会注册错误页面 registrar.registerErrorPages(registry); ,如下图所示此时的registry为TomcatServletWebServerFactory:

idea创建springboot项目错误,springboot自定义注解处理异常

我们再来看下TomcatServletWebServerFactory继承示意图(可以看到其父类AbstractConfigurableWebServerFactory实现了ErrorPageRegistry接口):

idea创建springboot项目错误,springboot自定义注解处理异常

2.1 AbstractConfigurableWebServerFactory.addErrorPages

方法源码如下:

@Override
publicvoidaddErrorPages(ErrorPage...errorPages){
Assert.notNull(errorPages,"ErrorPagesmustnotbenull");
this.errorPages.addAll(Arrays.asList(errorPages));
}


也就说错误页面现在被放到了属性 private Set<ErrorPage> errorPages = new LinkedHashSet<>(); 中。

2.2 TomcatServletWebServerFactory.configureContext

创建完TomcatServletWebServerFactory后会调用configureContext方法,如下图所示:

idea创建springboot项目错误,springboot自定义注解处理异常

在configureContext方法中会获取错误页面然后逐个调用StandardContext.addErrorPage方法添加到其 ErrorPageSupport errorPageSupport 中。

configureContext方法中遍历错误页面如下所示:

for(ErrorPageerrorPage:getErrorPages()){
org.apache.tomcat.util.descriptor.web.ErrorPagetomcatErrorPage=neworg.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}


StandardContext.addErrorPage方法源码如下所示:

@Override
publicvoidaddErrorPage(ErrorPageerrorPage){
//Validatetheinputparameters
if(errorPage==null)
thrownewIllegalArgumentException
(sm.getString("standardContext.errorPage.required"));
Stringlocation=errorPage.getLocation();
if((location!=null)&&!location.startsWith("/")){
if(isServlet22()){
if(log.isDebugEnabled())
log.debug(sm.getString("standardContext.errorPage.warning",
location));
errorPage.setLocation("/"+location);
}else{
thrownewIllegalArgumentException
(sm.getString("standardContext.errorPage.error",
location));
}
}
//调用errorPageSupport.add
errorPageSupport.add(errorPage);
fireContainerEvent("addErrorPage",errorPage);
}



ErrorPageSupport.add方法如下所示:

publicvoidadd(ErrorPageerrorPage){
StringexceptionType=errorPage.getExceptionType();
if(exceptionType==null){
statusPages.put(Integer.valueOf(errorPage.getErrorCode()),errorPage);
}else{
exceptionPages.put(exceptionType,errorPage);
}
}


通过该方法可以看到,不止可以通过HTTP状态码定义错误页面,还可以通过异常类型进行定义。

那么ErrorPageSupport、statusPages、exceptionPages分别是什么呢?我们看下图示意:

idea创建springboot项目错误,springboot自定义注解处理异常

③ 错误页面如何被用到

在ResourceHttpRequestHandler.handleRequest方法处理请求时,找不到资源会调用 response.sendError 方法:

idea创建springboot项目错误,springboot自定义注解处理异常

这里只需要关注这一点,无需关注细节,我们继续往下走。。。。一直走到StandardHostValve.status方法。

StandardHostValve.status中会对响应状态码进行处理。

privatevoidstatus(Requestrequest,Responseresponse){

intstatusCode=response.getStatus();

//Handleacustomerrorpageforthisstatuscode
Contextcontext=request.getContext();
if(context==null){
return;
}

/*OnlylookforerrorpageswhenisError()isset.
*isError()issetwhenresponse.sendError()isinvoked.This
*allowscustomerrorpageswithoutrelyingondefaultfrom
*web.xml.
*/
if(!response.isError()){
return;
}
//这里会从errorPageSupport.find(errorCode)获取到错误页
//根据错误码,比如404从statusPages获取对应的ErrorPage对象
ErrorPageerrorPage=context.findErrorPage(statusCode);
if(errorPage==null){
//Lookforadefaulterrorpage
errorPage=context.findErrorPage(0);
}
if(errorPage!=null&&response.isErrorReportRequired()){
response.setAppCommitted(false);
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
Integer.valueOf(statusCode));

Stringmessage=response.getMessage();
if(message==null){
message="";
}
request.setAttribute(RequestDispatcher.ERROR_MESSAGE,message);
request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
errorPage.getLocation());
request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
DispatcherType.ERROR);


Wrapperwrapper=request.getWrapper();
if(wrapper!=null){
request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,
wrapper.getName());
}
request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
request.getRequestURI());

//这里很重要,将会尝试跳转到我们自定义错误请求页面
if(custom(request,response,errorPage)){
response.setErrorReported();
try{
response.finishResponse();
}catch(ClientAbortExceptione){
//Ignore
}catch(IOExceptione){
container.getLogger().warn("ExceptionProcessing"+errorPage,e);
}
}
}
}



如下图所示,在StandardHostValve.custom方法中将会调用ApplicationDispatcher.forwar进行请求转发。

idea创建springboot项目错误,springboot自定义注解处理异常

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:

https://blog.csdn.net/j080624/article/details/109197726