為何一次請求會有兩次HttpServlet:service呼叫?
為什麼只訪問了http://localhost:8080/a.txt
,但Arthas的trace
命令打印出了兩個請求樹?
然後我去自己試了下,發現還真的是這個情況,trace日誌如下:
---ts=2019-02-24 18:05:20;thread_name=http-nio-8080-exec-1;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6601a0f8 `---[22.125552ms] javax.servlet.http.HttpServlet:service() `---[22.048005ms] javax.servlet.http.HttpServlet:service() `---[21.977486ms] org.springframework.web.servlet.FrameworkServlet:service() +---[0.015829ms] javax.servlet.http.HttpServletRequest:getMethod() +---[0.023379ms] org.springframework.http.HttpMethod:resolve() `---[21.847595ms] org.springframework.web.servlet.HttpServletBean:service() `---…… `---ts=2019-02-24 18:05:20;thread_name=http-nio-8080-exec-1;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6601a0f8 `---[108.691709ms] javax.servlet.http.HttpServlet:service() `---[108.658563ms] javax.servlet.http.HttpServlet:service() `---[108.612929ms] org.springframework.web.servlet.FrameworkServlet:service() +---[0.024764ms] javax.servlet.http.HttpServletRequest:getMethod() +---[0.004569ms] org.springframework.http.HttpMethod:resolve() `---[108.540682ms] org.springframework.web.servlet.HttpServletBean:service() `---……
然後,過去翻了一下程式碼才發現,是org.apache.catalina.core.StandardHostValve#invoke
方法的邏輯:
//正常的處理請求(這是第一個HttpServlet:service呼叫)
context.getPipeline().getFirst().invoke(request, response);
// 如果response是失敗的,那麼再處理ErrorPage的邏輯
if (response.isErrorReportRequired()) {
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
在status方法中,獲取ErrorPage
,然後給request
設定javax.servlet.error.*
的屬性,然後再forward到HttpServlet:service
。這兒就會出現第二個HttpServlet:service
請求。
那麼問題來了,這個行為是規範裡面有約定嗎?
翻了翻Java Servlet Specification ,第10.9.2小節有說:
To allow developers to customize the appearance of content returned to a Web client when a servlet generates an error, the deployment descriptor defines a list of error page descriptions. The syntax allows the configuration of resources to be returned by the container either when a servlet or filter calls sendError on the response for specific status codes, or if the servlet generates an exception or error that propagates to the container. If the sendError method is called on the response, the container consults the list of error page declarations for the Web application that use the status-code syntax and attempts a match. If there is a match, the container returns the resource as indicated by the location entry. The Web application may have declared error pages using the exception-typeelement. In this case the container matches the exception type by comparing the exception thrown with the list of error-page definitions that use the exception-typeelement. A match results in the container returning the resource indicated in the location entry. The closest match in the class hierarchy wins.
簡而言之,如果通過sendError方法設定了錯誤,就會被對應的ErrorPage物件處理,就會轉發到對應的location去處理。
其實到這兒,還有很多問題沒有解決,比如:
- ErrorPage是如何設定的?
- war模式下,容器是如何知曉spring的入口的?
不過,慢慢來吧。
最近在寫Java的時候,同時使用VSCode和IntelliJ IDEA,發現IDEA在很多細節做了優化,比如debug的時候,IDEA是選中了某一個執行緒,展示某一個執行緒的呼叫棧;而VSCode則是用一個Tree來表示,第一級是執行緒,更深的則是呼叫棧。還有,IDEA在展示Map等資料結構的時候,直接展示key、value對,而VSCode則暴露了很多內部實現,很不直觀。VSCode還有很多路要走啊……