访问springboot项目显示结果
出现白页的原因:
请求进入org.springframework.web.servlet.DispatcherServlet#doDispatch方法中:
#通过请求查询可供处理的handler
mappedHandler = getHandler(processedRequest);
默认注册了4个handlerMapping,分别是:
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
- org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
- org.springframework.web.servlet.function.support.RouterFunctionMapping
- org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
- 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不会跑这个异常?