SpringMVC
框架是Spring
框架中web
模塊死遭,時(shí)下常用來構(gòu)建web
應(yīng)用裙盾。在應(yīng)用之余绞愚,也一直想要搞明白SpringMVC
中是如何接受處理請(qǐng)求的?
SpingMVC 初始化
Spring
框架和其他框架類似史翘,都是配置元素集中于xml配置文件中枉长,在框架初始化的時(shí)候,加載配置文件琼讽,解析文件必峰,生成對(duì)應(yīng)的配置。SpringMVC
框架是依托于Spring
容器钻蹬。Spring
初始化的過程其實(shí)就是IoC
容器啟動(dòng)的過程吼蚁,也就是上下文建立的過程。
ServletContext
每一個(gè)web應(yīng)用中都有一個(gè)Servlet上下文脉让。servlet
容器提供一個(gè)全局上下文的環(huán)境桂敛,這個(gè)上下文環(huán)境將成為其他IoC
容器的宿主環(huán)境,例如:WebApplicationContext
就是作為ServletContext
的一個(gè)屬性存在溅潜。
WebApplicationContext
在使用SpringMVC
的時(shí)候,通常需要在web.xml
文件中配置:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener
實(shí)現(xiàn)了ServletContextListener
接口薪伏,在SpringMVC
中作為監(jiān)聽器的存在滚澜,當(dāng)servlet
容器啟動(dòng)時(shí)候,會(huì)調(diào)用contextInitialized
進(jìn)行一些初始化的工作嫁怀。而ContextLoaderListener
中contextInitialized
的具體實(shí)現(xiàn)在ContextLoader
類中设捐。
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
上面的部分代碼可以看出,初始化時(shí)候通過createWebApplicationContext(servletContext);
聲明一個(gè)WebApplicationContext
并賦值給ServletContext
的org.springframework.web.context.WebApplicationContext.ROOT
屬性塘淑,作為WebApplicationContext
的根上下文(root context)萝招。
DispatcherServlet
在加載完<context-param>
和<listener>
之后,容器將加載配置了load-on-startup
的servlet
存捺。
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
DispatcherServlet
在初始化的過程中槐沼,會(huì)建立一個(gè)自己的IoC
容器上下文Servlet WebApplicationContext
曙蒸,會(huì)以ContextLoaderListener
建立的根上下文作為自己的父級(jí)上下文。DispatcherServlet
持有的上下文默認(rèn)的實(shí)現(xiàn)類是XmlWebApplicationContext
岗钩。Servlet
有自己獨(dú)有的Bean
空間纽窟,也可以共享父級(jí)上下文的共享Bean
,當(dāng)然也存在配置有含有一個(gè)root WebApplicationContext
配置兼吓。其關(guān)系如下圖所示臂港,后面也還會(huì)詳細(xì)介紹DispatcherServlet
這個(gè)類。
DispatcherServlet類
DispatcherServlet
最為SpringMVC核心類视搏,起到了前端控制器(Front controller)的作用审孽,負(fù)責(zé)請(qǐng)求分發(fā)等工作。
從類圖中可以看出浑娜,DispatcherServlet
的繼承關(guān)系大致如此:
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> GenericServlet
從繼承關(guān)系上可以得出結(jié)論佑力,DispatcherServlet
本質(zhì)上還是一個(gè)Servlet
。Servlet
的生命周期大致分為三個(gè)階段:
- 初始化階段 init方法
- 處理請(qǐng)求階段 service方法
- 結(jié)束階段 destroy方法
這里就重點(diǎn)關(guān)注DispatcherServlet
在這三個(gè)階段具體做了那些工作棚愤。
DispatcherServlet初始化
DispatcherServlet
的init()
的實(shí)現(xiàn)在其父類HttpServletBean
中搓萧。
public final void init() throws ServletException {
...
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean();
...
}
以上部分源碼描述的過程是通過讀取<init-param>
的配置元素,讀取到DispatcherServlet
中宛畦,配置相關(guān)bean
的配置瘸洛。完成配置后調(diào)用initServletBean
方法來創(chuàng)建Servlet WebApplicationContext
。
initServletBean
方法在FrameworkServlet
類中重寫了:
protected final void initServletBean() throws ServletException {
...
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
...
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
上文提到Servlet
容器在啟動(dòng)的時(shí)候次和,通過ContextLoaderListener
創(chuàng)建一個(gè)根上下文反肋,并配置到ServletContext
中√な可以看出FrameworkServlet
這個(gè)類做的作用是用來創(chuàng)建WebApplicationContext
上下文的石蔗。大致過程如下:
- 首先檢查
webApplicationContext
是否通過構(gòu)造函數(shù)注入,如果有的話畅形,直接使用养距,并將根上下文設(shè)置為父上下文。 - 如果
webApplicationContext
沒有注入日熬,則檢查是否在ServletContext
已經(jīng)注冊(cè)過棍厌,如果已經(jīng)注冊(cè)過,直接返回使用竖席。 - 如果沒有注冊(cè)過耘纱,將重新新建一個(gè)
webApplicationContext
。將根上下文設(shè)置為父級(jí)上下文毕荐。 - 不管是何種策略獲取的
webApplicationContext
束析,都將會(huì)調(diào)用onRefresh
方法,onRefresh
方法會(huì)調(diào)用initStrategies
方法憎亚,通過上下文初始化HandlerMappings
员寇、HandlerAdapters
弄慰、ViewResolvers
等等。 - 最后丁恭,同樣會(huì)將所得
webApplicationContext
注冊(cè)到ServletContext
中曹动。
而initFrameworkServlet()
默認(rèn)的實(shí)現(xiàn)是空的。這也可算是SpingMVC
留的一個(gè)擴(kuò)展點(diǎn)牲览。
DispatcherServlet處理請(qǐng)求
縱觀SpringMVC
的源碼墓陈,大量運(yùn)用模板方法的設(shè)計(jì)模式。Servlet
的service
方法也不例外第献。FrameworkServlet
類重寫service
方法:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
如果請(qǐng)求的方法是PATCH
或者空贡必,直接調(diào)用processRequest
方法(后面會(huì)詳細(xì)解釋);否則庸毫,將調(diào)用父類的service
的方法仔拟,即HttpServlet
的service
方法, 而這里會(huì)根據(jù)請(qǐng)求方法,去調(diào)用相應(yīng)的doGet
飒赃、doPost
利花、doPut
......
而doXXX
系列方法的實(shí)現(xiàn)并不是HttpServlet
類中,而是在FrameworkServlet
類中载佳。在FrameworkServlet
中doXXX
系列實(shí)現(xiàn)中炒事,都調(diào)用了上面提到的processRequest
方法:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
為了避免子類重寫它,該方法用final
修飾蔫慧。
- 首先調(diào)用
initContextHolders
方法,將獲取到的localeContext
挠乳、requestAttributes
、request
綁定到線程上姑躲。 - 然后調(diào)用
doService
方法睡扬,doService
具體是由DispatcherServlet
類實(shí)現(xiàn)的。 -
doService
執(zhí)行完成后黍析,調(diào)用resetContextHolders
卖怜,解除localeContext
等信息與線程的綁定。 - 最終調(diào)用
publishRequestHandledEvent
發(fā)布一個(gè)處理完成的事件阐枣。
DispatcherServlet
類中的doService
方法實(shí)現(xiàn)會(huì)調(diào)用doDispatch
方法韧涨,這里請(qǐng)求分發(fā)處理的主要執(zhí)行邏輯。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch
主要流程是:
- 先判斷是否
Multipart
類型的請(qǐng)求侮繁。如果是則通過multipartResolver
解析request
- 通過
getHandler
方法找到從HandlerMapping
找到該請(qǐng)求對(duì)應(yīng)的handler
,如果沒有找到對(duì)應(yīng)的handler
則拋出異常。 - 通過
getHandlerAdapter
方法找到handler
對(duì)應(yīng)的HandlerAdapter
- 如果有攔截器如孝,執(zhí)行攔截器
preHandler
方法 -
HandlerAdapter
執(zhí)行handle
方法處理請(qǐng)求宪哩,返回ModelAndView
。 - 如果有攔截器第晰,執(zhí)行攔截器
postHandle
方法 - 然后調(diào)用
processDispatchResult
方法處理請(qǐng)求結(jié)果锁孟,封裝到response
中彬祖。
SpingMVC 請(qǐng)求處理流程
SpringMVC
框架是圍繞DispatcherServlet
設(shè)計(jì)的。DispatcherServlet
負(fù)責(zé)將請(qǐng)求分發(fā)給對(duì)應(yīng)的處理程序品抽。從網(wǎng)上找了兩個(gè)圖储笑,可以大致了解SpringMVC
的框架對(duì)請(qǐng)求的處理流程。
- 用戶發(fā)送請(qǐng)求圆恤,
Front Controller
(DispatcherServlet
)根據(jù)請(qǐng)求信息將請(qǐng)求委托給對(duì)應(yīng)的Controller
進(jìn)行處理突倍。 -
DispatcherServlet
接收到請(qǐng)求后,HandlerMapping
將會(huì)把請(qǐng)求封裝為HandlerExecutionChain
盆昙,而HandlerExecutionChain
包含請(qǐng)求的所有信息羽历,包括攔截器、Handler處理器等淡喜。 -
DispatcherServlet
會(huì)找到對(duì)應(yīng)的HandlerAdapter
秕磷,并調(diào)用對(duì)應(yīng)的處理方法,并返回一個(gè)ModelAndView
對(duì)象炼团。 -
DispatcherServlet
會(huì)將ModelAndView
對(duì)象傳入View
層進(jìn)行渲染澎嚣。 - 最終
DispatcherServlet
將渲染好的response
返回給用戶。
總結(jié)
本文主要分析SpringMVC
中DispatcherServlet
的初始化瘟芝、請(qǐng)求流傳過程等易桃。
發(fā)現(xiàn)了SpringMVC
中在DispatcherServlet
的實(shí)現(xiàn)過程中運(yùn)用了模板方法設(shè)計(jì)模式,看到SpringMVC
中留給用戶可擴(kuò)展的點(diǎn)也有很多模狭,體會(huì)到Open for extension, closed for modification
的設(shè)計(jì)原則颈抚。
本文只關(guān)注了DispatcherServlet
主流程,忽略了很多寶貴的細(xì)枝末節(jié)嚼鹉,如:HandlerMapping
贩汉、HandlerExecutionChain
、HandlerAdapter
等锚赤。后面有機(jī)會(huì)定會(huì)追本溯源匹舞。