1. 程式人生 > 其它 >Spring原始碼之springMVC

Spring原始碼之springMVC

目錄

web.xml

它的作用是配置初始化資訊,如web頁面、servlet、servlet-mapping、filter、listener、啟動載入級別等。
SpringMVC 通過servlet攔截所有的URL來達到控制的目的,所以它必須要有web.xml

比較關鍵的配置是:

  • contextConfigLocation 配置spring配置檔案地址

  • DispatcherServlet 前端控制器

程式入口

ContextLoaderListener.initWebApplicationContext().createWebApplicationContext()

載入ApplicationContext 並注入Servlet容器
先判斷contextClass 屬性是否配置,否則載入預設的:XmlWebApplicationContext

protected Class<?> determineContextClass(ServletContext servletContext) {
   	String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   	if (contextClassName != null) {
   		try {
   			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
   		}
   		catch (ClassNotFoundException ex) {
   			throw new ApplicationContextException(
   					"Failed to load custom context class [" + contextClassName + "]", ex);
   		}
   	}
   	else {
   		// 預設值:org.springframework.web.context.support.XmlWebApplicationContext 可繼承它修改容器配置 
   		contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
   		try {
   			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
   		}
   		catch (ClassNotFoundException ex) {
   			throw new ApplicationContextException(
   					"Failed to load default context class [" + contextClassName + "]", ex);
   		}
   	}
   }

ContextLoader 中的static 靜態語句塊可以知道載入的配置檔案是: ContextLoader.properties

static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			// DEFAULT_STRATEGIES_PATH = ContextLoader.properties 
			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());
		}
	}

以上為容器載入階段,詳細細節之前章節已經講述,不再贅述
ContextLoader 的 initWebApplicationContext 方法中,發現如下程式碼:

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

servlet容器持有該引用

servlet

Servlet是按照servlet規範使用Java編寫的程式,基於HTTP協議執行在伺服器端,它的宣告週期分為:初始化、執行、銷燬。

初始化

  • servlet容器載入servlet類,把它的.class位元組碼檔案讀取到記憶體中
  • servlet容器建立一個ServletConfig物件,它包含該servlet的初始化配置資訊
  • servlet容器建立一個 servlet 物件
  • servlet容器呼叫servlet物件的init() 方法進行初始化

執行階段

  • servlet容器接收到請求時,會根據請求建立一個servletRequest(請求資訊) 物件和servletResponse(封裝返回資訊) 物件,
    呼叫service方法並處理請求,通過servletResponse相應請求後銷燬這兩個物件。

銷燬階段

  • Web應用終止,servlet容器呼叫servlet物件的destory方法,然後銷燬servlet物件以及相關的 servletConfig物件。

DispatcherServlet

它是SpringMVC的核心, 是servlet的一個實現類

初始化

在它的父類HttpServletBean中找到了init方法的呼叫
該方法只是初始的配置資訊載入

public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		// 解析 <init-param> 並驗證
		// requiredProperties 配置必須要的引數,否則丟擲異常 
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				// servlet 轉為 BeanWrapper 從而可以像spring那樣 對 init-param 的值進行注入
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);// 鉤子函式
				bw.setPropertyValues(pvs, true);// 註冊到自定義屬性編輯器
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();// 鉤子函式

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

繼續,在父類 FrameworkServlet 中找到了鉤子函式:initServletBean方法的具體實現:
有模板方法模式那味兒了,很遺憾這裡還是準備工作

@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// 對WebApplicationContext進一步初始化和補充
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

然後 initWebApplicationContext() 方法對容器進一步初始化和補充

protected WebApplicationContext initWebApplicationContext() {// 對WebApplicationContext進一步初始化和補充
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());// 從 容器 servletContext 中獲取 
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) { // webApplicationContext 是否在建構函式中被注入 (未解析過)  new DispatcherServlet()->.super(WebApplicationContext)
			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) {
			// WebApplicationContext 是否被 contextAttribute 屬性注入 
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// 既無構造器注入,也無contextAttribute屬性注入,那麼通過初始化的 WebApplicationContext 構造新的容器 
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);// 載入配置  鉤子,由子類 DispatcherServlet 實現,用於 Spring Web功能的 相關解析器的初始化 
			}
		}

		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

跟進 方法configureAndRefreshWebApplicationContext() 發現了我們的老朋友 refresh() 方法,是不是很眼熟?
ApplicationContext 容器載入過程中 它近乎是一切的起點了,檢視預設的容器類XmlWebApplicationContext 的類圖不難證實這點

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		...
		......
		.........
		wac.refresh();// ConfigurableApplicationContext.refresh() <-   AbstractApplicationContext.refresh() <- XmlWebApplicationContext 
	}

然後看initWebApplicationContext()方法內呼叫的,onRefresh()方法
FrameworkServlet 類中找到的onRefresh() 又是空方法,不解釋,鉤子函式它又來了,最後回到DispatcherServlet類,發現了該方法的具體定義:
該方法的主要功能是重新整理Spring在Web功能實現中所必須使用的全域性變數的初始化
從配置檔案:DispatcherServlet.properties 可得知部分全域性變數所使用的預設值


    @Override
	protected void onRefresh(ApplicationContext context) {// 用於 Spring Web功能的 相關解析器的初始化
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {// 各個全域性功能解析器初始化
		
		initMultipartResolver(context);// 處理檔案上傳
		
		initLocaleResolver(context);// 國際化配置? 基於:url  session  cookie 支援國際化
		
		initThemeResolver(context);// Theme主題控制網頁風格
		
		// 可以有多個HandleMapping,根據優先順序訪問,直到獲取到可用的Handle 為止 (Ordered 介面控制)
		initHandlerMappings(context);// 處理客戶端發起的Request請求,根據WebApplicationContext的配置來,回傳給 DispatcherServler 對應的Controller  
		
		// DispatcherServlet 通過處理器對映(HandleMapping)配置,得到處理器(Handle),之後會輪詢處理器(Handle)的<配適器模組> 
		// 並查詢能夠處理當前HTTP請求的處理器(Handle),的配適器實現(Adapter)
		// org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter  對Http請求處理器進行配適  OtherClass <- HttpAdapter <-  HttpHandle
		// org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter  將Http請求配飾到一個Controller 的實現進行處理
		// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter  用於執行Controller方法的配適器,對註解方式的支援 
		initHandlerAdapters(context);// 配適器
		
		initHandlerExceptionResolvers(context);// 異常處理
		
		initRequestToViewNameTranslator(context);// 當Controller沒有返回任何View物件或者邏輯檢視名稱,並在該方法中沒有向response的輸出流裡面寫任何資料,那麼spring會使用約定方式提供一個邏輯檢視名稱。
		
		// resolverViewName 方法 根據 viewName建立合適的View 實現 
		initViewResolvers(context);// Controller 計算結束後將結果封裝到ModleAndView,DispatcherServlet 會根據ModleAndView 選擇合適的檢視進行渲染
		
		initFlashMapManager(context);// SpringMVC Flash attributes 提供了屬性儲存功能,可夠重定向時其它請求使用 
	}

DispatcherServlet 的邏輯處理

看 HttpServlet 類的結構 看關鍵的doGet和doPost,在FrameworkServlet類中找到了如下方法實現:

    @Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}


	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}
	
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		// 主要目的:提取請求引數,用於重定向
    		long startTime = System.currentTimeMillis();
    		Throwable failureCause = null;
    
    		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 為了後續請求能使用提取屬性
    		LocaleContext localeContext = buildLocaleContext(request);
    
    		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 為了後續請求能使用提取屬性
    		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
    		// 初始化
    		initContextHolders(request, localeContext, requestAttributes);
    		try {
    			// 準備工作 具體實現由 DispatcherServlet 提供
    			doService(request, response);
    		}
    		catch (ServletException | IOException ex) {
    			failureCause = ex;
    			throw ex;
    		}
    		catch (Throwable ex) {
    			failureCause = ex;
    		}
    		finally {
    			resetContextHolders(request, previousLocaleContext, previousAttributes);// 設定提取的請求引數,用於重定向 
    			if (requestAttributes != null) {
    				requestAttributes.requestCompleted();
    			}
     			publishRequestHandledEvent(request, response, startTime, failureCause);// 事件通知 
    		}
    	}
	

忽略準備工作:doService().doDispatch(HttpServletRequest request, HttpServletResponse response)
如下為核心程式碼邏輯,之前提到的全部變數配置將登上舞臺了

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		// 當業務比較複雜時啟用另一個執行緒 
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);// 管理非同步請求處理的中心類,通常不由應用程式類直接使用。

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;
			try {
				// 檢查全域性變數
				// 如果請求型別為:multipartContent 將 HttpServletRequest 轉為 MultipartHttpServletRequest  (包裝器 ? 策略模式)
				processedRequest = checkMultipart(request); 
				multipartRequestParsed = (processedRequest != request);// 檢查原型別   包裝 /  代理 

				// Determine handler for the current request.
				// 根據URL 匹配 
				mappedHandler = getHandler(processedRequest);// 按優先順序從各個HandleMapping 中獲取Handle 
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 根據handle 獲取匹配的配適器 Adapter 
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {// last-modified(快取處理機制)  請求頭處理 <最後修改時間> 
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					// 未變化? 最後修改時間未變?  過濾重複請求???
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				// 呼叫攔截器的preHandle方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 啟用Handle 並返回檢視  (由配適器的 handle方法完成 )
				// 檢視配置檔案DispatcherServlet.properties 可以知道  HandlerAdapter ha 的具體實現類,跟蹤handle方法:
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);// 檢視名稱處理 
				// 呼叫所有攔截器的postHandle 方法,如果存在的話
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			// 完成處理後,啟用觸發器
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			// 完成處理後,啟用觸發器
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

檢視配置檔案 DispatcherServlet.properties 可以知道 HandlerAdapter ha 的具體實現類,跟蹤handle方法:
org.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.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

  1. getHandle()
    AbstractHandlerMapping.getHandel().[ AbstractUrlHandlerMapping.getHandlerInternal() ]
    根據請求url獲取handel
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 擷取 url
		Object handler = lookupHandler(lookupPath, request);// 根據 url 獲取 handle 
		if (handler == null) {// 獲取到的解析器為空 
			// We need to care for the default handler directly, since we need to
			// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
			Object rawHandler = null;
			if ("/".equals(lookupPath)) {// 請求路徑僅為根路徑: 則使用RootHandle處理
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();// 否則設定預設的 Handle
			}
			if (rawHandler != null) { // 預設 Handle 可能為空 
				// Bean name or resolved handler?
				if (rawHandler instanceof String) { // 若查詢的 Handle 型別為String 則為beanName 否則為 Handle 本身
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);// 從容器中獲取 
				}
				validateHandler(rawHandler, request);// 校驗鉤子函式 
				
				// 初始化 Handle ???  HandlerExecutionChain 對 Handle 進行包裝 
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		if (handler != null && logger.isDebugEnabled()) {
			logger.debug("Mapping [" + lookupPath + "] to " + handler);
		}
		else if (handler == null && logger.isTraceEnabled()) {
			logger.trace("No handler mapping found for [" + lookupPath + "]");
		}
		return handler;
	}

2.getHandlerAdapter
根據handel獲取配飾器Adapter
SimpleControllerHandlerAdapter.suport

public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

看到controller主要解析就完成了,剩下的事情就是處理請求,並繫結檢視放回,以及當發生異常時對異常檢視進行處理。