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);
//從配置文件中獲取全局init參數“contextConfigLocation”,也就是spring.xml,並設置到父容器中
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);
//設置容器啟動XML:例如init-param的contextConfigLocation classpath*:spring-mvc.xml
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,是下文中的RequestMappingHandlerMapping,其中包含URL和Mapping的映射Map Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) {
//將從容器中查找出來的HandlerMapping加入到DispatcherServlet的handlerMappings屬性中 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子容器中有RequestMappingHandlerMapping對象,這個對象中有一個mappingRegistry內部類,這個內部類中有URL和Mapping的映射的Map,還有MappingInfo和方法的映射的Map
class MappingRegistry { private final Map<T, AbstractHandlerMethodMapping.MappingRegistration<T>> registry = new HashMap(); private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap(); private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap(); private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap(); private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); MappingRegistry() { } public Map<T, HandlerMethod> getMappings() { return this.mappingLookup; } public List<T> getMappingsByUrl(String urlPath) { return (List)this.urlLookup.get(urlPath); } }
參數解析器和返回值解析器的初始化
在使用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,才支撐起豐富便捷的使用。
同RequestMappingHandlerMapping,RequestMappingHandlerAdapter會被注冊到SpringMvc的容器中,此對象中有所有的處理器,也就是List或者Map,最后在DispatcherServlet的init方法中的initHandlerAdapters方法中會找到這個RequestMappingHandlerAdapter並放到DispatcherServlet的handlerAdapters中
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException var3) { ; } } if (this.handlerAdapters == null) { this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class); if (this.logger.isDebugEnabled()) { this.logger.debug("No HandlerAdapters found in servlet '" + this.getServletName() + "': using default"); } } }
下一章我們去探究一個請求如何在SpringMVC各組件中進行流轉。