作者: 一字馬胡
轉(zhuǎn)載標(biāo)志 【2018-01-07】
更新日志
日期 | 更新內(nèi)容 | 備注 |
---|---|---|
2018-01-07 | 創(chuàng)建分析文檔 | Spring源碼分析系列文章(四) |
導(dǎo)入
Spring源碼分析系列文章索引:
本文是系列文章的第四篇缝驳,內(nèi)容為Spring WebMVC模塊的源碼分析潮尝。主要分析WebMVC模塊的工作流程哨毁,依然只分析主干流程,不會(huì)涉及太多的細(xì)節(jié)茄蚯。MVC是一種將Web層進(jìn)行解耦的架構(gòu)模式饲帅,MVC即Model、View耐薯、Controller顽分,Model即數(shù)據(jù)模型徐许,View即視圖,Controller即處理器卒蘸,知道了MVC的大概原理雌隅,就可以開始進(jìn)行Spring MVC的源碼分析了,Spring MVC是MVC架構(gòu)的一種優(yōu)秀實(shí)現(xiàn)缸沃,能高效的進(jìn)行Web模塊的開發(fā)工作恰起。
在進(jìn)行實(shí)際的Spring MVC源碼分析之前,大概先來猜測一下整個(gè)流程中的關(guān)鍵步驟趾牧,然后對照著猜測來分析Spring MVC中的具體實(shí)現(xiàn)检盼。
- 首要的一點(diǎn)是如何將bean解析并加載到容器中來。因?yàn)樵赟pring MVC開發(fā)中武氓,我們不需要也沒有權(quán)限寫一個(gè)main方法梯皿,然后使用ApplicationContext來加載xml文件的環(huán)節(jié)(是否也可以這樣呢?但是需要放在什么位置來加載xml呢县恕?使用什么來加載xml呢东羹?),所以在WebMVC中首先要解決的一個(gè)問題就是使用一個(gè)特殊的組件來觸發(fā)Spring的bean解析-bean加載這個(gè)流程忠烛。
- 在把Spring bean加載成功之后属提,現(xiàn)在,我們可以正常使用我們在Spring配置文件中配置的bean了美尸,對于Web應(yīng)用來說冤议,使用的協(xié)議一般為Http/Https,所以接下來需要考慮的一個(gè)問題是师坎,在Spring MVC中是如何處理客戶端請求的恕酸。客戶端的請求應(yīng)該是一個(gè)http請求胯陋,而達(dá)到Spring MVC之后需要做的事情應(yīng)該是找到合適的Controller蕊温,并且找到Controller中的具體可以處理該請求的Handler,讓Handler來處理請求遏乔,并且獲取到結(jié)果之后將結(jié)果傳遞到View解析器义矛,View解析器會(huì)根據(jù)合適的視圖對數(shù)據(jù)進(jìn)行渲染,然后返回給客戶端去盟萨。
第一步看起來比較簡單凉翻,畢竟我們需要的只是在合適的時(shí)候引導(dǎo)Spring來加載xml文件來解析bean并且加載解析的bean,較為復(fù)雜和核心的功能應(yīng)該是第二步捻激,需要做的事情看起來非常多制轰,首先要攔截請求前计,并且將請求封裝成Spring MVC可以處理的bean,然后需要根據(jù)請求來選擇合適的Controller艇挨,并且將合適的Handler交給攔截器進(jìn)行請求處理残炮,處理完了還需要將數(shù)據(jù)交付給視圖渲染組件來返回合適的試圖。
所以就目前來說缩滨,在第二步势就,有幾個(gè)問題需要得到解決:
- Controller看起來和普通的bean是不一樣的,因?yàn)槠胀ǖ腷ean不涉及Handler脉漏,而Controller涉及到Handler苞冯,并且可能一個(gè)Controller包含了多個(gè)Handler,所以看起來Controller需要特殊對待侧巨,就解析Controller來說舅锄,需要結(jié)合Controller代碼來進(jìn)行解析,將所有Controller支持的Handler和對應(yīng)的Url解析好司忱,然后在請求到達(dá)的時(shí)候皇忿,只需要和解析好的這些Url和Handler進(jìn)行匹配就可以了。
- Controller的Handler解析好了坦仍,那是怎么匹配具體的請求的呢鳍烁?這是另外一個(gè)需要考慮的問題,一個(gè)請求肯定會(huì)帶著一個(gè)url繁扎,怎么為這個(gè)url找到合適的Handler是處理請求的關(guān)鍵一步幔荒。
- 匹配好了Handler之后,Handler是怎么處理請求的呢梳玫?也就是怎么交給具體的Handler的呢爹梁?處理完成的數(shù)據(jù)會(huì)流轉(zhuǎn)到哪里呢?
現(xiàn)在想起來并不會(huì)太全面提澎,很容易忽略細(xì)節(jié)姚垃,但是大概就應(yīng)該是這樣,下面就帶著這些問題來分析Spring WebMVC的源碼盼忌。
Spring WebMVC模塊解析
首先是第一個(gè)問題积糯,怎么觸發(fā)Spring中的bean的解析-bean的加載那一套流程。在Spring WebMVC項(xiàng)目中都需要配置一個(gè)web.xml文件碴犬,這個(gè)文件配置一些相關(guān)Web的配置項(xiàng),下面是一個(gè)關(guān)鍵配置梆暮,與觸發(fā)Spring bean解析密切相關(guān):
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
context-param標(biāo)簽用于進(jìn)行一些key-value類型的配置項(xiàng)設(shè)置服协,key是param-name,Value就是param-value啦粹,上面的配置中配置了一個(gè)key為contextConfigLocation的配置項(xiàng)偿荷,代表Spring bean解析bean的xml文件來源窘游,從value可以看出,Spring會(huì)加載classpath:applicationContext.xml這些文件來進(jìn)行bean的解析跳纳。
接著是一個(gè)listener標(biāo)簽忍饰,看起來是一個(gè)監(jiān)聽器,所謂監(jiān)聽器寺庄,就是會(huì)監(jiān)聽一些事件艾蓝,當(dāng)某些事件發(fā)生的時(shí)候就會(huì)做一些事情的組件,而listener-class標(biāo)簽設(shè)定了具體的監(jiān)聽器類斗塘。在上面的設(shè)置中設(shè)置了ContextLoaderListener這個(gè)類赢织,看起來就是這個(gè)類來觸發(fā)Spring 進(jìn)行bean的加載,而上面的context-param配置的就是Spring 加載bean掃描的xml文件馍盟,下面來具體分析一下整個(gè)流程于置。
ContextLoaderListener 實(shí)現(xiàn)了 ServletContextListener接口,ServletContextListener 有一個(gè)關(guān)鍵的方法是contextInitialized贞岭,根據(jù)注釋八毯,這個(gè)方法會(huì)在web應(yīng)用初始化的時(shí)候調(diào)用,所以也就是在Web應(yīng)用最開始的地方會(huì)觸發(fā)這個(gè)方法瞄桨,具體聯(lián)系ContextLoaderListener话速,就是會(huì)在這個(gè)時(shí)候進(jìn)行bean的加載流程,下面來具體分析一下ContextLoaderListener中contextInitialized這個(gè)方法的實(shí)現(xiàn):
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
關(guān)鍵的流程流轉(zhuǎn)到了initWebApplicationContext這個(gè)方法中來了讲婚,下面來分析一下initWebApplicationContext這個(gè)方法中的關(guān)鍵代碼:
// 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);
上面的代碼截取自initWebApplicationContext方法尿孔,這個(gè)方法實(shí)現(xiàn)的功能就是觸發(fā)Spring的bean的加載流程,但是這只是觸發(fā)的開始筹麸,來分析一下上面的代碼活合,首先是createWebApplicationContext方法,會(huì)根據(jù)servletContext來create一個(gè)WebApplicationContext物赶,下面是這個(gè)方法的具體實(shí)現(xiàn):
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
經(jīng)過上面的步驟之后白指,就會(huì)走到關(guān)鍵的方法configureAndRefreshWebApplicationContext,這個(gè)方法很關(guān)鍵酵紫,它引導(dǎo)web應(yīng)用開始進(jìn)行bean的加載操作告嘲,下面來看一下這個(gè)方法內(nèi)部的實(shí)現(xiàn):
其中的configLocationParam就是我們在web.xml中配置的那個(gè)參數(shù),是Spring掃描bean的路徑奖地,配置好路徑之后橄唬,就和我們自己寫加載bean的流程是一樣的了,只是這里webMVC會(huì)自動(dòng)進(jìn)行這些步驟参歹,看到最后的wac.refresh()仰楚,就可以確定,Spring要開始進(jìn)行xml的加載,并且進(jìn)行bean的解析僧界、加載等流程了侨嘀,關(guān)于這些步驟已經(jīng)在前面的文章中分析過,在此不再贅述捂襟。
DispatcherServlet
DispatcherServlet是Spring MVC中的核心組件咬腕,它接收請求,并且為請求找到合適的Handler進(jìn)行處理葬荷,DispatcherServlet也是本文分析的重點(diǎn)內(nèi)容涨共,下面首先來看一下再Spring MVC應(yīng)用中web.xml中關(guān)于DispatcherServlet的配置:
<servlet>
<servlet-name>Spring-MVC-API-Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring-MVC-API-Servlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
就像上面看起來的一樣,每一個(gè)servlet都需要配置一個(gè)ervlet-mapping闯狱,用來將相應(yīng)的請求交給對應(yīng)的DispatcherServlet進(jìn)行處理煞赢。在上面的配置中,配置了一個(gè)servlet名字叫做Spring-MVC-API-Servlet哄孤,指定為org.springframework.web.servlet.DispatcherServlet這個(gè)類照筑,并且配置了加載Controller的xml文件路徑為classpath*:springmvc-servlet.xml,除此之外瘦陈,為該servlet配置了mapping凝危,將所有以/api開頭的請求都路由到該DispatcherServlet進(jìn)行處理。一個(gè)Web 應(yīng)用可以配置多個(gè)DispatcherServlet來處理不同的資源類型晨逝,視具體情況來使用蛾默,只需要記住,每一個(gè)DispatcherServlet都需要配置配套的mapping就可以了捉貌。
有了DispatcherServlet配置支鸡,現(xiàn)在來分析一下DispatcherServlet的具體細(xì)節(jié),在第一步觸發(fā)Spring bean的加載流程哪里趁窃,結(jié)束之后并不會(huì)包含Controller牧挣,所以Controller需要特殊解析,因?yàn)樯婕暗紿andler的解析問題醒陆,所以可以理解需要特殊解析瀑构。下面是DispatcherServlet的類圖,可以看到DispatcherServlet的實(shí)現(xiàn)是比較復(fù)雜的:
首先關(guān)注DispatcherServlet實(shí)現(xiàn)了Servlet接口刨摩,Servlet接口有一個(gè)重要的方法init寺晌,這個(gè)方法應(yīng)該是會(huì)在實(shí)例化了Servlet之后調(diào)用,具體的實(shí)現(xiàn)是在HttpServletBean的澡刹,下面是它的實(shí)現(xiàn):
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 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();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
其中有一個(gè)方法值得注意:initServletBean呻征,具體的實(shí)現(xiàn)在FrameworkServlet:
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
這里面初始化的就是我們在web.cml中配置的一個(gè)DispatcherServlet,每一個(gè)DispatcherServlet都會(huì)進(jìn)行一次罢浇,首先需要注意的一個(gè)方法是initWebApplicationContext這個(gè)方法陆赋,然后是initFrameworkServlet這個(gè)方法边篮,首先來看一下前面的那個(gè)方法的具體實(shí)現(xiàn)內(nèi)容。
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);
}
}
}
上面是第一個(gè)試圖找到一個(gè)webApplicationContext的第一個(gè)分支奏甫,如果webApplicationContext在構(gòu)造函數(shù)中被帶賦值,那么就會(huì)走到該分支中來凌受,這個(gè)分支中需要關(guān)注的一個(gè)方法是configureAndRefreshWebApplicationContext阵子,這個(gè)方法做什么的呢?下面是該方法的具體實(shí)現(xiàn):
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
最后的wac.refresh()代表需要重新走一次Spring bean加載的流程胜蛉。下面是第二個(gè)試圖找到一個(gè)webApplicationContext的第二個(gè)分支:
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();
}
下面是第三個(gè)分支挠进,一般情況下會(huì)走到這個(gè)分支中來:
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
主要是設(shè)定了掃描路徑,然后就調(diào)用了configureAndRefreshWebApplicationContext方法來開始進(jìn)行bean的加載流程誊册,這個(gè)方法在上面已經(jīng)提及领突,在此不再贅述。
接著是一個(gè)關(guān)鍵的方法onRefresh:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
//初始化andlerMappings
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
這其中初始化了很多內(nèi)容案怯,比如MultipartResolver君旦、hemeResolver等,但是目前我比較關(guān)心的是HandlerMappings的初始化嘲碱,下面就主要來分析這個(gè)initHandlerMappings這個(gè)方法的實(shí)現(xiàn)細(xì)節(jié)金砍。
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");
}
}
}
上面這個(gè)方法大概的意思就是加載系統(tǒng)設(shè)置的HandlerMappings,可以在webMVC模塊中的Resources中看到有一個(gè)文件叫做DispatcherServlet.properties麦锯,可以在其中找到下面的內(nèi)容:
org.springframework.web.servlet.HandlerMapping=
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
也就是說恕稠,如果沒有設(shè)置自己的HandlerMappings的話,Spring就會(huì)去加載這兩個(gè)HandlerMappings扶欣,本文主要分析后者RequestMappingHandlerMapping鹅巍。
到目前為止,總結(jié)一下現(xiàn)在的上下文料祠,首先骆捧,我們已經(jīng)知道了Spring MVC是什么時(shí)候以及怎么樣觸發(fā)Bean的加載流程的,這一步貌似和Spring MVC關(guān)系不大术陶,但是卻很重要凑懂,接著,了解了web.xml中關(guān)于servlet的配置準(zhǔn)則梧宫,以及配置的意義接谨,然后對servlet進(jìn)行了初始化,并且最終觸發(fā)了一系列的初始化塘匣,包括HandlerMappings脓豪。至此,貌似可以接受請求了忌卤,也就是說Spring WebMVC的分析已經(jīng)走了一半了扫夜,接下來的一半內(nèi)容就是如何處理請求了,這就得和Servlet的生命周期配合起來理解分析了,并且會(huì)涉及到Servlet將請求交給合適的Controller的合適的Handler的過程笤闯。下面來逐步分析一下堕阔。
DispatcherServlet在實(shí)現(xiàn)上繼承了HttpServlet,而HttpServlet提高了大量的方法來進(jìn)行請求的處理颗味,比如doGet超陆、doPut等,而HttpServlet中的service方法就是一個(gè)dispatch浦马,會(huì)解析請求时呀,然后根據(jù)不同的請求方法來調(diào)用不同的doXXX方法,我們主要關(guān)注doGet和doPut方法即可晶默。需要注意的是谨娜,service方法在FrameworkServlet類中被重寫了,具體實(shí)現(xiàn)如下:
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);
}
}
一般情況下會(huì)走到else分支中磺陡,然后調(diào)用了super的service方法趴梢,下面是該方法的實(shí)現(xiàn)內(nèi)容:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} 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);
}
}
可以看到,service方法就是解析請求币他,然后根據(jù)不同的請求類型交給不同的方法來處理垢油,比如GET類型的請求就會(huì)交給doGet方法來進(jìn)行處理,下面主要關(guān)注doGet這個(gè)方法的接下來的流程圆丹,其余的方法分析類似滩愁,就不再贅述了。
doGet方法在FrameworkServlet類中重寫了辫封,所以會(huì)走到FrameworkServlet類中的doGet方法中來硝枉,下面是該方法的具體實(shí)現(xiàn):
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
接著走到了processRequest方法內(nèi)部,下面是該方法的主要代碼:
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);
doService(request, response);
}
這個(gè)方法將對Request和Response做一些修飾倦微,然后就會(huì)走到doService這個(gè)方法妻味。下面是doService方法的具體細(xì)節(jié):
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("org.springframework.web.servlet")) {
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);
}
}
}
}
看起來這個(gè)方法也還沒開始真正處理請求,而會(huì)繼續(xù)修飾Request欣福,然后交給doDispatch這個(gè)方法來做责球,下面是doDispatch方法的具體細(xì)節(jié):
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);
}
看起來這個(gè)方法就是真正處理請求的方法了,下面詳細(xì)分析一下這個(gè)方法的實(shí)現(xiàn)內(nèi)容拓劝。
首先對request做了一些處理雏逾,然后會(huì)調(diào)用getHandler來獲取一個(gè)可以處理該請求的Handler,這個(gè)方法是關(guān)鍵郑临,需要詳細(xì)分析一下栖博。
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;
}
這個(gè)方法內(nèi)部會(huì)進(jìn)行遍歷所有已加載的handlerMappings,而handlerMappings的加載在上文中已經(jīng)提到過厢洞,getHandler方法會(huì)詢問所有加載的handlerMappings仇让,看看到底哪個(gè)handlerMapping可以處理典奉。通過調(diào)用HandlerMapping的getHandler方法來進(jìn)行判斷是否這個(gè)Handler可以處理當(dāng)前請求。下面以RequestMappingHandlerMapping為例來分析接下來的具體流程丧叽∥谰粒可以在AbstractHandlerMapping類中找到getHandler這個(gè)方法:
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;
}
該方法的關(guān)鍵是第一個(gè)方法調(diào)用getHandlerInternal,下面來分析一下getHandlerInternal這個(gè)方法的實(shí)現(xiàn)細(xì)節(jié)踊淳『П剩可以在AbstractHandlerMethodMapping中找到getHandlerInternal這個(gè)方法的具體實(shí)現(xiàn):
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
這里面的第一個(gè)需要注意的方法是getUrlPathHelper().getLookupPathForRequest,該方法會(huì)根據(jù)請求解析出具體的用于匹配Handler的url嚣崭,這是一個(gè)很關(guān)鍵的步驟,尋找合適的Handler就是根據(jù)url來進(jìn)行的懦傍。下面是該方法的具體實(shí)現(xiàn)內(nèi)容:
public String getLookupPathForRequest(HttpServletRequest request) {
// Always use full path within current servlet context?
if (this.alwaysUseFullPath) {
return getPathWithinApplication(request);
}
// Else, use path within current servlet mapping if applicable
String rest = getPathWithinServletMapping(request);
if (!"".equals(rest)) {
return rest;
}
else {
return getPathWithinApplication(request);
}
}
這個(gè)方法里面需要注意的是getPathWithinServletMapping這個(gè)方法的調(diào)用雹舀,這就是具體的對請求的url的處理。下面是該方法的具體實(shí)現(xiàn)細(xì)節(jié):
public String getPathWithinServletMapping(HttpServletRequest request) {
String pathWithinApp = getPathWithinApplication(request);
String servletPath = getServletPath(request);
String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
String path;
// if the app container sanitized the servletPath, check against the sanitized version
if (servletPath.indexOf(sanitizedPathWithinApp) != -1) {
path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
}
else {
path = getRemainingPath(pathWithinApp, servletPath, false);
}
if (path != null) {
// Normal case: URI contains servlet path.
return path;
}
else {
// Special case: URI is different from servlet path.
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
// Use path info if available. Indicates index page within a servlet mapping?
// e.g. with index page: URI="/", servletPath="/index.html"
return pathInfo;
}
if (!this.urlDecode) {
// No path info... (not mapped by prefix, nor by extension, nor "/*")
// For the default servlet mapping (i.e. "/"), urlDecode=false can
// cause issues since getServletPath() returns a decoded path.
// If decoding pathWithinApp yields a match just use pathWithinApp.
path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
if (path != null) {
return pathWithinApp;
}
}
// Otherwise, use the full servlet path.
return servletPath;
}
}
getPathWithinApplication這個(gè)方法會(huì)解析好一個(gè)請求的url(純潔的url粗俱,比如 /api/user/1310561):
public String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
String path = getRemainingPath(requestUri, contextPath, true);
if (path != null) {
// Normal case: URI contains context path.
return (StringUtils.hasText(path) ? path : "/");
}
else {
return requestUri;
}
}
接著说榆,getServletPath這個(gè)方法會(huì)返回web.xml中配置的路由路徑:
public String getServletPath(HttpServletRequest request) {
String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
if (servletPath == null) {
servletPath = request.getServletPath();
}
if (servletPath.length() > 1 && servletPath.endsWith("/") && shouldRemoveTrailingServletPathSlash(request)) {
// On WebSphere, in non-compliant mode, for a "/foo/" case that would be "/foo"
// on all other servlet containers: removing trailing slash, proceeding with
// that remaining slash as final lookup path...
servletPath = servletPath.substring(0, servletPath.length() - 1);
}
return servletPath;
}
getRemainingPath這個(gè)方法大概是將url中的前綴去掉,所謂前綴就是web.xml中配置的路由寸认,這樣才能去匹配Controller中的Handler對吧签财?現(xiàn)在回到getHandlerInternal這個(gè)方法,現(xiàn)在可以拿到lookupPath了偏塞,那接下來就可以根據(jù)lookupPath來匹配Controller的Handler了吧唱蒸?接著往下分析。接著一個(gè)比較關(guān)鍵的方法是lookupHandlerMethod灸叼,下面來分析一下這個(gè)方法的實(shí)現(xiàn):
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);
}
}
這里面需要關(guān)注的一個(gè)方法是addMatchingMappings神汹,用于添加匹配的Handler:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
獲取到所有匹配的Handler之后需要挑選一個(gè)最合適的Handler進(jìn)行請求的處理,lookupHandlerMethod方法中接下來的代碼實(shí)現(xiàn)的功能就是這些古今,下面回到doDispatch方法屁魏,接著走接下來的流程,現(xiàn)在捉腥,我們已經(jīng)獲取到了合適的Handler氓拼,下面,就是進(jìn)行Handler的訪問來處理請求了抵碟。
關(guān)鍵代碼是:ha.handle(processedRequest, response, mappedHandler.getHandler())桃漾,具體的handle方法實(shí)現(xiàn)在AbstractHandlerMethodAdapter類中,具體實(shí)現(xiàn)細(xì)節(jié)如下:
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
handleInternal就是我們希望看到的方法拟逮,這個(gè)方法做的事情就是執(zhí)行Controller中根據(jù)url挑選出來的Handler呈队,并且將Handler的處理結(jié)果進(jìn)行合適的view渲染的過程,關(guān)鍵的方法是invokeHandlerMethod:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
上面的方法的關(guān)鍵是invocableMethod.invokeAndHandle唱歧,下面是關(guān)鍵的方法調(diào)用代碼:
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace(sb.toString());
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
doInvoke方法就是實(shí)際訪問Handler的實(shí)現(xiàn)宪摧,到此粒竖,Controller中的Handler方法已經(jīng)執(zhí)行完成了,接著會(huì)調(diào)用getModelAndView來進(jìn)行試圖渲染几于,這一部分的內(nèi)容就不再分析了蕊苗,未來找機(jī)會(huì)再進(jìn)行詳細(xì)分析。
至此沿彭,我們居然已經(jīng)分析完了一個(gè)請求的處理流程朽砰,包括請求的解析,url匹配Handler喉刘,已經(jīng)Controller中Handler的執(zhí)行等內(nèi)容瞧柔,但是好像還缺點(diǎn)什么,那就是我們在進(jìn)行用url來匹配handler的時(shí)候睦裳,貌似沒有解析Controller類的流程造锅,但是可以肯定的是這個(gè)流程肯定是存在的,那是否這個(gè)流程在處理請求之前就完成了呢廉邑?現(xiàn)在來挖掘一下這部分的內(nèi)容哥蔚,當(dāng)我們找到并且分析了這部分的內(nèi)容之后,整個(gè)流程就算是走通了蛛蒙。
在AbstractHandlerMethodMapping類中有一個(gè)方法特別關(guān)鍵糙箍,那就是afterPropertiesSet:
public void afterPropertiesSet() {
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());
}
AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口,所以在bean進(jìn)行了初始化之后會(huì)調(diào)用該afterPropertiesSet做一些事情牵祟,下面來具體分析一下initHandlerMethods這個(gè)方法的細(xì)節(jié)深夯,看樣子是開始初始化Handler的過程。首先獲取了所有的beanName诺苹,然后會(huì)對每一個(gè)bean進(jìn)行處理塌西,使用etApplicationContext().getType方法來獲取bean的類型,然后使用isHandler來判斷一個(gè)bean是否是Handler類型的bean筝尾,如果是的話就會(huì)調(diào)用detectHandlerMethods捡需,下面是isHandler方法的具體實(shí)現(xiàn):
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
關(guān)于如何快速判斷一個(gè)類是否具有注解可以參考文章Java如何快速獲取類附帶的注解,只要注解中有 Controller或者RequestMapping就代表該類含有Handler筹淫,所以就得去detect站辉,現(xiàn)在回到initHandlerMethods方法,接著看一下detectHandlerMethods方法的具體實(shí)現(xiàn)损姜。
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
return getMappingForMethod(method, userType);
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
主要看registerHandlerMethod這個(gè)方法饰剥,就是注冊Handler的實(shí)現(xiàn):
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
register方法實(shí)現(xiàn)了handler的注冊,解析好了之后當(dāng)然要保存起來啊摧阅,否則后面怎么忍亍?所以在此注冊之后棒卷,就可以在用url匹配handler的時(shí)候使用了顾孽。
至此祝钢,整條鏈路就走通了,從Spring bean加載流程的觸發(fā)若厚,到web.xml的配置以及準(zhǔn)則等細(xì)節(jié)拦英,再到實(shí)際請求的處理流程,最后發(fā)現(xiàn)在處理請求的時(shí)候使用到的handler還沒有分析解析流程测秸,所以最后分析了MVC中Controller的handler解析流程疤估,在這一步保存解析好保存起來之后,后面處理請求的時(shí)候就可以用來匹配具體的url以及其他的匹配項(xiàng)了霎冯,最終一個(gè)請求可以得到處理铃拇。當(dāng)然,本文并沒有涉及到view渲染的分析沈撞,因?yàn)樵诤芏鄷r(shí)候慷荔,我們直接將model寫到了response中去了,而不是返回一個(gè)視圖关串,而且渲染視圖的流程簡單但是較為繁瑣,基于這些原因就不再分析了监徘。本文涉及的大部分細(xì)節(jié)內(nèi)容會(huì)不斷進(jìn)行學(xué)習(xí)補(bǔ)充晋修,但是主干流程應(yīng)該就像本文分析的一樣,并且這些都是可以猜出來的凰盔,只是Spring MVC的實(shí)現(xiàn)由他自己的優(yōu)秀的地方墓卦,而正是由于具備優(yōu)秀的特性,才值得不斷學(xué)習(xí)户敬,發(fā)現(xiàn)其中的優(yōu)秀落剪。