SpringMVC原始碼分析
1 Servlet功能分析
我們知道,WEB應用一般是打包成WAR包然後部署到Tomcat等應用伺服器上去的;部署完成後,通過URL訪問時,一般是http://ip:port/app這種訪問路徑。Tomcat根據路徑中埠後的第一個單詞並結合該應用的web.xml檔案,可以找到哪個應用的哪個Servlet需要處理該URL請求。
在Spring之前,如果要開發一個處理Http請求的功能,需要做兩項工作:一是開發繼承於Servlet類的業務處理Servlet;二是在Web.xml中註冊並對映該Servlet處理的請求路徑。
我們先來建立一個簡單原始的測試工程(通過Itellij測試),以瞭解原始Servlet的配置及生效過程,GITHUB地址:
1.1 原始的WEB工程
第一步:配置Itellij Tomcat執行環境(Tomcat9):
第二步:通過Itellij建立一個空白工程learn;
第三步:新增Module,名稱為testWebApp;
Mvn初始化完成後的工程結構如下所示:
第四步:構建工程,按下圖進行:
第五步:執行Tomcat,將會跳轉到首頁;
第六步:定義測試Servlet
此處使用Kotlin,並引入log4j等日誌元件,具體的MVN配置檔案見GITHUB工程。
Servlet程式碼見下文,需要繼承自HttpServlet;
class TestServlet: HttpServlet() {
val logger: Logger = LoggerFactory.getLogger(TestServlet::class.java)
override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
logger.info("Test servlet begin to execute!")
response.outputStream.println("TestServlet" )
logger.info("Test servlet has been executed!")
}
}
該Servlet實現很簡單的向前臺返回TestServlet字串的過程;
此時Servlet定義即完成;但僅僅這樣還不夠,Tomcat並不知道什麼情況下來執行這個Servlet; 這個時候就需要在Web.xml檔案中進行Servlet的配置了,具體的配置見下文:
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.liuqi.learn.testWebApp.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
以上配置主要包含兩個,一是Servlet的定義,名稱以及它後臺對應的Java類;二是Servlet與URL的對映關係,這裡表示將它對映到/test這個地址上。
這個時候我們啟動Tomcat,然後開啟連結:http://localhost:8080/test,就會看到瀏覽器輸出的了內容為TestServlet的頁面。
經過以上幾個步驟,一個最原始的不使用任何框架的WEB應用即構建完成。
經過以上分析,WEB應用一般就是一系列Servlet的集合;想象下,如果我們要完成A與B兩個功能,那我們就可以定義兩個Servlet並分別進行路徑對映; 但這樣的話功能越多Servlet就會越來越多,配置檔案也越來越複雜;如果在此基礎上進行抽象,Servlet只定義一個,在Servlet內部再根據URL進行功能分發,這樣就會簡單很多了。
SpringMVC實際就是這樣做的,它定義了一個名稱為DispatcherServlet的Servlet,用於接收請求並進行分發處理。
1.2 SpringMVC配置
上文已經分析了,SpringMVC的入口實際就是實現了一個名稱為DispatcherServlet的Servlet。
使用Spring而非Spring Boot的實現時,需要在Web.xml中配置該DispatcherServlet;
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
使用Spring Boot的載入過程將會在後續進行分析,此處暫不涉及。
1.3 Itellij查詢方法實現過程
在Java類中,可能出現A繼承B,B繼承C,C繼承D;D中有多個方法,有的在C中實現,有的在B,而有的在C;
像這種情況,在A中需要查詢某個方法的實現在其父類的哪個類中,可通過Ctrl+F12的快捷鍵來實現,其介面如下:
如需要在DispatcherServlet中查詢Service的實現,我們按快捷鍵彈出查詢視窗後,輸入Service,會將名稱中包含Service的方法全部查找出來;如果選中了”Inherited members”,則其所有父類中符合條件的方法全部都會搜尋到,再滑鼠選中記錄後就可以方便的跳轉到需要查詢的方法實現的地方。
如上圖中表示Service在FrameworkServlet及HttpServlet中都有實現,通過檢視這兩個地方可以清楚的知道FrameworkServlet中的實現為最終實現。
這個操作在閱讀Spring原始碼時會經常使用到,後續不再說明。
2 SpringMVC分析
2.1 關鍵類分析
DispatcherServlet收到請求後,根據它本身所維護的URL路徑對映關係,決定呼叫哪個Controller來進行請求響應;其中涉及到的關鍵類見下表:
類 | 說明 |
---|---|
HandlerMapping | 將請求對映到相應的處理器(Controller),在執行實際處理器前後會執行一系列的前置和後置攔截器; |
HandlerAdapter | 呼叫真正需要呼叫的處理器的處理方法; |
ViewResolver | 將Handler執行後返回的字串檢視名稱對映到實際的頁面或者其它的檢視上去; |
WebApplicationContext | 為Web應用提供配置功能; |
ServletContext | 提供了一系列方法以供Servlet與應用伺服器之間進行互動;每個應用每個虛擬機器啟動一個ServletContext物件; 因此不同Tomcat中的ServletContext是不能共享的; |
我們先來看下幾個關鍵類在響應請求時是如何相互合作的:
在後面的分析中我們將會看到這個請求響應過程是如何進行的。
2.2 生效過程原始碼分析
Servlet的關鍵方法為init與service;Init方法用於初始化ApplicationContext、BeanFactory等物件; 而Service用於響應請求。 我們將針對DispatcherServlet的init與service方法進行分析,以瞭解SpringMVC的初始化及請求響應過程。
2.2.1 初始化
我們先來看DispatcherServlt的Init方法。
2.2.1.1 init
經過原始碼閱讀,DispatcherServlet的init方法實際在ServletBean中定義,其實現如下:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
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) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
實際呼叫initServletBean方法;該方法在DispatcherServlet的父類FrameworkServlet中定義:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
其關鍵呼叫為initWebApplicationContext
2.2.1.2 initWebApplicationContext
該方法主要用於初始化WebApplicationContext,實現BeanFactory的初始化、Bean配置的載入等功能,其實現如下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
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;
}
實際就是對DispatcherServlet的webApplicationContext屬性進行配置與重新整理;
其關鍵過程就是如果屬性為空時,通過createWebApplicationContext建立屬性,此時例項化的實際類名為contextClass屬性所指定的類;該屬性的初始化是在哪個地方進行的?
跟蹤該屬性,我們可以看到在FrameworkServlet中有定義:
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
實際DEFAULT_CONTEXT_CLASS的值為: XmlWebApplicationContext.class
也就是,如果沒有特殊配置,預設的ContextClass為XmlWebApplicationContext類;
該物件預設從名為/WEB-INF/applicationContext.xml的檔案中載入Bean定義;
其refresh方法定義如下:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
其主要過程:
invokeBeanFactoryPostProcessors: 在Bean初始化前配置BeanFactory中的屬性,可修改Bean的定義屬性;
registerBeanPostProcessors:從容器中查詢BeanPostProcessors的Beans並進行註冊;
onRefresh: 初始化Servlet的一些屬性;
初始化完成後,如果呼叫ApplicationContext中的GetBean方法時,實際呼叫的是BeanFactory中的GetBean方法進行Bean的例項化等過程;關於Bean例項化的具體過程後續再進行分析。
2.2.1.3 onRefresh
我們來重點分析下DispatcherServlet的onRefresh方法,通過閱讀原始碼,可以發現其呼叫的實際為initStrategies方法,其實現如下:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
可以看到在這個地方會初始化HandlerMappings、HandlerAdapters、ViewResolvers等屬性;
我們先來看下HandlerMapping的初始化:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
其執行過程為:如果配置了detectAllHandlerMappings屬性,則查詢所有型別為HandlerMapping的Bean;如果沒有配置則只查詢名稱為handlerMapping的Bean; 如果兩個都沒有查詢到則使用預設的HandlerMapping類。那預設的Handler在哪進行配置的?
跟蹤getDefaultStrategies方法,其邏輯就是從DispatcherServlet.properties檔案中查詢預設的配置項; 這個檔案的內容如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
可以看到HandlerMapping如果使用者沒有進行配置的話,則使用預設的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping兩個HandlerMapping。
HandlerAdapter等的初始化過程與HandlerMappings一致,不再贅述。
2.2.2 請求響應
Servlet的請求響應是通過Service方法來實現的;我們先來分析Service方法的實現;
2.2.2.1 Service方法實現分析
我們先來跟蹤Service方法的實現,在DispatcherServlet中已經沒有沒有Service方法定義,查詢其父類,查詢到最終實現在HttpServlet中,其關鍵程式碼段如下:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
...
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
可見HttpServlet中實際呼叫的是doGet/doPost等方法;
再跟蹤doGet等方法的實現,以doGet為例,實際定義在FrameworkServlet中:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
實際呼叫為processRequest方法,其關鍵程式碼段如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
doService(request, response);
...
}
可以看到實際呼叫的是doService方法,而這個方法的實現就在DispatcherServlet中:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
該方法主要是為Request設定了一系列的屬性,最終呼叫doDispatch方法:
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);
}
}
}
}
此處看到,之前所介紹的幾個關鍵類已經開始起作用了;
該方法即為Service生效的關鍵過程,分析該方法執行流程如下:
接下來我們就上述流程中幾個關鍵的過程進行分析。
2.2.2.2 Handler查詢過程
handler查詢通過DispatcherServlet中的findHandler方法進行:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
其中,HandlerMappings這個屬性的初始化過程在2.2.1.3中已分析,我們知道預設情況下如果使用者未配置HandlerMapping類時使用預設的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping,檢視DefaultAnnotationHandlerMapping類,看到這個類已經廢棄了,其建議是使用RequestMappingHandlerMapping類,而這個類是註解中主要使用的RequestMapping註解的處理類,因此此處我們主要分析RequestMappingHandlerMapping這個類的實現及生效過程。
我們從其getHandler方法開始分析;其getHandler方法在其父類的AbstractHandlerMapping中實現,其實現如下:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
這個方法主要做了兩個事情,一是呼叫getHandlerInternal獲取Handler;二是呼叫getHandlerExecutionChain獲取生效的各個攔截器並組裝成HandlerExecutionChain並返回;
跟蹤getHandlerInternal實現,其實現在類AbstractHandlerMethodMapping中,實際上呼叫了lookupHandlerMethod方法來獲取HandlerMethod:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
其過程為先從mappingRegistry中獲取RequestMappingInfo物件,然後將所有的RequestMappingInfo物件轉換成Match物件;如果找到的Match物件列表不為空,則返回其第一個Match元素的HandlerMethod屬性;
這個地方邏輯有點繞;我們先來看下mappingRegistry屬性,這個屬性物件型別為MappingRegistry物件,主要包含了一系列的Map來儲存資料。 它的初始化過程在方法initHandlerMethods中進行:
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
實際上就是找出所有滿足isHandler方法的所有Bean,檢測其所有滿足條件的Methods,最終註冊到mappingRegistry中去。
isHandler的判斷依據,其實現在RequestMappingHandlerMapping中:
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
可以看到如果Bean被Controller或者是RequestMapping註解,則認為它是一個Handler。
而detectHandlerMethods方法完成的功能就是檢測滿足條件的Methods,並將其註冊到mappingRegistry屬性中去;
至此Handler的查詢過程已經分析完畢。
2.2.2.3 HandlerAdapter查詢過程
回到doDispatcher方法,在查詢Handler完成後即呼叫getHandlerAdapter(DispatcherServlet)來查詢HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
就是從handlerAdapters中查詢合適的HandlerAdapter並返回。在2.2.1.3中我們已經分析了handlerAdapters的載入過程,其載入的預設的HandlerAdapter為HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、AnnotationMethodHandlerAdapter
檢視AnnotationMethodHandlerAdapter發現其已建議被RequestMappingHandlerAdapter替換。
2.2.2.4 Handler執行過程
查詢到HandlerAdapter及Handler後就簡單了,最終就是通過HandlerAdapter呼叫到了Handler的handle方法;最終返回ModelAndView物件。
而前置及後置攔截器也就是在handle方法執行前後執行的。
相關推薦
JAVA springMVC原始碼分析
就簡單的寫一寫,面試可能會問,我一箇中級java開發,不會太多東西,大佬繞道! 建立一個Servlet工程,分三層寫好: … 2.建立web.xml <?xml version="1.0" encoding="UTF-8"?> <disp
看透SpringMVC原始碼分析與實踐(一)
一、網站架構及其演變過程 1.軟體的三大型別 軟體分為三個型別:單機軟體、BS結構的軟體(瀏覽器-服務端)、CS結構的軟體(客戶端-服務端)。 2.BS的基礎結構 &nb
看透SpringMVC原始碼分析與實踐(二)
一、Tomcat的頂層結構及啟動過程 1.Tomcat的頂層結構 Tomcat中最頂層的容器叫Server,代表整個伺服器,Server至少包含一個Service用於具體的服務。Service主要包含兩部分,Connector和Conta
springMVC原始碼分析[email protected
@SessionAttribute作用於處理器類上,用於在多個請求之間傳遞引數,類似於Session的Attribute,但不完全一樣,一般來說@SessionAttribute設定的引數只用於暫時的傳遞,而不是長期的儲存,長期儲存的資料還是要放到Session中。通過@Se
SpringMVC原始碼分析1:SpringMVC概述
轉載自:https://blog.csdn.net/a724888/article/details/76014532 Web MVC簡介 1.1、Web開發中的請求-響應模型: 在Web世界裡,具體步驟如下: 1、 Web瀏覽器(如IE)發起請求,如訪問http:/
springMVC原始碼分析--國際化LocaleResolver(一)
springMVC給我們提供了國際化支援,簡單來說就是設定整個系統的執行語言,然後根據系統的執行語言來展示對應語言的頁面,一般我們稱之為多語言。springMVC國際化機制就是可以設定整個系統的執行語言,其定義了一個國際化支援介面LocaleResolver
SpringMVC原始碼分析--容器初始化(四)FrameworkServlet
一下SpringMVC配置檔案的地址contextConfigLocation的配置屬性,然後其呼叫的子類FrameworkServlet的initServletBean方法。 其實FrameworkServlet是springMVC初始化IOC容器的核心,通過讀取配置的c
springMVC原始碼分析--容器初始化(一)ContextLoaderListener
在spring Web中,需要初始化IOC容器,用於存放我們注入的各種物件。當tomcat啟動時首先會初始化一個web對應的IOC容器,用於初始化和注入各種我們在web執行過程中需要的物件。當tomcat啟動的時候是如何初始化IOC容器的,我們先看一下在web.xml中經常看
SpringMVC原始碼分析--容器初始化(五)DispatcherServlet
上一篇部落格SpringMVC原始碼分析--容器初始化(四)FrameworkServlet我們已經瞭解到了SpringMVC容器的初始化,SpringMVC對容器初始化後會進行一系列的其他屬性的初始化操作,在SpringMVC初始化完成之後會呼叫onRefresh(wac
springMVC原始碼分析--異常處理機制HandlerExceptionResolver執行原理(二)
上一篇部落格springMVC原始碼分析--異常處理機制HandlerExceptionResolver簡單示例(一)中我們簡單地實現了一個異常處理例項,接下來我們要介紹一下HandlerExceptionResolver是如何捕獲到Controller中丟擲的異常並展示到前
springMVC原始碼分析--異常處理機制HandlerExceptionResolver簡單示例(一)
springMVC對Controller執行過程中出現的異常提供了統一的處理機制,其實這種處理機制也簡單,只要丟擲的異常在DispatcherServlet中都會進行捕獲,這樣就可以統一的對異常進行處理。 springMVC提供了一個HandlerExcepti
springMVC原始碼分析--ControllerBeanNameHandlerMapping(八)
在上一篇部落格springMVC原始碼分析--AbstractControllerUrlHandlerMapping(六)中我們介紹到AbstractControllerUrlHandlerMapping定義了抽象方法buildUrlsForHandler,接下來我們看看在其
springMVC原始碼分析--攔截器HandlerExecutionChain(三)
上一篇部落格springMVC原始碼分析--HandlerInterceptor攔截器呼叫過程(二)中我們介紹了HandlerInterceptor的執行呼叫地方,最終HandlerInterceptor呼叫的地方是在HandlerExecutionChain中,接下來我們就
springMVC原始碼分析--ControllerClassNameHandlerMapping(九)
在上一篇部落格springMVC原始碼分析--AbstractControllerUrlHandlerMapping(六)中我們介紹到AbstractControllerUrlHandlerMapping定義了抽象方法buildUrlsForHandler,接下來我們看看在其
springMVC原始碼分析--動態樣式ThemeResolver(二)
ThemeResolver的體系結構如下:1、介面ThemeResolver中定義的介面是比較簡單的,提供兩個介面:(1)resolveThemeName獲取樣式名(2)setThemeName設定樣式名public interface ThemeResolver { /
springMVC原始碼分析--AbstractHandlerMethodMapping獲取url和HandlerMethod對應關係(十)
在之前的部落格 springMVC原始碼分析--AbstractHandlerMapping(二)中我們介紹了AbstractHandlerMethodMapping的父類AbstractHandlerMapping,其定義了抽象方法getHandlerInternal(Ht
springMVC原始碼分析--HandlerMethod
在之前的部落格中我們已經接觸過HandlerMethod,接下來我們簡單介紹一下HandlerMethod,簡單來說HandlerMethod包含的資訊包括類、方法和引數的一個資訊類,通過其兩個建構函式我們就可以瞭解其功能,對應著springMVC的Controller來說就
springMVC原始碼分析--HandlerInterceptor攔截器(一)
對SpringMVC有所瞭解的人肯定接觸過HandlerInterceptor攔截器,HandlerInterceptor介面給我們提供了3個方法:(1)preHandle: 在執行controller處理之前執行,返回值為boolean ,返回值為true時接著執行post
springMVC原始碼分析--頁面跳轉RedirectView(三)
跳轉的示例:@RequestMapping("/index") public String index(Model model,RedirectAttributes attr){ attr.addAttribute("attributeName", "attribute
springMVC原始碼分析--HandlerInterceptor攔截器呼叫過程(二)
在上一篇部落格springMVC原始碼分析--HandlerInterceptor攔截器(一)中我們介紹了HandlerInterceptor攔截器相關的內容,瞭解到了HandlerInterceptor提供的三個介面方法:(1)preHandle: 在執行controlle