SpringBoot自定义错误页面与原理讲解

简介: SpringBoot自定义错误页面与原理讲解

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

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

【1】解决方法


① 注册错误页面

如下所示:

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        ErrorPage error400Page = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/404");
        ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
        ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
        registry.addErrorPages(error400Page,error404Page,error500Page);
    }
}


② controller进行拦截

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

@RequestMapping("/error/{status}")
public String errorPage(@PathVariable Integer status){
    switch (status){
        case 401:
        case 400:return "/error/404";
        case 500:return "/error/500";
        default:return "/error/default";
    }
}


那么原理呢?

【2】原理讲解

① 启动SpringBoot,注册错误页面

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


1.1AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization

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

@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
  throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
  Object current = processor.postProcessBeforeInitialization(result, beanName);
  if (current == null) {
    return result;
  }
  result = current;
}
return result;
}

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


1.2 ErrorPageRegistrarBeanPostProcessor.postProcessBeforeInitialization

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

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  if (bean instanceof ErrorPageRegistry) {
    postProcessBeforeInitialization((ErrorPageRegistry) bean);
  }
  return bean;
}

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

private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
  for (ErrorPageRegistrar registrar : getRegistrars()) {
    registrar.registerErrorPages(registry);
  }
}

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

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;
}

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


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

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


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


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

2.1 AbstractConfigurableWebServerFactory.addErrorPages

方法源码如下:

@Override
public void addErrorPages(ErrorPage... errorPages) {
  Assert.notNull(errorPages, "ErrorPages must not be null");
  this.errorPages.addAll(Arrays.asList(errorPages));
}


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


2.2 TomcatServletWebServerFactory.configureContext

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


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


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

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


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

@Override
 public void addErrorPage(ErrorPage errorPage) {
     // Validate the input parameters
     if (errorPage == null)
         throw new IllegalArgumentException
             (sm.getString("standardContext.errorPage.required"));
     String location = errorPage.getLocation();
     if ((location != null) && !location.startsWith("/")) {
         if (isServlet22()) {
             if(log.isDebugEnabled())
                 log.debug(sm.getString("standardContext.errorPage.warning",
                              location));
             errorPage.setLocation("/" + location);
         } else {
             throw new IllegalArgumentException
                 (sm.getString("standardContext.errorPage.error",
                               location));
         }
     }
//调用errorPageSupport.add
     errorPageSupport.add(errorPage);
     fireContainerEvent("addErrorPage", errorPage);
 }

ErrorPageSupport.add方法如下所示:

public void add(ErrorPage errorPage) {
     String exceptionType = errorPage.getExceptionType();
     if (exceptionType == null) {
         statusPages.put(Integer.valueOf(errorPage.getErrorCode()), errorPage);
     } else {
         exceptionPages.put(exceptionType, errorPage);
     }
 }


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

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


③ 错误页面如何被用到

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


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

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

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;
     }
//这里会从errorPageSupport.find(errorCode)获取到错误页
//根据错误码,比如404从statusPages获取对应的ErrorPage对象
     ErrorPage errorPage = context.findErrorPage(statusCode);
     if (errorPage == null) {
         // Look for a default error page
         errorPage = context.findErrorPage(0);
     }
     if (errorPage != null && response.isErrorReportRequired()) {
         response.setAppCommitted(false);
         request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
                           Integer.valueOf(statusCode));
         String message = 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);
         Wrapper wrapper = 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 (ClientAbortException e) {
                 // Ignore
             } catch (IOException e) {
                 container.getLogger().warn("Exception Processing " + errorPage, e);
             }
         }
     }
 }

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


目录
相关文章
|
6天前
|
Java
SpringBoot自动装配的原理
在SpringBoot项目的启动引导类上都有一个注解@SpringBootApplication 这个注解是一个复合注解, 其中有三个注解构成 , 分别是 ● @SpringBootConfiguration : 是@Configuration的派生注解 , 标注当前类是一个SpringBoot的配置类 ● @ComponentScan : 开启组件扫描, 默认扫描的是当前启动引导了所在包以及子包 ● @EnableAutoConfiguration : 开启自动配置(自动配置核心注解) 2.在@EnableAutoConfiguration注解的内容使用@Import注解导入了一个AutoC
|
1月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
87 12
|
4月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
75 0
|
5天前
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——拦截自定义异常
本文介绍了在实际项目中如何拦截自定义异常。首先,通过定义异常信息枚举类 `BusinessMsgEnum`,统一管理业务异常的代码和消息。接着,创建自定义业务异常类 `BusinessErrorException`,并在其构造方法中传入枚举类以实现异常信息的封装。最后,利用 `GlobalExceptionHandler` 拦截并处理自定义异常,返回标准的 JSON 响应格式。文章还提供了示例代码和测试方法,展示了全局异常处理在 Spring Boot 项目中的应用价值。
18 0
|
1月前
|
Java Maven 开发者
编写SpringBoot的自定义starter包
通过本文的介绍,我们详细讲解了如何创建一个Spring Boot自定义Starter包,包括自动配置类、配置属性类、`spring.factories`文件的创建和配置。通过自定义Starter,可以有效地复用公共配置和组件,提高开发效率。希望本文能帮助您更好地理解和应用Spring Boot自定义Starter,在实际项目中灵活使用这一强大的功能。
58 17
|
2月前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
90 17
springboot自动配置原理
|
13天前
|
JavaScript 前端开发 Java
Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use;端口冲突的原理与解决方案
本文解决了Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use的问题,并通过介绍端口的使用原理和操作系统的端口管理机制,可以更有效地解决端口冲突问题,并确保Web服务器能够顺利启动和运行。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
3月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
3月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
258 14
|
4月前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
89 17

热门文章

最新文章