SpringMVC加載流程

NO IMAGE

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

1. MVC

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

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

2. SpringMVC流程

 基本上大家都會在網上看到這張圖:

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;
@RestController
public 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. contextInitialized

 contextInitialized方法的主要內容如下:

SpringMVC加載流程

過程為:

(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繼承簡圖如下:

SpringMVC加載流程

層級比較明顯,本身也是一個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配置,使用指定的配置文件,若沒有則使用上面提到的默認配置文件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的結構如下:

SpringMVC加載流程

如上,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文件,如下:

SpringMVC加載流程

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

SpringMVC加載流程

主要關注handlerMappings中的RequestMappingHandlerMapping和handlerAdapters中的RequestMappingHandlerAdapter。這兩個都不是在DispatcherServlet.properties文件中指定的,而是在開啟後自動註冊的,這個後面會介紹。

3.1.1 RequestMappingHandlerMapping初始化

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

SpringMVC加載流程

  • 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的執行,,其繼承關係如下:

SpringMVC加載流程

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

3.2. 重寫doService方法

 實現了Http請求的處理過程,具體流程如下圖,即開頭提及的SpringMVC處理Http請求的過程,前面已經介紹過流程,這裡不再贅述。

SpringMVC加載流程

3.3. mvc:annotation-driven

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

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

支持的標籤如下:

SpringMVC加載流程

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模塊下便存在該配置:

SpringMVC加載流程

內容為:

org.springframework.web.SpringServletContainerInitializer

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

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

更多原創內容請搜索微信公眾號:啊駝(doubaotaizi)

相關文章

Elasticsearch調優篇慢查詢分析筆記

FishRedux完成一個玩安卓客戶端

淺談MySQL的事務與ACID

HashMap原理技術知識整理