這節介紹SpringMVC,SpringMVC是一種基於Java的實現MVC設計模式的請求驅動型別的輕量級Web框架。本章會介紹相關概念,流程,再從原始碼進行講解。
1. MVCMVC(Model View Controller)是一種軟體設計的框架模式,它採用模型(Model)-檢視(View)-控制器(controller)的方法把業務邏輯、資料與介面顯示分離。MVC框架模式是一種複合模式,MVC的三個核心部件分別是
Model(模型):所有的使用者資料、狀態以及程式邏輯,獨立於檢視和控制器View(檢視):呈現模型,類似於Web程式中的介面,檢視會從模型中拿到需要展現的狀態以及資料,對於相同的資料可以有多種不同的顯示形式(檢視)Controller(控制器):負責獲取使用者的輸入資訊,進行解析並反饋給模型,通常情況下一個檢視具有一個控制器2. SpringMVC流程基本上大家都會在網上看到這張圖:
這個圖描述了SpringMVC處理一個Http請求的基本流程,對應的流程為:
使用者傳送請求至前端控制器DispatcherServlet。DispatcherServlet收到請求呼叫HandlerMapping處理器對映器。處理器對映器找到具體的處理器(可以根據xml配置、註解進行查詢),生成處理器物件及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。DispatcherServlet呼叫HandlerAdapter處理器介面卡。HandlerAdapter經過適配呼叫具體的處理器(Controller,也叫後端控制器)。Controller執行完成返回ModelAndView。HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。DispatcherServlet將ModelAndView傳給ViewReslover檢視解析器。ViewReslover解析後返回具體View.DispatcherServlet根據View進行渲染檢視(即將模型資料填充至檢視中)。DispatcherServlet響應使用者。還有大家都會接觸到的demo:
web.xml
<?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;@RestControllerpublic class HelloController { @RequestMapping("/hello") public String excute() { return "hello"; }}
上面表示的意思為:
開啟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. contextInitializedcontextInitialized方法的主要內容如下:
過程為:
(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方法
@Overrideprotected 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); } }} @Overrideprotected 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配置,使用指定的配置檔案,若沒有則使用上面提到的預設配置檔案DEFAULTCONFIGLOCATION載入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的結構如下:
如上,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檔案,如下:
下面為本人demo(SpringBoot)執行時DispatcherServlet各屬性以及註冊的各策略的情況
主要關注handlerMappings中的RequestMappingHandlerMapping和handlerAdapters中的RequestMappingHandlerAdapter。這兩個都不是在DispatcherServlet.properties檔案中指定的,而是在開啟 後自動註冊的,這個後面會介紹。
3.1.1 RequestMappingHandlerMapping初始化
RequestMappingHandlerMapping主要用於查詢@RequestMapping註解的handler,其繼承關係如下:
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的執行,,其繼承關係如下:
AbstractHandlerMethodAdapter:用於判斷是否支援Handler的執行,需要傳入的handler是否為HandlerMethod例項,同時將handler的執行委託給子類的handleInternal方法。RequestMappingHandlerAdapter:真正執行handler對應的Method物件,會呼叫各種resolvers解析引數,用於在反射時作為入參傳入;呼叫各種converter用於對結果進行加工等操作。
3.2. 重寫doService方法
實現了Http請求的處理過程,具體流程如下圖,即開頭提及的SpringMVC處理Http請求的過程,前面已經介紹過流程,這裡不再贅述。
3.3. mvc:annotation-driven按照之前說的,先看resource/META-INF/spring.handlers檔案,這個配置在spring-webmvc模組下,內容為:
http\\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler支援的標籤如下:
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. WebApplicationInitializerServlet3.0+提供了ServletContainerInitializer介面,用於在web容器啟動時為提供給第三方元件機會做一些初始化的工作,例如註冊servlet或者filtes等。
每個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄建立一個名為javax.servlet.ServletContainerInitializer的檔案,檔案內容指定具體的ServletContainerInitializer實現類。spring-web模組下便存在該配置:
內容為:
org.springframework.web.SpringServletContainerInitializerSpringServletContainerInitializer的主要功能是載入classpath下的所有WebApplicationInitializer實現類(非介面、非抽象類),按照@Order進行排序後依次執行WebApplicationInitializer的onStartup方法。
spring-web模組提供的抽象類實現AbstractContextLoaderInitializer能夠不用web.xml配置增加RootContext;提供的抽象類實現AbstractDispatcherServletInitializer能夠不用web.xml配置增加DispatcherServlet。當然更重要的實現是SpringBoot中的實現,這個後續介紹SpringBoot時再提。