SpringMVC加載流程

 這節介紹SpringMVC,SpringMVC是一種基於Java的實現MVC設計模式的請求驅動類型的輕量級Web框架。本章會介紹相關概念,流程,再從源碼進行講解。前端

1. MVC

 MVC(Model View Controller)是一種軟件設計的框架模式,它採用模型(Model)-視圖(View)-控制器(controller)的方法把業務邏輯、數據與界面顯示分離。MVC框架模式是一種複合模式,MVC的三個核心部件分別是java

  • Model(模型):全部的用戶數據、狀態以及程序邏輯,獨立於視圖和控制器
  • View(視圖):呈現模型,相似於Web程序中的界面,視圖會從模型中拿到須要展示的狀態以及數據,對於相同的數據能夠有多種不一樣的顯示形式(視圖)
  • Controller(控制器):負責獲取用戶的輸入信息,進行解析並反饋給模型,一般狀況下一個視圖具備一個控制器

2. SpringMVC流程

 基本上你們都會在網上看到這張圖:web

file

這個圖描述了SpringMVC處理一個Http請求的基本流程,對應的流程爲:spring

  • 用戶發送請求至前端控制器DispatcherServlet。
  • DispatcherServlet收到請求調用HandlerMapping處理器映射器。
  • 處理器映射器找到具體的處理器(能夠根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(若是有則生成)一併返回給DispatcherServlet。
  • DispatcherServlet調用HandlerAdapter處理器適配器。
  • HandlerAdapter通過適配調用具體的處理器(Controller,也叫後端控制器)。
  • Controller執行完成返回ModelAndView。
  • HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。
  • DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
  • ViewReslover解析後返回具體View.
  • DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。
  • DispatcherServlet響應用戶。

 還有你們都會接觸到的demo:json

web.xmlsegmentfault

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <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>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

在applicationContext.xml,指定包的掃描訪問並添加標籤後端

<context:component-scan base-package="xxx" />

<mvc:annotation-driven />

添加Controller設計模式

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String excute() {
        return "hello";
    }
}

上面表示的意思爲:spring-mvc

  • 開啓ContextLoaderListener加載Spring根Context,對應的配置文件爲applicationContext.xml
  • 開啓DispatcherServlet監聽/*下的全部請求,加載WebContext,對應的配置文件爲dispatcher-servlet.xml
  • 指定掃描包路徑,並開啓mvc註解支持
  • 添加對應的Controller,使用@RestController標記爲Controller對象並使用@RequestMapping標記處理的請求路徑

3. SpringMVC加載流程

 SpringMVC的加載是依賴Servlet切入的,主要依賴兩個技術點:Listener和Servlet。緩存

3.1. ContextLoaderListener的加載

 從web.xml中能夠知道,ContextLoaderListener依賴於Servlet的Listener技術。Listener是在servlet2.3中加入的,主要用於對session、request、context等進行監控。使用Listener須要實現相應的接口。觸發Listener事件的時候,tomcat會自動調用相應的Listener的方法。經常使用的監聽接口包括:

  • HttpSessionListener:監聽session的建立和銷燬。
  • ServletRequestListener:監聽request的建立和銷燬
  • ServletContextListener:監聽context的建立和銷燬。

這裏主要使用了ServletContextListener,用於在Servlet初始化前執行自定義動做。

 ContextLoaderListener的定義以下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

該類繼承了ContextLoader,並經過contextInitialized方法執行了初始化(傳入ServletContext),經過contextDestroyed方法進行資源銷燬回收。重點看ContextLoader方法。

 ContextLoader在初始化時,會先執行內部的一個靜態代碼塊:

private static final Properties defaultStrategies;

static {
    try {
        ClassPathResource 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());
    }
}

這一步會加載classpath下的配置文件ContextLoader.properties,該文件將做爲默認配置用於初始化Properties對象defaultStrategies。

3.1.1. contextInitialized

 contextInitialized方法的主要內容以下:

file

過程爲:

(1) 判斷當前Context是否已經初始化過

 經過判斷ServletContext中是否存在key爲org.springframework.web.context.ROOT的值

  • 初始化WebApplicationContext:從ContextLoader.properties中查找WebApplicationContext的具體實現,以下:

    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

即XmlWebApplicationContext,而後初始化該類

(2) 配置並刷新該XMLWebApplicationContext

 XMLWebApplicationContext繼承簡圖以下:

file

層級比較明顯,自己也是一個RefreshableConfigApplicationContext(具體內容能夠看往期內容)。其父類保存了ServletContext和ServletConfig兩個Web Context相關的對象,其自己也維持了一些默認屬性,如

DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

這個屬性就是默認的Spring配置文件的路徑。

 須要指出的是XMLWebApplicationContext重寫了父類的loadBeanDefinitions方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    beanDefinitionReader.setEnvironment(getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    initBeanDefinitionReader(beanDefinitionReader);
    loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}
    
@Override
protected String[] getDefaultConfigLocations() {//Tip:返回配置文件路徑
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

這裏用了XmlBeanDefinitionReader來解析Bean定義,且指定了配置文件的加載邏輯,getConfigLocations方法:若是父類的configLocations不爲空,則返回該值,不然返回getDefaultConfigLocations的值。而getDefaultConfigLocations方法邏輯爲:若是存在命名空間,則返回/WEB_INF/namespace.xml做爲配置文件,不然返回/WEB-INF/applicationContext.xml。對應上面的demo,將返回配置中的文件(同默認值相同)。

 XMLWebApplicationContext的初始化步驟爲:

  • 讀取contextId配置,進行設置
  • 讀取contextConfigLocation配置,使用指定的配置文件,若沒有則使用上面提到的默認配置文件DEFAULT_CONFIG_LOCATION
  • 加載contextInitializerClasses指定的class,用於在context刷新前執行自定義處理
  • 調用XMLWebApplicationContext的refresh方法

(3)標記已經初始化

 經過將該根Context存在ServletContext中,並設置值爲org.springframework.web.context.ROOT,用於第(1)步的判斷

3.1.2. contextDestroyed

 銷燬過程比較簡單,首先調用WebApplicationContext的close方法銷燬該Context,而後移除ServletContex中的org.springframework.web.context.ROOT屬性值,最後清除ServletContext中全部org.springframework.開頭的屬性值。

3.2. DispatcherServlet的加載

 同ContextLoaderListener相似,DispatcherServlet依賴於Servlet進行擴展。DispatcherServlet的結構以下:

file

如上,DispatcherServlet繼承自HttpServlet,並重寫了doService方法,用於處理http請求,其中:

3.2.1. HttpServletBean

 在HttpServlet的繼承上增長了ConfigurableEnvironment屬性,用於存放Servlet的配置項。經過重寫init方法,在初始化時將servlet配置項添加到上下文環境變量中,並在該方法中開放了initBeanWrapper和initServletBean方法給子類。

3.2.2. FrameworkServlet

 基於Servlet實現的Web框架,每一個Servlet內部都對應一個XmlWebApplicationContext對象,且namespace格式爲ServletName-servlet。上面說了在沒顯示設定配置文件路徑的狀況下,且存在namespace時,會使用/WEB-INF/namespace.xml做爲Spring配置文件,對應到demo即爲/WEB-INF/dispatcher-servlet.xml。FrameworkServlet重寫了父類的intServletBean方法,對XmlWebApplicationContext的初始化工做。Servlet在初始化XmlWebApplicationContext時,會嘗試從ServletContext中獲取根Context(上面提到的,會將根Ccontext放到ServletContext中以標記已經初始化過)並設置爲當前Context的父Context,而後再按照雷士根Contextde 的初始化過程對其進行初始化。不一樣的是,會在refresh前開放口子進行擴展,包括:

  • 對內經過重寫子類的postProcessWebApplicationContext方法
  • 對外經過加載並執行globalInitializerClasses中配置的ApplicationContextInitializer類

FrameworkServlet還重寫了父類的各doXXX方法,都交給processService方法,以處理Http請求。processService最終委託給了doService方法。

3.2.3. DispatchdrServlet

 是SpringMVC處理Http請求的主要實現,主要完成了兩件事:

3.1. 重寫了onRefresh方法

 初始化時設置了衆多默認處理策略,包括:文件處理策略、HandlerMapping處理策略、HandlerAdapter處理策略、HandlerException處理策略、View解析策略等。SpringMVC在處理Http的每一個步驟上,都提供了相似filter的機制,每一個步驟都可以註冊多個策略處理器,按照順序選擇出可以處理當前請求的策略並交給其處理。而大部分的默認策略來至於spring-mvc模塊下的org/springframework/web/servlet/DispatcherServlet.properties文件,以下:

file

下面爲本人demo(SpringBoot)運行時DispatcherServlet各屬性以及註冊的各策略的狀況

file

主要關注handlerMappings中的RequestMappingHandlerMapping和handlerAdapters中的RequestMappingHandlerAdapter。這兩個都不是在DispatcherServlet.properties文件中指定的,而是在開啓<mvc:annotation-driven />後自動註冊的,這個後面會介紹。

3.1.1 RequestMappingHandlerMapping初始化

 RequestMappingHandlerMapping主要用於查找@RequestMapping註解的handler,其繼承關係以下:

file

  • AbstractHandlerMapping:實現了HandlerMapping接口,提供了獲取handler的主要實現。getHandler方法的實現爲,將具體handler的查找委託給了子類的getHandlerInternal方法,而後跟當前請求路徑相關的interceptor一塊兒包裝爲一個HandlerExecutionChain返回。interceptor爲全部實現了MappedInterceptor接口的bean,會在AbstractHandlerMapping初始化的時候遍歷上下文進行查找。
  • AbstractHandlerMethodMapping:在AbstractHandlerMapping的基礎上,主要提供了根據請求查找對應handler method的實現,即getHandlerInternal方法。該類會在初始化時遍歷上下文中全部的Bean,而後符合條件的Bean(經過isHandler方法),遍歷當前Bean符合條件的方法(經過getMappingForMethod方法),每一個方法都有一個對應的path,稱爲lookUpPath。getHandlerInternal實現上也是經過請求的HttpServletRequest獲得對應的lookUpPath,而後從內存緩存中獲取對應的handler。
  • RequestMappingHandlerMapping:@RequestMapping的實現,主要實現了 isHandler和getMappingForMethod。

    • isHandler:判斷是否出現@Controller註解或者@RequestMapping註解
    • getMappingForMethod:根據@RequestMapping註解返回RequestMappingInfo實例。

3.1.2 RequestMappingHandlerAdapter初始化

  RequestMappingHandlerAdapter主要完成HandlerMethod的執行,,其繼承關係以下:

file

  • AbstractHandlerMethodAdapter:用於判斷是否支持Handler的執行,須要傳入的handler是否爲HandlerMethod實例,同時將handler的執行委託給子類的handleInternal方法。
  • RequestMappingHandlerAdapter:真正執行handler對應的Method對象,會調用各類resolvers解析參數,用於在反射時做爲入參傳入;調用各類converter用於對結果進行加工等操做。

3.2. 重寫doService方法

 實現了Http請求的處理過程,具體流程以下圖,即開頭說起的SpringMVC處理Http請求的過程,前面已經介紹過流程,這裏再也不贅述。

file

3.3. mvc:annotation-driven

 按照以前說的,先看resource/META-INF/spring.handlers文件,這個配置在spring-webmvc模塊下,內容爲:

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

支持的標籤以下:

file

annotation-driven的解析類爲:AnnotationDrivenBeanDefinitionParser,該類主要自動作了以下動做:

  • 注入了RequestMappingHandlerMapping和BeanNameUrlHandlerMapping兩個HandlerMapping實現
  • 注入了RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter三個HandlerAdapter實現。須要指出的是對於RequestMappingHandlerAdapter,若是沒有配置message-converters標籤指定消息處理器的話,會根據classpath中存在的包自動注入處理器,包括:

    • ByteArrayHttpMessageConverter
    • StringHttpMessageConverter
    • ResourceHttpMessageConverter
    • SourceHttpMessageConverter
    • AllEncompassingFormHttpMessageConverter
    • 若是存在com.rometools.rome.feed.WireFeed類,則增長AtomFeedHttpMessageConverter、RssChannelHttpMessageConverter
    • 若是存在com.fasterxml.jackson.dataformat.xml.XmlMapper類,則增長MappingJackson2XmlHttpMessageConverter
    • 若是存在javax.xml.bind.Binder類,則增長Jaxb2RootElementHttpMessageConverter
    • 若是存在com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator,則增長MappingJackson2HttpMessageConverter
    • 若是存在com.google.gson.Gson,則增長GsonHttpMessageConverter
  • 注入了ExceptionHandlerExceptionResolver用於實現@ExceptionHandler註解、注入了ResponseStatusExceptionResolver用於實現@ResponseStatus和DefaultHandlerExceptionResolver
  • 注入了AntPathMatcher和UrlPathHelper用於路徑解析

 上面介紹了SpringMVC大致流程的實現,固然還有不少細節沒有進行說明,如@Param,HttpServletRequest等各類參數的解析和注入,響應結果轉爲json等各類結果的加工,詳細內容能夠根據上面介紹再進行深刻。

4. WebApplicationInitializer

 Servlet3.0+提供了ServletContainerInitializer接口,用於在web容器啓動時爲提供給第三方組件機會作一些初始化的工做,例如註冊servlet或者filtes等。

 每一個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄建立一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類。spring-web模塊下便存在該配置:

file

內容爲:

org.springframework.web.SpringServletContainerInitializer

SpringServletContainerInitializer的主要功能是加載classpath下的全部WebApplicationInitializer實現類(非接口、非抽象類),按照@Order進行排序後依次執行WebApplicationInitializer的onStartup方法。

 spring-web模塊提供的抽象類實現AbstractContextLoaderInitializer可以不用web.xml配置增長RootContext;提供的抽象類實現AbstractDispatcherServletInitializer可以不用web.xml配置增長DispatcherServlet。固然更重要的實現是SpringBoot中的實現,這個後續介紹SpringBoot時再提。

更多原創內容請搜索微信公衆號:啊駝(doubaotaizi)
相關文章
相關標籤/搜索