SpringMVC作為MVC框架近年來被廣泛地使用,其與Mybatis和Spring的組合,也成為許多公司開發web的套裝。SpringMVC繼承了Spring的優點,對業務程式碼的非侵入性,配置的便捷和靈活,再加上註解方式的簡便與流行,SpringMVC自然成為web開發中MVC框架的首選。
SpringMVC的設計理念,簡單來說,就是將Spring的IOC容器與Servlet結合起來,從而在IOC容器中維護Servlet相關物件的生命週期,同時將Spring的上下文注入到Servlet的上下文中。依靠Servlet的事件和監聽機制來操作和維護外部請求,以及組裝和執行請求對應的響應。
XML配置
SpringMVC想與Servlet相結合,首先得在Servlet容器中進行配置。以Tomcat為例,通常在web.xml檔案中配置一個監聽器和SpringMVC的核心Servlet。
監聽器
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
核心Servlet
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
當我準備研究SpringMVC原始碼時,我問出了一個早應該問的問題:為什麼配置了DispatcherServlet,還需要一個監聽器,而且都能載入配置檔案?在context-param中的配置檔案要不要在DispatcherServlet中的init-param再加上?相信很多剛用SpringMVC的人都閃現過這樣的問題。翻閱過原始碼後,明白了SpringMVC通過這種方式實現了父子上下文容器結構。
Tomcat啟動時,監聽器ContextLoaderListener建立一個XMLWebApplicationContext上下文容器,並載入context-param中的配置檔案,完成容器的重新整理後將上下文設定到ServletContext。當DispatcherServlet建立時,先進行初始化操作,從ServletContext中查詢出監聽器中建立的上下文物件,作為父類上下文來建立servlet的上下文容器,並載入Servlet配置中的init-param的配置檔案(預設載入/WEB-INF/servletName-servlet.xml,servletName為DispatcherServlet配置的servlet-name),然後完成容器的重新整理。子上下文可以訪問父上下文中的bean,反之則不行。
父子上下文容器結構如下
通常是將業務操作及資料庫相關的bean維護在Listener的父容器中,而在Servlet的子容器中只載入Controller相關的業務實現的bean。從而將業務實現和業務的具體操作分隔在兩個上下文容器中,業務實現bean可以呼叫業務具體操作的bean。
ServletContext啟動監聽
ServletContextListener監聽ServletContext的生命週期。每個web application對應一個ServletContext,用於servlet與servlet容器溝通的中介。它定義兩個方法,context初始化和context銷燬。
public interface ServletContextListener extends EventListener { public void contextInitialized(ServletContextEvent sce); public void contextDestroyed(ServletContextEvent sce); }
SpringMVC的ContextLoaderListener實現了此介面,在web application啟動時建立一個Spring的ROOT上下文。
根上下文的建立
SpringMVC根上下文是通過ServletContext的監聽器進行建立,預設的監聽器為ContextLoaderListener。當web應用啟動時,會呼叫監聽器的contextInitialized方法。
public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
contextInitialized方法接受引數ServletContext,實際的web上下文的建立交給了子類ContextLoader。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 判斷ServletContext是否已存在SpringMVC根上下文,存在則報錯 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } // 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); } // 載入並重新整理上下文環境,也就是初始化Spring容器 // 繫結ServletContext到Spring根上下文 configureAndRefreshWebApplicationContext(cwac, servletContext); } } // 將建立完的根上下文繫結到ServletContext servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); return this.context; }
我們來看看建立根容器 createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = this.determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } else { return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); } }
先是獲取Class物件,然後利用反射例項化物件
protected Class<?> determineContextClass(ServletContext servletContext) { //可以手動在web.xml中配置contextClass引數 String contextClassName = servletContext.getInitParameter("contextClass"); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException var4) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4); } } else { //在配置檔案中有如下配置 //org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { //利用反射載入類 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException var5) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5); } } }
最後再呼叫 BeanUtils.instantiateClass 例項化物件
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } else { try { //獲取構造器並例項化 return instantiateClass(clazz.getDeclaredConstructor()); } catch (NoSuchMethodException var2) { throw new BeanInstantiationException(clazz, "No default constructor found", var2); } } }
最後我們來看看容器的初始化 configureAndRefreshWebApplicationContext(cwac, servletContext);
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { String configLocationParam; if (ObjectUtils.identityToString(wac).equals(wac.getId())) { configLocationParam = sc.getInitParameter("contextId"); if (configLocationParam != null) { wac.setId(configLocationParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); configLocationParam = sc.getInitParameter("contextConfigLocation"); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null); } this.customizeContext(sc, wac); wac.refresh(); }
其實是呼叫 ConfigurableWebApplicationContext 的 refresh() 對容器的初始化。
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //準備重新整理的上下文 環境 /* * 初始化前的準備工作,例如對系統屬性或者環境變數進行準備及驗證。 * 在某種情況下專案的使用需要讀取某些系統變數,而這個變數的設定很可能會影響著系統 * 的正確性,那麼ClassPathXmlApplicationContext為我們提供的這個準備函式就顯得非常必要, * 它可以在Spring啟動的時候提前對必須的變數進行存在性驗證。 */ prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //初始化BeanFactory,並進行XML檔案讀取 /* * ClassPathXMLApplicationContext包含著BeanFactory所提供的一切特徵,在這一步驟中將會複用 * BeanFactory中的配置檔案讀取解析及其他功能,這一步之後,ClassPathXmlApplicationContext * 實際上就已經包含了BeanFactory所提供的功能,也就是可以進行Bean的提取等基礎操作了。 */ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //對beanFactory進行各種功能填充 /* * @Qualifier與@Autowired等註解正是在這一步驟中增加的支援 */ prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. //子類覆蓋方法做額外處理 /* * Spring之所以強大,為世人所推崇,除了它功能上為大家提供了便利外,還有一方面是它的 * 完美架構,開放式的架構讓使用它的程式設計師很容易根據業務需要擴充套件已經存在的功能。這種開放式 * 的設計在Spring中隨處可見,例如在本例中就提供了一個空的函式實現postProcessBeanFactory來 * 方便程式猿在業務上做進一步擴充套件 */ postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. //啟用各種beanFactory處理器 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //註冊攔截Bean建立的Bean處理器,這裡只是註冊,真正的呼叫實在getBean時候 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //為上下文初始化Message源,即不同語言的訊息體,國際化處理 initMessageSource(); // Initialize event multicaster for this context. //初始化應用訊息廣播器,並放入“applicationEventMulticaster”bean中 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //留給子類來初始化其它的Bean onRefresh(); // Check for listener beans and register them. //在所有註冊的bean中查詢Listener bean,註冊到訊息廣播器中 registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //初始化剩下的單例項(非惰性的) finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //完成重新整理過程,通知生命週期處理器lifecycleProcessor重新整理過程,同時發出ContextRefreshEvent通知別人 finishRefresh(); } catch (BeansException ex) { destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { resetCommonCaches(); } } }
當SpringMVC上下文建立完成後,以固定的屬性名稱將其繫結到Servlet上下文上,用以在servlet子上下文建立時從Servlet上下文獲取,並設定為其父上下文,從而完成父子上下文的構成。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
Servlet的初始化
Servlet的生命週期從第一次訪問Servlet開始,Servlet物件被建立並執行初始化操作。而每次請求則由servlet容器交給Servlet的service方法執行,最後在web application停止時呼叫destroy方法完成銷燬前處理。
public interface Servlet { public void init(ServletConfig config) throws ServletException; public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public void destroy(); }
在web.xml的servlet配置選項中有一個load-on-startup,其值為整數,標識此Servlet是否在容器啟動時的載入優先順序。若值大於0,按從小到大的順序被依次載入;若為0,則標識最大整數,最後被載入;若值小於0,表示不載入。預設load-on-startup的值為-1。servlet的載入是在載入完所有ServletContextListener後才執行。
先來看下DispatcherServlet的類圖
servlet子上下文的建立是在servlet的初始化方法init中。而SpringMVC的核心Servlet-DispatcherServlet的初始化操作則是在其父類HttpServletBean中。
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. //從初始化引數設定bean屬性。例如init-param的contextConfigLocation classpath*:spring-mvc.xml PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { //將DispatcherServlet轉化成Spring裡面的Bean, BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); //載入配置資訊 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); //通過Spring的Bean賦值方式給DispatcherServlet初始化屬性值 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的實現在子類FrameworkServlet中
protected final void initServletBean() throws ServletException { // 建立servlet子上下文 this.webApplicationContext = initWebApplicationContext(); // 可擴充套件方法 initFrameworkServlet(); }
此處的initWebApplicationContext方法同ContextLoader步驟相似
protected WebApplicationContext initWebApplicationContext() { // 從ServletContext獲取SpringMVC根上下文 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 如果SpringMVC的servlet子上下文物件不為空,則設定根上下文為其父類上下文,然後重新整理 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) { // 根據init-param配置的屬性名稱從ServletContext查詢SpringMVC的servlet子上下文 wac = findWebApplicationContext(); } if (wac == null) { // 若還為空,則建立一個新的上下文物件並重新整理 wac = createWebApplicationContext(rootContext); } // 子類自定義對servlet子上下文後續操作,在DispatcherServlet中實現 if (!this.refreshEventReceived) { onRefresh(wac); } // 釋出servlet子上下文到ServletContext 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; }
servlet子上下文的建立也有幾個關鍵點
- 從ServletContext中獲取第一步中建立的SpringMVC根上下文,為下面做準備
- 根據init-param中的contextAttribute屬性值從ServletContext查詢是否存在上下文物件
- 以XmlWebApplicationContext作為Class型別建立上下文物件,設定父類上下文,並完成重新整理
- 執行子類擴充套件方法onRefresh,在DispatcherServlet內初始化所有web相關元件
- 將servlet子上下文物件釋出到ServletContext
例項化上下文物件時使用預設的ContextClass,即XmlWebApplicationContext,子類也可以重寫此方法來支援其他上下文型別。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { //this.contextClass = DEFAULT_CONTEXT_CLASS; //DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; Class<?> contextClass = this.getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + this.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 '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { //反射建立例項 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); //設定父容器 wac.setParent(parent); wac.setConfigLocation(this.getContextConfigLocation()); //初始化新建立的子容器 this.configureAndRefreshWebApplicationContext(wac); return wac; } }
在上下文的配置重新整理方法configureAndRefreshWebApplicationContext中,將ServletContext和ServletConfig都繫結到servlet子上下文物件中。另外設定了預設的namespace,即servletName-servlet
。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName()); } } //將ServletContext和ServletConfig都繫結到servlet子上下文物件中 wac.setServletContext(this.getServletContext()); wac.setServletConfig(this.getServletConfig()); wac.setNamespace(this.getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener(null))); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig()); } this.postProcessWebApplicationContext(wac); this.applyInitializers(wac); //最後初始化子容器,和上面根容器初始化一樣 wac.refresh(); }
當所有操作完成後,將servlet子上下文以org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
的屬性名稱註冊到ServletContext中,完成和ServletContext的雙向繫結。
SpringMVC在DispatcherServlet的初始化過程中,同樣會初始化一個WebApplicationContext的實現類,作為自己獨有的上下文,這個獨有的上下文,會將上面的根上下文作為自己的父上下文,來存放SpringMVC的配置元素,然後同樣作為ServletContext的一個屬性,被設定到ServletContext中,只不過它的key就稍微有點不同,key和具體的DispatcherServlet註冊在web.xml檔案中的名字有關,從這一點也決定了,我們可以在web.xml檔案中註冊多個DispatcherServlet,因為Servlet容器中註冊的Servlet名字肯定不一樣,設定到Servlet環境中的key也肯定不同。
元件初始化
在servlet子上下文完成建立,呼叫了模板擴充套件方法OnRefresh,它在FrameworkServlet中僅僅只是個空方法,但在其子類DispatcherServlet中則至關重要,它是一切元件的起源。
DispatcherServlet.java protected void onRefresh(ApplicationContext context) { initStrategies(context); }
初始化所有策略,其實是指各個元件可以通過策略動態地進行配置。
protected void initStrategies(ApplicationContext context) { // 檔案上傳解析器 initMultipartResolver(context); // 本地化解析器 initLocaleResolver(context); // 主題解析器 initThemeResolver(context); // 處理器對映器(url和Controller方法的對映) initHandlerMappings(context); // 處理器介面卡(實際執行Controller方法) initHandlerAdapters(context); // 處理器異常解析器 initHandlerExceptionResolvers(context); // RequestToViewName解析器 initRequestToViewNameTranslator(context); // 檢視解析器(檢視的匹配和渲染) initViewResolvers(context); // FlashMap管理者 initFlashMapManager(context); }
以上基本將SpringMVC中的主要元件都羅列出來了,其中最重要的當是HandlerMapping,HandlerAdapter和ViewResolver。由於各元件的初始化策略方式相似,我們以HandlerMapping來介紹。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 是否查詢所有HandlerMapping標識 if (this.detectAllHandlerMappings) { // 從上下文(包含所有父上下文)中查詢HandlerMapping型別的Bean 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物件 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. } } // 確保至少有一個HandlerMapping,如果沒能找到,註冊一個預設的 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
策略的邏輯很簡單:有一個標識,是否查詢所有HandlerMapping(預設為true)。如果為是,則從上下文(包括所有父上下文)中查詢型別為HandlerMapping的Bean,並進行排序。如果為否,則從上下文中按指定名稱去尋找。如果都沒有找到,提供一個預設的實現。這個預設實現從DispatcherServlet同級目錄的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
可以看到SpringMVC為每個元件都提供了預設的實現,通常情況下都能夠滿足需求。如果你想對某個元件進行定製,可以通過spring的xml檔案或者@Configuration類中的@Bean來實現。比如常配置的檢視解析器:
xml方式
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/"></property> <property name="suffix" value=".jsp"></property> </bean>
由於其他元件的初始化方式完全一致,這裡就不贅述了。需要關注的一點是,當匹配到合適的元件時,都會通過Spring的方式例項化元件。而有些元件在例項化時也會對自身執行環境進行初始化。
initHandlerAdapters(context);
URL-Controller方法對映初始化
在使用SpringMVC時,需要在xml檔案中新增一行註解
<mvc:annotation-driven />
或者在配置類上增加註解@EnableWebMvc,才能使SpringMVC的功能完全開啟。我們以xml的配置為例,看看它都做了什麼。根據spring對namespace的解析策略找到MvcNamespaceHandler類,在其init方法中
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
直接看AnnotationDrivenBeanDefinitionParser的parse方法,上下有一百多行,瀏覽一遍,主要就是註冊各種元件和內部需要的解析器和轉換器。找到HandlerMapping元件的實現
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
因而實際用的HandlerMapping實現即為RequestMappingHandlerMapping。其抽象基類AbstractHandlerMethodMapping實現了InitializingBean介面。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean
當RequestMappingHandlerMapping物件初始化時,會呼叫InitializingBean介面的afterPropertiesSet方法。在此方法中完成了Controller方法同請求url的對映表。
public void afterPropertiesSet() { initHandlerMethods(); } protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } // 預設只從當前上下文中查詢所有beanName 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); } } // 如果類上有@Controller或@RequestMapping註解,則進行解析 if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); }
isHandler通過反射工具類判斷類上是否有 Controller.class 或者 RequestMapping.class 註解
protected boolean isHandler(Class<?> beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class); }
對於類上有@Controller或@RequestMapping註解,都進行了detect。
protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); final Class<?> userType = ClassUtils.getUserClass(handlerType); // 方法內省器,用於發現@RequestMapping註解的方法 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<T>() { @Override public T inspect(Method method) { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } // 遍歷所有有效方法,封裝方法為可執行的Method,註冊到URL-Controller方法對映表 for (Map.Entry<Method, T> entry : methods.entrySet()) { Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); T mapping = entry.getValue(); registerHandlerMethod(handler, invocableMethod, mapping); } }
方法內省器中的內部類呼叫的getMappingForMethod方法為抽象方法,實現在RequestMappingHandlerMapping中。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } } return info; } private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }
解析方法上的@RequestMapping註解返回RequestMappingInfo,其實就是請求對映資訊。
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, RequestCondition<?> customCondition) { return RequestMappingInfo.paths(this.resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name()).customCondition(customCondition).options(this.config).build(); }
而在上面的detect方法最後,註冊URL-Controller方法對映表由registerHandlerMethod方法完成。
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); }
mappingRegistry是AbstractHandlerMethodMapping的核心構成,主要作用就是維護HandlerMethod的對映關係,以及提供對映的查詢方法。其register方法的實現如下:
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); } // 新增mapping->HandlerMethod的對映 this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { // 新增url->mapping的對映 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); } // 新增mapping->MappingRegistration的對映 this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
註冊過程增加了三個個對映關係,一個是mapping->HandlerMethod的對映,一個是url->mapping的對映,一個是mapping->MappingRegistration的對映。通過前兩個對映,可以將請求定位到確定的Controller的方法上,最後一個對映保留所有Mapping註冊資訊,用於unregister。而方法加鎖則是保證所有對映的一致性。
至此,請求URL和Controller方法之間的關係就初始化完成了。上面 initHandlerMappings 就能從容器中拿到所有的HandlerMapping。
引數解析器和返回值解析器的初始化
在使用SpringMVC時,對Controller中方法的引數和返回值的處理都非常的方便。我們知道,常用型別的引數不需要任何額外配置,SpringMVC即可完美轉換,而返回值既可以是ModelAndView, 也可以是String,或者是HashMap,或者是ResponseEntity,多種方式簡單配置,完美相容。它是怎麼做到的呢,通過一系列的轉換器來完成的。而這些轉換器也是需要初始化到執行環境中的, 誰的執行環境, HandlerAdapter的。
同樣SpringMVC提供了一個預設的強大實現,RequestMappingHandlerAdapter,同樣在<mvc:annotation-driven />
中定義。它也實現了InitializingBean介面。
public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans // 初始化Controller通用切面 initControllerAdviceCache(); // 初始化引數解析器 if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } // 初始化InitBinder解析器 if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } // 初始化返回值處理器 if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
每個元件都通過內建預設的實現,我們主要來看引數解析器和返回值處理器兩個。
引數解析器
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
大家根據解析器名稱大概可以推測出其作用,比如@RequestParam解析器,@PathVariable解析器,及@RequestBody和@ResponseBody解析器等等。SpringMVC強大的引數解析能力其實來源於豐富的內建解析器。
另一個返回值處理器的初始化
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters())); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; }
同樣內建了對多種返回型別,返回方式的處理器, 如處理@ResponseBody 的處理器 RequestResponseBodyMethodProcessor,才支撐起豐富便捷的使用。
下一章我們去探究一個請求如何在SpringMVC各元件中進行流轉。