初始化时机
SpringBoot的嵌入式Tomcat,将Spring与Tomcat做了深度的融合,使得初始化流程与外置Tomcat的Spring应用程序有了较大不同。
ServletWebServerApplicationContext
ServletWebServerApplicationContext类就是负责创建并启动嵌入式的Tomcat服务器。
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
protected ServletWebServerFactory getWebServerFactory() {
return (ServletWebServerFactory) getBeanFactory().getBean(AbstractServletWebServerFactory.BEAN_NAME);
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 这里要加几行注释,写明这个类是干什么的。
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
自动配置机制跳出懒加载
在传统的外置Tomcat的Spring Web程序中,可以通过配置load-on-startup控制Servlet的初始化时机。
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>-1</load-on-startup> <!-- 默认懒加载,改为1则立即初始化 -->
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在SpringBoot嵌入式Tomcat中,spring-boot-autoconfigure包下的META-INF/spring.factories中标记的自动配置类DispatcherServletAutoConfiguration负责定义DispatcherServlet。
就在ServletWebServerApplicationContext的getWebServerFactory()方法中,getBeanFactory().getBean(AbstractServletWebServerFactory.BEAN_NAME)会触发级联加载,其中包括由DispatcherServletAutoConfiguration定义的Bean。
当然上述流程是非常复杂的,里面有很多细节都还没有理清楚,应该可以从Tomcat启动的视角,后续再回头来看这个问题。
请求处理
那么,上面的流程指出,在SpringBoot程序启动完成时,DispatcherServlet就已经初始化好了,已经处于可以随时处理请求的状态了。
处理流程
1、接收分发
请求首先由 Tomcat 的 Connector 接收,并传递给合适的 Context 和 Wrapper 进行处理。对于 Spring Boot 应用,DispatcherServlet 一般被映射到根路径 (/),所以几乎所有请求都会通过 DispatcherServlet。
当然这部分应该算是Tomcat部分的内容,先按下不表。
2、请求接收
和其他HttpServlet一样,DispatcherServlet接收Http请求的方法都是doService():
void doService(HttpServletRequest request, HttpServletResponse response);
而在doService()方法中,会调用doDispatch()方法真正处理请求:
void doDispatch(HttpServletRequest request, HttpServletResponse response);
doDispatch()处理时序如下。其中HandlerExecutionChain用到责任链模式,拦截器数组中每个拦截器都可以处理请求的某一阶段,避免请求发送者和接收者之间的耦合;HandlerAdapter用到适配器模式,用于处理不同类型的Handler,可以统一的方式处理不同类型请求。
3、路由Controller
在getHandler()方法中,通过debug走到getHandlerInternal方法中。这里,lookupPath就是url,lookupHandlerMethod()方法会获取到url路径对应后端的Controller处理方法。
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
4、获取适配器
经过getHandlerAdapter()方法,会获取到RequestMappingHandlerAdapter,而这个适配器是请求处理的核心。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
5、请求处理
请求处理是在RequestMappingHandlerAdapter中执行的,一路debug可以看到:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// ...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// ...
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这里就获取到了Controller方法的返回值了
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// ...
}
6、返回值
@ResponseBody 注解的作用是指示 Spring MVC 将控制器的方法返回值直接写入 HTTP 响应体,而不是解析为视图名进行跳转。
HandlerMethodReturnValueHandlerComposite 按顺序调用合适的 HandlerMethodReturnValueHandler, 其中RequestResponseBodyMethodProcessor 检测 @ResponseBody 注解,并处理返回值。 消息转换:
HttpMessageConverter 接口及其实现类: 将返回值转换为指定格式并写入响应体。