springboot项目中出现Whitelabel Error Page

  • Post author:
  • Post category:其他




访问springboot项目显示结果

在这里插入图片描述

出现白页的原因:

请求进入org.springframework.web.servlet.DispatcherServlet#doDispatch方法中:

#通过请求查询可供处理的handler
mappedHandler = getHandler(processedRequest);

在这里插入图片描述

默认注册了4个handlerMapping,分别是:

  1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  2. org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
  3. org.springframework.web.servlet.function.support.RouterFunctionMapping
  4. org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
  5. org.springframework.boot.autoconfigure.web.servlet.WelcomePageHandlerMapping

  • 遗留问题:这5个类是何时注册到spring?分别有何作用?它们的顺序代表了什么含义?

    在这里插入图片描述

    在SimpleUrlHandlerMapping中有/**的路径匹配,所以当前的handler为SimpleUrlHandlerMapping,当前的handlerAdapter是org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

#org.springframework.web.servlet.resource.ResourceHttpRequestHandler
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		// For very general mappings (e.g. "/") we need to check 404 first  找不到资源返回
		Resource resource = getResource(request);
		if (resource == null) {
			logger.debug("Resource not found");
# 找不到资源返回设置设置状态码为HttpServletResponse.SC_NOT_FOUND(HttpServletResponse.SC_NOT_FOUND代表的值为404)
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		#javax.servlet.http.HttpServletResponse#SC_NOT_FOUND=404
#org.apache.catalina.connector.Response#sendError(int, java.lang.String)
    public void sendError(int status, String message) throws IOException {

        if (isCommitted()) {
            throw new IllegalStateException
                (sm.getString("coyoteResponse.sendError.ise"));
        }

        // Ignore any call from an included servlet
        if (included) {
            return;
        }

        setError();
		#设置响应状态为404
        getCoyoteResponse().setStatus(status);
        getCoyoteResponse().setMessage(message);

        // Clear any data content that has been buffered
        resetBuffer();

        // Cause the response to be finished (from the application perspective)
        setSuspended(true);
    }		

  • org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter这个类有什么用?何时初始化

  • org.springframework.web.servlet.resource.ResourceHttpRequestHandler#handleRequest这个类有何作用?何时初始化

结果Tomcat的层层处理,进入到 org.apache.catalina.core.StandardHostValve

在这里插入图片描述


  • Tomcat中StandardHostValve如何创建?在该请求中的具体执行流程是咋样的?

可以看到location=/error,进行forward到该路径,这个就是白页上出现/error的原因。


  • 这个forward是何时触发的

    重新进入springmvc容器,/error路径匹配到org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml方法
	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		#返回给error的view
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

然后进入到渲染方法中,

在这里插入图片描述


  • BasicErrorController的作用?启动流程是啥?,啥时候回调用该方法

进入org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView#render的方法中

	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
				throws Exception {
			if (response.isCommitted()) {
				String message = getMessage(model);
				logger.error(message);
				return;
			}
			response.setContentType(TEXT_HTML_UTF8.toString());
			StringBuilder builder = new StringBuilder();
			Date timestamp = (Date) model.get("timestamp");
			Object message = model.get("message");
			Object trace = model.get("trace");
			if (response.getContentType() == null) {
				response.setContentType(getContentType());
			}
			#在该方法中可以看到白页上的熟悉的提示信息
			builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
					"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
					.append("<div id='created'>").append(timestamp).append("</div>")
					.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
					.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
			if (message != null) {
				builder.append("<div>").append(htmlEscape(message)).append("</div>");
			}
			if (trace != null) {
				builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
			}
			builder.append("</body></html>");
			response.getWriter().append(builder.toString());
		}

  • ErrorMvcAutoConfiguration有啥作用?何时初始化?何时调用?

  • StaticView有啥作用?何时初始化?何时调用?

  • 该处的model中的path是为啥是原始的值,如何保留原始的值?

在这里插入图片描述

总结:出现白页的主要流程是在DispatcherServlet中的doDispatch方法中,根据得到的handler没有在ResourceHttpRequestHandler的方法中找到resource,然后将response的状态设置为404。然后forward到/error路径,重新进入doDispatch方法寻找可以匹配的方法,和spring boot自动装配的用于错误处理的BasicErrorController的方法,然后找到viewName为error的staticView,在view中有关于所有的错误信息的定义。然后返回给首页。


  • response的状态设置为404后,如何和接下来的转发/error路径关联起来,流程是啥?
#以下为debug到doDispatch时的调用栈信息
getHandler:1233, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1016, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:634, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:103, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:103, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:712, ApplicationDispatcher (org.apache.catalina.core)
processRequest:461, ApplicationDispatcher (org.apache.catalina.core)
doForward:384, ApplicationDispatcher (org.apache.catalina.core)
forward:312, ApplicationDispatcher (org.apache.catalina.core)
custom:394, StandardHostValve (org.apache.catalina.core)
status:253, StandardHostValve (org.apache.catalina.core)
invoke:175, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:373, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:868, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1590, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

  • 发起一次http请求,到进入doDispatch所经历的过程是啥?

那该如和解决那?

  • 解决方案一:使用拦截器
#定义拦截器
public class CustomHandlerInterceptor implements HandlerInterceptor {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
        if (modelAndView != null) {
            modelAndView.setViewName("err");
//            modelAndView.setViewName("custiomError");
        }
    }

}

#注册拦截器
 @Bean
    WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new CustomHandlerInterceptor()).addPathPatterns("/**");
                super.addInterceptors(registry);
            }
        };
    }


#yaml中的配置
spring:
  mvc:
    view:
      prefix: G:\code\visitstatistic\src\main\resources\templates\
      suffix: .html

在这里插入图片描述

结果是:
在这里插入图片描述


  • 但是Tomcat返回的页面如下是如何产生的:

    在这里插入图片描述

    总共进行了三次转发:/count/reuse/2——>error——>?
  • 添加ErrorPage
#配置的bean如下
    @Bean
    WebServerFactoryCustomizer<ConfigurableWebServerFactory> embeddedServletContainerCustomizer() {
        return container->{
            ErrorPage errorPage = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
            container.addErrorPages(errorPage);
        };
    }

#配置的getMapping为
  @GetMapping("404")
    public String query() {
        return "com.demo.visitstatistic.NotController.query 404 页面";
    }

返回的页面如下:

在这里插入图片描述

结果:返回给路径为404的方法的返回值。

流程为:count/reuse/2——》/404(ErrorPage中的配置的路径)


  • ErrorPage实际的工作原理是啥?

  • WebServerFactoryCustomizer如何使用?在该过程中如何发挥作用
  • 解决方法三:当访问/error时跳转到自己配置的controller方法
#配置的方法为
    @GetMapping("error")
    @ResponseBody
    public String errorPath() {
        return "com.demo.visitstatistic.CustomErrorController.errorPath  ";
    }

结果为:

在这里插入图片描述


  • 即使配置了自己的/error的controller,但是spring仍旧使用默认的路径

    从下图可以看到spring的方法中总共有三个路径为/error

    在这里插入图片描述

    修补方案一:使用SimpleUrlHandlerMapping
#配置SimpleUrlHandlerMapping
 @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
        return new SimpleUrlHandlerMapping();
    }

@RestController
public class NotController { 
    @Autowired
    private SimpleUrlHandlerMapping simpleUrlHandlerMapping;

    @PostConstruct
    public void init() {
        Map<String, Object> map = new HashMap<>();
        map.put(ERROR_PATH, this);
        simpleUrlHandlerMapping.setUrlMap(map);
        simpleUrlHandlerMapping.initApplicationContext();
    }

    @GetMapping("error")
    public String error() {
        return "com.demo.visitstatistic.NotController.error";
    }   

}

结果:配置的没有生效

在这里插入图片描述


  • 为啥没有生效?SimpleUrlHandlerMapping如何使用?原理是啥?它的同类有哪些?

  • 修补方法二:仿照默认的controller,继承BasicErrorController

#自定的controller,继承自ErrorController
@Controller
public class CustomErrorController implements ErrorController {

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

    @GetMapping("error")
    @ResponseBody
    public String errorPath() {
        return "com.demo.visitstatistic.CustomErrorController.errorPath  ";
    }

    private static final String ERROR_PATH = "/error";

}

结果成功,显示如下:

在这里插入图片描述


  • 现在有三个方法的路径为/error,为啥自己配置的会生效??

  • 不同的controller中的两个方法的路径相同,会抛 Ambiguous mapping,产生这个异常的流程是啥?为啥三个/error不会跑这个异常?


参考的博客



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