I. Giới thiệu
Tình huống xảy ra được miêu tả như dưới đây.
Đầu tiên bạn có một Spring Application với cấu trúc thư mục như sau:
Và được run thành công ready trên port 8487:
Tuy nhiên khi bạn thực hiện việc gọi API thì HTTP response status là 404? Ủa 😂😂?
II. Phân tích vấn đề
Chúng ta muốn biết vì sao xảy ra lỗi 404 thì chúng ta phải hình dung được flow của một HTTP request thực thi trong ứng dụng Spring Boot như thế nào trước.
Khi Spring Boot application nhận được một HTTP request thì có một thằng gọi là DispatcherServlet sẽ dispatching những request này đến các Controller tương ứng. Sau khi tìm được Controller sẽ phải tìm một handler method có thể handle request đó. Nếu tìm thấy handler method phù hợp thì DispatcherServlet sẽ gọi handler method và trả về response.
Handler method ở đây là method được annotated với @RequestMapping
annotation hay @GetMapping
annotation với HTTP request method (GET/POST..) và path cụ thể.
#1
@RequestMapping(value = "/404", method = RequestMethod.GET)
public Object demo404NotFound(){ return "404 not found";
} #2
@GetMapping("/404")
public Object demo404NotFound(){ return "404 not found";
}
Tuy nhiên để một request gọi được vào ứng dụng thì Spring Boot cần có một vài bước tiền xử lý trong quá trình startup application như là initHandlerMethods
. Phương thức initHandlerMethods
chịu trách nhiệm cho việc khởi tạo tất cả mọi handler methods trong Spring Boot.
Phương thức này ban đầu sẽ đi tìm những beans trong ApplicationContext annotated với @Controller annotation hay chứa @RequestMapping annotation. Đến đây nếu để ý chúng ta sẽ phát hiện ra nguyên nhân gây nên lỗi 404 kể trên đó là _404Controller
bean không được tìm thấy và đây chính là thủ phạm.
Nhưng chúng ta sẽ không chỉ dừng phân tích lại ở đó, sau khi phương thức initHandlerMethods
tìm được bean thõa mãn yêu cầu như @Controller/ @RestController.. thì với mỗi bean tìm được, phương thức đi tìm tiếp những method annotated với @RequestMapping annotation và add vào registry
để sử dụng.
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
Callstack
register:654, AbstractHandlerMethodMapping$MappingRegistry (org.springframework.web.servlet.handler)
registerHandlerMethod:332, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
registerHandlerMethod:420, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
registerHandlerMethod:76, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
lambda$detectHandlerMethods$2:299, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
accept:-1, 10885570 (org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$$Lambda$526)
forEach:684, LinkedHashMap (java.util)
detectHandlerMethods:297, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
processCandidateBean:266, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
initHandlerMethods:225, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
afterPropertiesSet:213, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
afterPropertiesSet:205, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
invokeInitMethods:1863, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1800, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:620, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:542, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 1896232624 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$227)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:955, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:918, AbstractApplicationContext (org.springframework.context.support)
refresh:583, AbstractApplicationContext (org.springframework.context.support)
refresh:147, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:734, SpringApplication (org.springframework.boot)
refreshContext:408, SpringApplication (org.springframework.boot)
run:308, SpringApplication (org.springframework.boot)
run:1306, SpringApplication (org.springframework.boot)
run:1295, SpringApplication (org.springframework.boot)
main:14, SpringBean404Application (com.logbasex)
Sau khi xong phần startup application và initHandlerMethods thì chúng ta sẽ tiến hành cắm debugging point ở controller và call API để xem cách Spring Boot nhận và xử lý HTTP request như thế nào.
Callstack:
createWithResolvedBean:372, HandlerMethod (org.springframework.web.method)
getHandlerInternal:384, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
getHandlerInternal:125, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)
getHandlerInternal:67, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)
getHandler:498, AbstractHandlerMapping (org.springframework.web.servlet.handler)
getHandler:1264, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1046, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:750, Thread (java.lang)
References
===
Thanks for reading.