這節介紹SpringMVC,SpringMVC是一種基於Java的實現MVC設計模式的請求驅動類型的輕量級Web框架。本章會介紹相關概念,流程,再從源碼進行講解。前端
MVC(Model View Controller)是一種軟件設計的框架模式,它採用模型(Model)-視圖(View)-控制器(controller)的方法把業務邏輯、數據與界面顯示分離。MVC框架模式是一種複合模式,MVC的三個核心部件分別是java
基本上你們都會在網上看到這張圖:web
這個圖描述了SpringMVC處理一個Http請求的基本流程,對應的流程爲:spring
還有你們都會接觸到的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
SpringMVC的加載是依賴Servlet切入的,主要依賴兩個技術點:Listener和Servlet。緩存
從web.xml中能夠知道,ContextLoaderListener依賴於Servlet的Listener技術。Listener是在servlet2.3中加入的,主要用於對session、request、context等進行監控。使用Listener須要實現相應的接口。觸發Listener事件的時候,tomcat會自動調用相應的Listener的方法。經常使用的監聽接口包括:
這裏主要使用了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。
contextInitialized方法的主要內容以下:
過程爲:
(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繼承簡圖以下:
層級比較明顯,自己也是一個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的初始化步驟爲:
(3)標記已經初始化
經過將該根Context存在ServletContext中,並設置值爲org.springframework.web.context.ROOT,用於第(1)步的判斷
銷燬過程比較簡單,首先調用WebApplicationContext的close方法銷燬該Context,而後移除ServletContex中的org.springframework.web.context.ROOT屬性值,最後清除ServletContext中全部org.springframework.開頭的屬性值。
同ContextLoaderListener相似,DispatcherServlet依賴於Servlet進行擴展。DispatcherServlet的結構以下:
如上,DispatcherServlet繼承自HttpServlet,並重寫了doService方法,用於處理http請求,其中:
在HttpServlet的繼承上增長了ConfigurableEnvironment屬性,用於存放Servlet的配置項。經過重寫init方法,在初始化時將servlet配置項添加到上下文環境變量中,並在該方法中開放了initBeanWrapper和initServletBean方法給子類。
基於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前開放口子進行擴展,包括:
FrameworkServlet還重寫了父類的各doXXX方法,都交給processService方法,以處理Http請求。processService最終委託給了doService方法。
是SpringMVC處理Http請求的主要實現,主要完成了兩件事:
3.1. 重寫了onRefresh方法
初始化時設置了衆多默認處理策略,包括:文件處理策略、HandlerMapping處理策略、HandlerAdapter處理策略、HandlerException處理策略、View解析策略等。SpringMVC在處理Http的每一個步驟上,都提供了相似filter的機制,每一個步驟都可以註冊多個策略處理器,按照順序選擇出可以處理當前請求的策略並交給其處理。而大部分的默認策略來至於spring-mvc模塊下的org/springframework/web/servlet/DispatcherServlet.properties文件,以下:
下面爲本人demo(SpringBoot)運行時DispatcherServlet各屬性以及註冊的各策略的狀況
主要關注handlerMappings中的RequestMappingHandlerMapping和handlerAdapters中的RequestMappingHandlerAdapter。這兩個都不是在DispatcherServlet.properties文件中指定的,而是在開啓<mvc:annotation-driven />後自動註冊的,這個後面會介紹。
3.1.1 RequestMappingHandlerMapping初始化
RequestMappingHandlerMapping主要用於查找@RequestMapping註解的handler,其繼承關係以下:
RequestMappingHandlerMapping:@RequestMapping的實現,主要實現了 isHandler和getMappingForMethod。
3.1.2 RequestMappingHandlerAdapter初始化
RequestMappingHandlerAdapter主要完成HandlerMethod的執行,,其繼承關係以下:
3.2. 重寫doService方法
實現了Http請求的處理過程,具體流程以下圖,即開頭說起的SpringMVC處理Http請求的過程,前面已經介紹過流程,這裏再也不贅述。
按照以前說的,先看resource/META-INF/spring.handlers文件,這個配置在spring-webmvc模塊下,內容爲:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
支持的標籤以下:
annotation-driven的解析類爲:AnnotationDrivenBeanDefinitionParser,該類主要自動作了以下動做:
注入了RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter三個HandlerAdapter實現。須要指出的是對於RequestMappingHandlerAdapter,若是沒有配置message-converters標籤指定消息處理器的話,會根據classpath中存在的包自動注入處理器,包括:
上面介紹了SpringMVC大致流程的實現,固然還有不少細節沒有進行說明,如@Param,HttpServletRequest等各類參數的解析和注入,響應結果轉爲json等各類結果的加工,詳細內容能夠根據上面介紹再進行深刻。
Servlet3.0+提供了ServletContainerInitializer接口,用於在web容器啓動時爲提供給第三方組件機會作一些初始化的工做,例如註冊servlet或者filtes等。
每一個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄建立一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類。spring-web模塊下便存在該配置:
內容爲:
org.springframework.web.SpringServletContainerInitializer
SpringServletContainerInitializer的主要功能是加載classpath下的全部WebApplicationInitializer實現類(非接口、非抽象類),按照@Order進行排序後依次執行WebApplicationInitializer的onStartup方法。
spring-web模塊提供的抽象類實現AbstractContextLoaderInitializer可以不用web.xml配置增長RootContext;提供的抽象類實現AbstractDispatcherServletInitializer可以不用web.xml配置增長DispatcherServlet。固然更重要的實現是SpringBoot中的實現,這個後續介紹SpringBoot時再提。
更多原創內容請搜索微信公衆號:啊駝(doubaotaizi)