Spring 源碼學習(十) Spring mvc

經過前面的 AOP(面向切面編程) 和 Transaction(事務管理),這次來到了 MVC(Web 應用,進行請求分發和處理)

Spring MVC 定義:

分離了控制器(Controller)、模型(Model)、分配器(Adapter)、視圖(View)和處理程序對象(Handler,實際上調用的是 Controller 中定義的邏輯)。

基於 Servlet 功能實現,通過實現了 Servlet 接口的 DispatcherServlet 來封裝其核心功能實現,通過將請求分派給處理程序,同時帶有可配置的處理程序映射、視圖解析、本地語言、主題解析以及上傳文件支持。

同樣老套路,本篇按照以下思路展開:

(1) 介紹如何使用

(2) 輔助工具類 ContextLoaderContext

(3) DispatcherServlet 初始化

(4) DispatcherServlet 處理請求

Table of Contents generated with DocToc

  • 如何使用
  • ContextLoaderContext
  • DispatcherServlet 初始化容器初始化WebApplicationContext 的初始化根容器查找根據 contextAttribute 尋找重新創建實例獲取上下文類 contextClassconfigureAndRefreshWebApplicationContextApplicationContextInitializer加載 Spring 配置註冊 mvc 解析器mvc 初始化默認策略multipartResolver 文件上傳相關LocalResolver 與國際化相關ThemeResolver 主題更換相關HandlerMapping 與匹配處理器相關HandlerAdapter 適配器HandlerExceptionResolver 處理器異常解決器RequestToViewNameTranslator 處理邏輯視圖名稱ViewResolver 視圖渲染FlashMapManager 存儲屬性RequestMappingHandlerMappingRegistryRequestMappingHandlerAdapter
  • DispatcherServlet 的邏輯處理請求上下文請求分發 doDispatch尋找處理器 mappedHandler尋找適配器 HandlerAdapter請求處理Session 代碼塊自定義參數解析邏輯處理返回值解析視圖渲染render
  • 總結
  • 題外話
  • 參考資料

如何使用

代碼結構如下:(詳細代碼可在文章末尾下載)

<code>├── java│   ├── domains│   └── web│       └── controller│           └── BookController.java├── resources│   └── configs└── webapp│   └── WEB-INF│       ├── views│       │   ├── bookView.jsp│       │   └── index.jsp├──     ├── applicationContext.xml│       ├── spring-mvc.xml│       └── web.xml└── build.gradle/<code>

(1)配置 web.xml

在該文件中,主要配置了兩個關鍵點:

1. contextConfigLocation :使 Web 和 Spring 的配置文件相結合的關鍵配置

2. DispatcherServlet : 包含了 SpringMVC 的請求邏輯,使用該類攔截 Web 請求並進行相應的邏輯處理

<code><web-app>        <context-param>        <param-name>contextConfigLocation/<param-name>        <param-value>/WEB-INF/applicationContext.xml/<param-value>    /<context-param>        <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener/<listener-class>    /<listener>        <servlet>        <servlet-name>dispatcherServlet/<servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class>        <init-param>            <param-name>contextConfigLocation/<param-name>            <param-value>/WEB-INF/spring-mvc.xml/<param-value>        /<init-param>    /<servlet>    <servlet-mapping>        <servlet-name>dispatcherServlet/<servlet-name>        <url-pattern>//<url-pattern>    /<servlet-mapping>/<web-app>/<code>

使用 IDEA 時,儘量選擇默認條件和自動掃描加載 Web 配置文件,然後添加 tomcat 進行啟動,具體配置請查閱 idea 創建java web項目ssm-gradle


(2) 配置 applicationContext.xml

<code><beans>    <component-scan>/<beans>/<code>

可以在這裡自定義想要加載的 bean,或者設置數據庫數據源、事務管理器等等 Spring 應用配置。


(3) 配置 spring-mvc.xml

<code><beans>        <component-scan>        <annotation-driven>     
<bean> <property> <property> /<bean>/<beans>/<code>

使用了 InternalResourceViewResolver,它是一個輔助 Bean,這樣配置的意圖是: 在 ModelAndView 返回的視圖名前加上 prefix 指定的前綴和 suffix 的後綴(我理解為用來解析和返回視圖,以及將視圖層進行統一管理,放到指定路徑中)


(4) 創建 BookController

<code>@Controllerpublic class BookController {@RequestMapping(value = "/", method = RequestMethod.GET)public String welcome() {return "index";}@RequestMapping(value = "bookView", method = RequestMethod.GET)public String helloView(Model model) {ComplexBook book1 = new ComplexBook("Spring 源碼深度分析", "技術類");ComplexBook book2 = new ComplexBook("雪國", "文學類");List<complexbook> list = new ArrayList<>(2);list.add(book1);list.add(book2);model.addAttribute("bookList", list);return "bookView";}@RequestMapping(value = "plain")@ResponseBodypublic String plain(@PathVariable String name) {return name;}}/<complexbook>/<code>

可以看出,與書中示例並不一樣,使用的是更貼合我們實際開發中用到的 @RequestMapping 等註解作為例子。根據請求的 URL 路徑,匹配到對應的方法進行處理。


(5) 創建 jsp 文件

<code>index.jsp    <title>Hello World!/<title>

Hello JingQ!

---bookView.jsp <title>Book Shop/<title><foreach> /<foreach>/<code>

按照現在前後端分離的大趨勢,我其實並不想用 jsp 視圖技術作為例子,但考慮到之前入門時也接觸過,也為了跟我一樣不會寫前端的同學更好理解,所以還是記錄一下如何使用 jsp。


(6) 添加依賴 build.gradle

<code>// 引入 spring-web 和 spring-webmvc,如果不是跟我一樣使用源碼進行編譯,請到 mvn 倉庫中尋找對應依賴optional(project(":spring-web"))optional(project(":spring-webmvc"))// 引入這個依賴,使用 jsp 語法 https://mvnrepository.com/artifact/javax.servlet/jstlcompile group: 'javax.servlet', name: 'jstl', version: '1.2'/<code>

(7) 啟動 Tomcat 如何配置和啟動,網上也有很多例子,參考資料 3 是個不錯的例子,下面是請求處理結果:

http://localhost:8080/bookView (使用了 JSP 視圖進行渲染)

Spring 源碼學習(十) Spring mvc

http://localhost:8080/plain/value (前後端分離的話,常用的是這種,最後可以返回簡單字符或者 json 格式的對象等)

Spring 源碼學習(十) Spring mvc

在剛才的 web.xml 中有兩個關鍵配置,所以現在學習下這兩個配置具體是幹啥的。


ContextLoaderContext

作用:在啟動 web 容器時,自動裝載 ApplicationContext 的配置信息。

下面是它的繼承體系圖:


Spring 源碼學習(十) Spring mvc


這是一個輔助工具類,可以用來傳遞配置信息參數,在 web.xml 中,將路徑以 context-param 的方式註冊並使用 ContextLoaderListener 進行監聽讀取。

從圖中能看出,它實現了 ServletContextListener 這個接口,只要在 web.xml 配置了這個監聽器,容器在啟動時,就會執行 contextInitialized(ServletContextEvent) 這個方法,進行應用上下文初始化。

<code>public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}/<code>

每個 Web 應用都會有一個 ServletContext 貫穿生命週期(在應用啟動時創建,關閉時銷燬),跟 Spring 中 ApplicationContext 類似,在全局範圍內有效。

實際上初始化的工作,是由父類 ContextLoader 完成的:(簡略版)

<code>public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {    // demo 中用到的根容器是 Spring 容器 WebApplicationContext.class.getName() + ".ROOT"if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {// web.xml 中存在多次 ContextLoader 定義throw new IllegalStateException();}long startTime = System.currentTimeMillis();    // 將上下文存儲在本地實例變量中,以保證在 ServletContext 關閉時可用。    if (this.context == null) {    // 初始化 context    this.context = createWebApplicationContext(servletContext);    }    if (this.context instanceof ConfigurableWebApplicationContext) {    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;    if (!cwac.isActive()) {    if (cwac.getParent() == null) {    ApplicationContext parent = loadParentContext(servletContext);    cwac.setParent(parent);    }    configureAndRefreshWebApplicationContext(cwac, servletContext);    }    }    // 記錄在 ServletContext 中    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);    ClassLoader ccl = Thread.currentThread().getContextClassLoader();    if (ccl == ContextLoader.class.getClassLoader()) {    currentContext = this.context;    }    else if (ccl != null) {    currentContextPerThread.put(ccl, this.context);    }    if (logger.isInfoEnabled()) {    // 計數器,計算初始化耗時時間    long elapsedTime = System.currentTimeMillis() - startTime;    logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");    }    return this.context;}/<code>

該函數主要是體現了創建 WebApplicationContext 實例的一個功能架構,實現的大致步驟如下:

1. WebApplicationContext 存在性的驗證:

只能初始化一次,如果有多個聲明,將會擾亂 Spring 的執行邏輯,所以有多個聲明將會報錯。 2. 創建 WebApplicationContext 實例: createWebApplicationContext(servletContext);

<code>protected Class> determineContextClass(ServletContext servletContext) {    // defaultStrategies 是個靜態變量,在靜態代碼塊中初始化    contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}/** * 默認策略 */private static final Properties defaultStrategies;static {try {// 從 ContextLoader.properties 文件中加載默認策略// 在這個目錄下:org/springframework/web/context/ContextLoader.propertiesClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());}}org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext/<code>

如果按照默認策略,它將會從配置文件 ContextLoader.properties 中讀取需要創建的實現類:XmlWebApplicationContext

3. 將實例記錄在 servletContext 中 4. 映射當前的類加載器與創建的實例到全局變量 currentContextPerThread 中

通過以上步驟,完成了創建 WebApplicationContext 實例,它繼承自 ApplicaitonContext,在父類的基礎上,追加了一些特定於 web 的操作和屬性,可以把它當成我們之前初始化 Spring 容器時所用到的 ClassPathApplicaitonContext 那樣使用。


DispatcherServlet 初始化

該類是 spring-mvc 的核心,該類進行真正邏輯實現,DisptacherServlet 實現了 Servlet 接口。

Spring 源碼學習(十) Spring mvc

介紹:

servlet 是一個 Java 編寫的程序,基於 Http 協議,例如我們常用的 Tomcat,也是按照 servlet 規範編寫的一個 Java 類

servlet 的生命週期是由 servlet 的容器來控制,分為三個階段:初始化、運行和銷燬。

在 servlet 初始化階段會調用其 init 方法:

HttpServletBean#init


<code>public final void init() throws ServletException {// 解析 init-param 並封裝到 pvs 變量中PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);// 將當前的這個 Servlet 類轉換為一個 BeanWrapper,從而能夠以 Spring 的方式對 init—param 的值注入BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());// 註冊自定義屬性編輯器,一旦遇到 Resource 類型的屬性將會使用 ResourceEditor 進行解析bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));// 空實現,留給子類覆蓋initBeanWrapper(bw);bw.setPropertyValues(pvs, true);// 初始化 servletBean (讓子類實現,這裡它的實現子類是 FrameworkServlet)initServletBean();}/<code>

在這裡初始化 DispatcherServlet,主要是通過將當前的 servlet 類型實例轉換為 BeanWrapper 類型實例,以便使用 Spring 中提供的注入功能進行相應屬性的注入。

從上面註釋,可以看出初始化函數的邏輯比較清晰,封裝參數、轉換成 BeanWrapper 實例、註冊自定義屬性編輯器、屬性注入,以及關鍵的初始化 servletBean。


容器初始化

下面看下初始化關鍵邏輯:

FrameworkServlet#initServletBean

剝離了日誌打印後,剩下的兩行關鍵代碼

<code>protected final void initServletBean() throws ServletException {// 僅剩的兩行關鍵代碼this.webApplicationContext = initWebApplicationContext();// 留給子類進行覆蓋實現,但我們例子中用的 DispatcherServlet 並沒有覆蓋,所以先不用管它initFrameworkServlet();}/<code>

WebApplicationContext 的初始化

FrameworkServlet#initWebApplicationContext

該函數的主要工作就是創建或刷新 WebApplicationContext 實例並對 servlet 功能所使用的變量進行初始化。

<code>protected WebApplicationContext initWebApplicationContext() {// 從根容器開始查找WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {// 有可能在 Spring 加載 bean 時,DispatcherServlet 作為 bean 加載進來了// 直接使用在構造函數被注入的 context 實例wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}// 刷新上下文環境configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// 根據 contextAttribute 屬性加載 WebApplicationContextwac = findWebApplicationContext();}if (wac == null) {// 經過上面步驟都沒找到,那就來創建一個wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {// 刷新,初始化很多策略方法onRefresh(wac);}}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;}/<code>

根容器查找

我們最常用到的 spring-mvc,是 spring 容器和 web 容器共存,這時 rootContext 父容器就是 spring 容器。

在前面的 web.xml 配置的監聽器 ContextLaoderListener,已經將 Spring 父容器進行了加載

WebApplicationContextUtils#getWebApplicationContext(ServletContext)


<code>public static WebApplicationContext getWebApplicationContext(ServletContext sc) {// key 值 :WebApplicationContext.class.getName() + ".ROOT"// (ServletContext) sc.getAttribute(attrName) ,return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);}/<code>

同時,根據上面代碼,瞭解到 Spring 父容器,是以 key 值為 : WebApplicationContext.class.getName() + ".ROOT" 保存到 ServletContext 上下文中。


根據 contextAttribute 尋找

雖然有默認 key,但用戶可以重寫初始化邏輯(在 web.xml 文件中設定 servlet 參數 contextAttribute),使用自己創建的 WebApplicaitonContext,並在 servlet 的配置中通過初始化參數 contextAttribute 指定 key。

<code>protected WebApplicationContext findWebApplicationContext() {String attrName = getContextAttribute();if (attrName == null) {return null;}// attrName 就是用戶在`web.xml` 文件中設定的 `servlet` 參數 `contextAttribute`WebApplicationContext wac =WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");}return wac;}/<code>

重新創建實例

通過前面的方法都沒找到,那就來重新創建一個新的實例:

FrameworkServlet#createWebApplicationContext(WebApplicationContext)


<code>protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {return createWebApplicationContext((ApplicationContext) parent);}protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {// 允許我們自定義容器的類型,通過 contextClass 屬性進行配置// 但是類型必須要繼承 ConfigurableWebApplicationContext,不然將會報錯Class> contextClass = getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException();}// 通過反射來創建 contextClassConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());wac.setParent(parent);// 獲取 contextConfigLocation 屬性,配置在 servlet 初始化函數中String configLocation = getContextConfigLocation();    wac.setConfigLocation(configLocation);// 初始化 Spring 環境包括加載配置環境configureAndRefreshWebApplicationContext(wac);return wac;}/<code>

獲取上下文類 contextClass

默認使用的是 XmlWebApplicationContext,但如果需要配置自定義上下文,可以在 web.xml 中的 <init-param> 標籤中修改 contextClass 屬性對應的 value/<init-param>


configureAndRefreshWebApplicationContext

使用該方法,用來對已經創建的 WebApplicaitonContext 進行配置以及刷新

<code>protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {    // 遍歷 ApplicationContextInitializer,執行 initialize 方法applyInitializers(wac);// 關鍵的刷新,加載配置文件及整合 parent 到 wacwac.refresh();}/<code>

ApplicationContextInitializer

該類可以通過 <init-param> 的 contextInitializerClasses 進行自定義配置:/<init-param>

<code><init-param>    <param-name>contextInitializerClasses/<param-name>    <param-value>自定義類,需繼承於 `ApplicationContextInitializer`/<param-value>/<init-param>/<code>

正如代碼中的順序一樣,是在 mvc 容器創建前,執行它的 void initialize(C applicationContext) 方法:

<code>protected void applyInitializers(ConfigurableApplicationContext wac) {    AnnotationAwareOrderComparator.sort(this.contextInitializers);for (ApplicationContextInitializer<configurableapplicationcontext> initializer : this.contextInitializers) {initializer.initialize(wac);}}/<configurableapplicationcontext>/<code>

所有如果沒有配置的話,默認情況下 contextInitializers 列表為空,表示沒有 ApplicationContextInitializer 需要執行。


加載 Spring 配置

wac.refresh(),實際調用的是我們之前就很熟悉的刷新方法:

org.springframework.context.support.AbstractApplicationContext#refresh

從圖中能夠看出,刷新方法的代碼邏輯與之前一樣,通過父類 AbstractApplicationContext 的 refresh 方法,進行了配置文件的加載。

在例子中的 web.xml 配置中,指定了加載 spring-mvc.xml 配置文件

<code><servlet><servlet-name>dispatcherServlet/<servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class><init-param><param-name>contextConfigLocation/<param-name><param-value>/WEB-INF/spring-mvc.xml/<param-value>/<init-param>/<servlet>/<code>

註冊 mvc 解析器

由於我們配置了 contextConfigLocation,指定了加載資源的路徑,所以在 XmlWebApplicationContext 初始化的時候,加載的 Spring 配置文件路徑是我們指定 spring-mvc.xml:

在 spring-mvc.xml 配置中,主要配置了三項


<code><component-scan><annotation-driven><bean>    <property>    <property>/<bean>/<code>

同樣老套路,使用了 <annotation> 自定義註解的話,要註冊相應的解析器後,Spring 容器才能解析元素:/<annotation>

org.springframework.web.servlet.config.MvcNamespaceHandler


<code>public void init() {// MVC 標籤解析需要註冊的解析器registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());}/<code> 

可以看到,mvc 提供了很多便利的註解,有攔截器、資源、視圖等解析器,但我們常用的到的是 anntation-driven 註解驅動,這個註解通過 AnnotationDrivenBeanDefinitionParser 類進行解析,其中會註冊兩個重要的 bean :

<code>class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();...}/<code>

跳過其他熟悉的 Spring 初始化配置,通過上面的步驟,完成了 Spring 配置文件的解析,將掃描到的 bean 加載到了 Spring 容器中。

那麼下面就正式進入 mvc 的初始化。


mvc 初始化

onRefresh 方法是 FrameworkServlet 類中提供的模板方法,在子類 DispatcherServlet 進行了重寫,主要用來刷新 Spring 在 Web 功能實現中所必須用到的全局變量:

<code>protected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {// 初始化 multipartResolver 文件上傳相關initMultipartResolver(context);// 初始化 LocalResolver 與國際化相關initLocaleResolver(context);// 初始化 ThemeResolver 與主題更換相關initThemeResolver(context);// 初始化 HandlerMapping 與匹配處理器相關initHandlerMappings(context);// 初始化 HandlerAdapter 處理當前 Http 請求的處理器適配器實現,根據處理器映射返回相應的處理器類型initHandlerAdapters(context);// 初始化 HandlerExceptionResolvers,處理器異常解決器initHandlerExceptionResolvers(context);// 初始化 RequestToViewNameTranslator,處理邏輯視圖名稱initRequestToViewNameTranslator(context);// 初始化 ViewResolver 選擇合適的視圖進行渲染initViewResolvers(context);// 初始化 FlashMapManager 使用 flash attributes 提供了一個請求存儲屬性,可供其他請求使用(重定向時常用)initFlashMapManager(context);}/<code> 

該函數是實現 mvc 的關鍵所在,先來大致介紹一下初始化的套路:

  1. 尋找用戶自定義配置
  2. 沒有找到,使用默認配置

顯然,Spring 給我們提供了高度的自定義,可以手動設置想要的解析器,以便於擴展功能。

如果沒有找到用戶配置的 bean,那麼它將會使用默認的初始化策略: getDefaultStrategies 方法


默認策略

DispatcherServlet#getDefaultStrategies(縮減版)


<code>protected  List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {// 策略接口名稱String key = strategyInterface.getName();// 默認策略列表String value = defaultStrategies.getProperty(key);String[] classNames = StringUtils.commaDelimitedListToStringArray(value);List strategies = new ArrayList<>(classNames.length);for (String className : classNames) {// 實例化Class> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());Object strategy = createDefaultStrategy(context, clazz);strategies.add((T) strategy);}return strategies;}// 默認策略列表private static final Properties defaultStrategies;static {// 路徑名稱是:DispatcherServlet.propertiestry {ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}} 
/<code>

從靜態默認策略屬性 defaultStrategies 的加載過程中,讀取的是 DispatcherServlet.properties 文件內容,看完下面列出來的信息,相信你跟我一樣恍然大悟,瞭解 Spring 配置了哪些默認策略:

<code>org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager/<code>

接下來看看它們各自的初始化過程以及使用場景:


multipartResolver 文件上傳相關

<code>private void initMultipartResolver(ApplicationContext context) {    try {this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);catch (NoSuchBeanDefinitionException ex) {// Default is no multipart resolver.this.multipartResolver = null;    }}/<code>

默認情況下,Spring 是沒有 mulitpart 處理,需要自己設定

<code><bean>/<code>

註冊的 id 為 multipartResolver


LocalResolver 與國際化相關

LocalResolver 接口定義瞭如何獲取客戶端的地區

<code>private void initLocaleResolver(ApplicationContext context) {try {    this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);}catch (NoSuchBeanDefinitionException ex) {    // We need to use the default.    this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);    }}/<code>

通過尋找 id 為 localeResolver 的 bean,如果沒有的話,將會使用默認的策略進行加載 AcceptHeaderLocaleResolver,它是基於 URL 參數來控制國際化,例如使用

當然還有其他兩種,基於 session 和基於 cookie 的配置,想要深入瞭解的可以去細看~


ThemeResolver 主題更換相關

主題是一組靜態資源(例如樣式表 css 和圖片 image),也可以理解為應用皮膚,使用 Theme 更改主題風格,改善用戶體驗。

默認註冊的 id 是 themeResolver,類型是 FixedThemeResolver,表示使用的是一個固定的主題,以下是它的繼承體系圖:

Spring 源碼學習(十) Spring mvc

工作原理是通過攔截器攔截,配置對應的主題解析器,然後返回主題名稱,還是使用上面的解析器作為例子:

FixedThemeResolver#resolveThemeName


<code>public String resolveThemeName(HttpServletRequest request) {return getDefaultThemeName();}public String getDefaultThemeName() {return this.defaultThemeName;}/<code>

HandlerMapping 與匹配處理器相關

首先判斷 detectAllHandlerMappings 變量是否為 true,表示是否需要加載容器中所有的 HandlerMapping,false 將會加載用戶配置的。

如註釋所說,至少得保證有一個 HandlerMapping,如果前面兩個分支都沒尋找到,那麼就進行默認策略加載。

<code>private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// 默認情況下,尋找應用中所有的 HandlerMapping ,包括祖先容器(其實就是 Spring 容器啦)Map<string> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// handlerMapping 有優先級,需要排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {// 從上下文中,獲取名稱為 handlerMapping 的 beanHandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}// 需要保證,至少有一個 HandlerMapping// 如果前面兩步都沒找到 mapping,將會由這裡加載默認策略if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);}}/<string>/<code>

通過 Debug 得知,之前在加載 Spring 配置時,就已經注入了 RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping

Spring 源碼學習(十) Spring mvc


HandlerAdapter 適配器

套路與前面的一樣,使用的默認策略是:HttpRequestHandlerAdapter 、SimpleControllerHandlerAdapter、 RequestMappingHandlerAdapter 和 HandlerFunctionAdapter。

說到適配器,可以將它理解為,將一個類的接口適配成用戶所期待的,將兩個接口不兼容的工作類,通過適配器連接起來。


HandlerExceptionResolver 處理器異常解決器

套路也與前面一樣,使用的默認策略是:ExceptionHandlerExceptionResolver、 ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver。

實現了 HandlerExceptionResolver 接口的 resolveException 方法,在方法內部對異常進行判斷,然後嘗試生成 ModelAndView 返回。

<code>public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);ModelAndView result = doResolveException(request, response, handler, ex);return result;}else {return null;}}/<code>

RequestToViewNameTranslator 處理邏輯視圖名稱

初始化代碼邏輯與前面一樣,使用的默認策略是:DefaultRequestToViewNameTranslator

使用場景:當 Controller 處理器方法沒有返回邏輯視圖名稱時,Spring 通過該類的約定,提供一個邏輯視圖名稱。

由於本地測試不出來,所以引用參考資料 7 的例子:

DefaultRequestToViewNameTranslator的轉換例子:

http://localhost:8080/gamecast/display.html -> display(視圖)


ViewResolver 視圖渲染

套路還是跟前面一樣,默認策略使用的是:InternalResourceViewResolver

同時,這也是 demo 中,我們手動配置的視圖解析器


FlashMapManager 存儲屬性

默認使用的是:SessionFlashMapManager,通過與 FlashMap 配合使用,用於在重定向時保存/傳遞參數

例如 Post/Redirect/Get 模式,Flash attribute 在重定向之前暫存(根據類名,可以知道範圍是 session 級別有效),以便重定向之後還能使用。


RequestMappingHandler

該類作用:配合 @Controller 和 @RequestMapping 註解使用,通過 URL 來找到對應的處理器。

前面在 spring-mvc.xml 文件加載時,初始化了兩個重要配置,其中一個就是下面要說的 RequestMappingHandler,先來看它的繼承體系圖:

Spring 源碼學習(十) Spring mvc

從繼承圖中看到,它實現了 InitializingBean 接口,所以在初始化時,將會執行 afterPropertiesSet 方法(圖片中註釋寫錯方法,請以下面為準),核心調用的初始化方法是父類 AbstractHandlerMethodMapping#initHandlerMethods 方法

AbstractHandlerMethodMapping#initHandlerMethods


<code>protected void initHandlerMethods() {// 獲取容器中所有 bean 名字for (String beanName : this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :obtainApplicationContext().getBeanNamesForType(Object.class)) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {    // 如果前綴不是 scopedTarget.        // 執行 detectHandlerMethods() 方法Class> beanType = obtainApplicationContext().getType(beanName);if (beanType != null && isHandler(beanType)) {    detectHandlerMethods(beanName);    }}}// 打印數量,可以當成空實現handlerMethodsInitialized(getHandlerMethods());}protected void detectHandlerMethods(Object handler) {Class> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {Class> userType = ClassUtils.getUserClass(handlerType);// 通過反射,獲取類中所有方法// 篩選出 public 類型,並且帶有 @RequestMapping 註解的方法Map<method> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup) method -> {// 通過 RequestMappingHandlerMapping.getMappingForMethod 方法組裝成 RequestMappingInfo(映射關係)return getMappingForMethod(method, userType);});methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);// 通過 mappingRegistry 進行註冊上面獲取到的映射關係registerHandlerMethod(handler, invocableMethod, mapping);});}} 
/<method>/<code>

梳理一下代碼邏輯,initHandlerMethods 方法將會掃描註冊 bean 下所有公共 public 方法,如果帶有 @RequestMapping 註解的,將會組裝成 RequestMappingInfo 映射關係,然後將它註冊到 mappingRegistry 變量中。之後可以通過映射關係,輸入 URL 就能夠找到對應的處理器 Controller。


MappingRegistry

該類是 AbstractHandlerMethodMapping 的內部類,是個工具類,用來保存所有 Mapping 和 handler method,通過暴露加鎖的公共方法,避免了多線程對該類的內部變量的覆蓋修改。

下面是註冊的邏輯:

<code>public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {// 包裝 bean 和方法HandlerMethod handlerMethod = createHandlerMethod(handler, method);// 校驗validateMethodMapping(handlerMethod, mapping);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);}// 將映射關係放入  Map> registrythis.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {this.readWriteLock.writeLock().unlock();}}/<string>/<code>

通過前面的包裝和校驗方法,最後映射關係將會放入這裡 Map> registry。它是一個泛型的 Map,key 類型是 RequestMappingInfo,保存了 @RequestMapping 各種屬性的集合,value 類型是 AbstractHandlerMethodMapping,保存的是我們的映射關係。

從圖中可以看出,如果輸入的 URL 是 /plain/{name},將會找到對應的處理方法 web.controller.BookController#plain{String}。


RequestMappingHandlerAdapter

而另一個重要的配置就是處理器適配器 RequestMappingHandlerAdapter,由於它的繼承體系與 RequestMappingHandler 類似,所以我們直接來看它在加載時執行的方法

RequestMappingHandlerAdapter#afterPropertiesSet


<code>public void afterPropertiesSet() {// 首先執行這個方法,可以添加 responseBody 切面 beaninitControllerAdviceCache();// 參數處理器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);}}/<handlermethodreturnvaluehandler>/<handlermethodargumentresolver>/<handlermethodargumentresolver>/<code>

所以看到這個適配器中,初始化了很多工具變量,用來處理 @ControllerAdvice 、InitBinder 等註解和參數。不過核心還是待會要講到的 handleInternal() 方法,它將適配處理器調用,然後返回 ModelView 視圖。


DispatcherServlet 的邏輯處理

請求處理的入口定義在 HttpServlet,主要有以下幾個方法:

Spring 源碼學習(十) Spring mvc

當然,父類 HttpServlet 只是給出了定義,直接調用父類這些方法將會報錯,所以 FrameworkServlet 將它們覆蓋重寫了處理邏輯:

<code>protected final void doGet(HttpServletRequest request, HttpServletResponse response) {// 註解 10. 具體調用的是 processRequest 方法processRequest(request, response);}protected final void doPost(HttpServletRequest request, HttpServletResponse response) {processRequest(request, response);}/<code>

可以看到 doGet 、doPost 這些方法,底層調用的都是 processRequest 方法進行處理,關鍵方法是委託給子類 DispatcherServlet 的 doServie() 方法

DispatcherServlet#doService


<code>protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 暫存請求參數Map<string> attributesSnapshot = null;...// 經過前面的準備(屬性、輔助變量),進入請求處理過程doDispatch(request, response);}/<string>/<code>

請求分發和處理邏輯的核心是在 doDispatch(request, response) 方法中,在進入這個方法前,還有些準備工作需要執行。


請求上下文

在 processRequest 的 doServie() 方法執行前,主要做了這以下準備工作:

(1) 為了保證當前線程的 LocaleContext 以及 RequestAttributes 可以在當前請求後還能恢復,提取當前線程的兩個屬性。 (2) 根據當前 request 創建對應的 LocaleContext 以及 RequestAttributes,綁定到當前線程 (3) 往 request 對象中設置之前加載過的 localeResolver、flashMapManager 等輔助工具變量


請求分發 doDispatch

經過前面的配置設置,doDispatch 函數展示了請求的完成處理過程:

DispatcherServlet#doDispatch


<code>protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;// 註釋 10. 檢查是否 MultipartContent 類型processedRequest = checkMultipart(request);// 根據 request 信息尋找對應的 HandlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {// 沒有找到 handler,通過 response 向用戶返回錯誤信息noHandlerFound(processedRequest, response);return;}// 根據當前的 handler 找到對應的 HandlerAdapter 適配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 如果當前 handler 支持 last-modified 頭處理String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 攔截器的 preHandler 方法的調用if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 真正激活 handler 進行處理,並返回視圖mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// 視圖名稱轉換(有可能需要加上前後綴)applyDefaultViewName(processedRequest, mv);// 應用所有攔截器的 postHandle 方法mappedHandler.applyPostHandle(processedRequest, response, mv);// 處理分發的結果(如果有 mv,進行視圖渲染和跳轉)processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}/<code>

上面貼出來的代碼略有縮減,不過從上面示例中能看出,整體的邏輯都挺清晰的,主要步驟如下:

1. 尋找處理器 mappedandler 2. 根據處理器,尋找對應的適配器 HandlerAdapter 3. 激活 handler,調用處理方法 4. 返回結果(如果有 mv,進行視圖渲染和跳轉)


尋找處理器 mappedHandler

以 demo 說明,尋找處理器,就是根據 URL 找到對應的 Controller 方法

DispatcherServlet#getHandler


<code>protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {// 遍歷註冊的全部 handlerMappingfor (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}/<code>

實際上,在這一步遍歷了所有註冊的 HandlerMapping,然後委派它們去尋找處理器,如果找到了合適的,就不再往下尋找,直接返回。

同時,HandlerMapping 之間有優先級的概念,根據 mvc 包下 AnnotationDrivenBeanDefinitionParser 的註釋:

This class registers the following {@link HandlerMapping HandlerMappings} @link RequestMappingHandlerMapping ordered at 0 for mapping requests to annotated controller methods.

說明了 RequestMappingHandlerMapping 的優先級是最高的,優先使用它來尋找適配器。

具體尋找調用的方法:

AbstractHandlerMapping#getHandler


<code>public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 根據 Request 獲取對應的 handlerObject handler = getHandlerInternal(request);// 將配置中的對應攔截器加入到執行鏈中,以保證這些攔截器可以有效地作用於目標對象HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (hasCorsConfigurationSource(handler)) {CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);config = (config != null ? config.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}/<code>

(1) getHandlerInternal(request) 函數作用:

根據 request 信息獲取對應的 Handler,也就是我們例子中的,通過 URL 找到匹配的 Controller 並返回。

(2) getHandlerExcetionChain 函數作用:

將適應該 URL 對應攔截器 MappedInterceptor 加入 addInterceptor() 到執行鏈 HandlerExecutionChain 中。

(3) CorsConfiguration

這個參數涉及到跨域設置,具體看下這篇文章:SpringBoot下如何配置實現跨域請求?


尋找適配器 HandlerAdapter

前面已經找到了對應的處理器了,下一步就得找到它對應的適配器

DispatcherServlet#getHandlerAdapter


<code>protected  getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}}/<code>

同樣,HandlerAdapter 之間也有優先級概念,由於第 0 位是 RequestMappingHandlerAdapter,而它的 supports 方法總是返回 true,所以毫無疑問返回了它


請求處理

通過適配器包裝了一層,處理請求的入口如下:

RequestMappingHandlerAdapter#handleInternal


<code>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 necessarymav = 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;}/<code>

通過 invokeHandlerMethod 方法,調用對應的 Controller 方法邏輯,包裝成 ModelAndView。


Session 代碼塊

判斷 synchronizeOnSession 是否開啟,開啟的話,同一個 session 的請求將會串行執行(Object mutex = WebUtils.getSessionMutex(session))


自定義參數解析

解析邏輯由 RequestParamMethodArgumentResolver 完成,具體請查看 spring-mvc


邏輯處理

InvocableHandlerMethod#invokeForRequest


<code>public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);return doInvoke(args);}/<code>

通過給定的參數,doInvoke 使用了反射操作,執行了 Controller 方法的邏輯。


返回值解析

拿 http://localhost:8080/bookView 作為例子,經過前面的邏輯處理後,返回的只是試圖名稱 bookView,在這時,使用到了 ViewNameMethodReturnValueHandler

Spring 源碼學習(十) Spring mvc

可以看到它實現了 HandlerMethodReturnValueHandler 接口的兩個方法

ViewNameMethodReturnValueHandler#supportsReturnType; 表示支持處理的返回類型


<code>public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}}}/<code>

ViewNameMethodReturnValueHandler#handleReturnValue; 返回處理值,給 mavContainer 設置視圖名稱 viewName


<code>public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}}}/<code>

最後在適配器中包裝成了 ModelAndView 對象



視圖渲染

根據處理器執行完成後,適配器包裝成了 ModelAndView 返回給 DispatcherServlet 繼續進行處理,來到了視圖渲染的步驟:

DispatcherServlet#processDispatchResult


<code>private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;// 跳過了異常判斷 =-=// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {// 如果視圖不為空並且 clear 屬性為 false, 進行視圖渲染render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}/<code>

render

還記得我們使用的是 jsp 視圖進行渲染麼,引用的依賴是 jstl,所以視圖渲染的是 JstlView 類提供的方法,以下是它的繼承體系:

Spring 源碼學習(十) Spring mvc

渲染調用的是其父類的方法:

InternalResourceView#renderMergedOutputModel

在給定指定模型的情況下呈現內部資源。這包括將模型設置為請求屬性


<code>protected void renderMergedOutputModel(Map<string> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.rd.forward(request, response);}}/<string>/<code>

最後發現渲染調用的是第三方依賴 org.apache.catalina.core.ApplicationDispatcher 進行視圖繪製,所以不再跟蹤下去。

所以整個視圖渲染過程,就是在前面將 Model 視圖對象中的屬性設置到請求 request 中,最後通過原生(tomcat)的 ApplicationDispatcher 進行轉發,渲染成視圖。


總結

本篇比較完整的描述了 spring-mvc 的框架體系,結合 demo 和代碼,將調用鏈路梳理了一遍,瞭解了每個環節註冊的工具類或解析器,瞭解了 Spring 容器和 Web 容器是如何合併使用,也瞭解到 mvc 初始化時加載的默認策略和請求完整的處理邏輯。

總結起來,就是我們在開頭寫下的內容:

(1) 介紹如何使用

(2) 輔助工具類 ContextLoaderContext

(3) DispatcherServlet 初始化

(4) DispatcherServlet 處理請求


分享到:


相關文章: