1. 程式人生 > >【Spring MVC】HandlerMapping初始化詳解(超詳細過程原始碼分析)

【Spring MVC】HandlerMapping初始化詳解(超詳細過程原始碼分析)

Spring MVC的Control主要由HandlerMapping和HandlerAdapter兩個元件提供。HandlerMapping負責對映使用者的URL和對應的處理類,HandlerMapping並沒有規定這個URL與應用的處理類如何對映,在HandlerMapping介面中只定義了根據一個URL必須返回一個由HandlerExecutionChain代表的處理鏈,我們可以在這個處理鏈中新增任意的HandlerAdapter例項來處理這個URL對應的請求。

HandlerMapping類相關的結構圖

HandlerMapping的初始化程式

Spring MVC提供了許多HandlerMapping的實現,預設使用的是BeanNameUrlHandlerMapping,可以根據Bean的name屬性對映到URL中。同時,我們可以為DispatcherServlet提供多個HandlerMapping供其使用。DispatcherServlet在選用HandlerMapping的過程中,將根據我們指定的一系列HandlerMapping的優先順序進行排序,然後優先使用優先順序高的HandlerMapping。
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}
預設情況下,Spring MVC會載入在當前系統中所有實現了HandlerMapping介面的bean,再進行按優先順序排序。如果只期望Spring MVC只加載指定的HandlerMapping,可以修改web.xml中的DispatcherServlet的初始化引數,將detectAllHandlerMappings的值設定為false。這樣,Spring MVC就只會查詢名為“handlerMapping”的bean,並作為當前系統的唯一的HandlerMapping。所以在DispatcherServlet的initHandlerMappings()方法中,優先判斷detectAllHandlerMappings的值,再進行下面內容。如果沒有定義HandlerMapping的話,Spring MVC就會按照DispatcherServlet.properties所定義的內容來載入預設的HandlerMapping。從配置檔案中確定了需要初始化的HandlerMapping,而HandlerMapping有很多的實現類,具體的某個實現類是怎麼初始化的呢?那麼接下來以SimpleUrlHandlerMapping為例,具體看看HandlerMapping的初始化過程。
通過類的結構圖,我們知道Spring MVC提供了一個HandlerMapping介面的抽象類AbstractHandlerMapping,而AbstractHandlerMapping同時還實現了Ordered介面並繼承了WebApplicationObjectSupport類。可以讓HandlerMapping通過設定setOrder()方法提高優先順序,並通過覆蓋initApplicationContext()方法實現初始化的一些工作。
public final void setApplicationContext(ApplicationContext context) throws BeansException {
		if (context == null && !isContextRequired()) {
			// Reset internal context state.
			this.applicationContext = null;
			this.messageSourceAccessor = null;
		}
		else if (this.applicationContext == null) {
			// Initialize with passed-in context.
			if (!requiredContextClass().isInstance(context)) {
				throw new ApplicationContextException(
						"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
			}
			this.applicationContext = context;
			this.messageSourceAccessor = new MessageSourceAccessor(context);
			initApplicationContext(context);
		}
		else {
			// Ignore reinitialization if same context passed in.
			if (this.applicationContext != context) {
				throw new ApplicationContextException(
						"Cannot reinitialize with different application context: current one is [" +
						this.applicationContext + "], passed-in one is [" + context + "]");
			}
		}
	}
ApplicationObjectSupport首先呼叫setApplicationContext()方法,其中的initApplicationContext()方法由子類覆蓋和實現。
	@Override
	public void initApplicationContext() throws BeansException {
		super.initApplicationContext();
		registerHandlers(this.urlMap);
	}
在子類SimpleUrlHandlerMapping中的initApplicationContext()方法中,先初始化Spring MVC容器,然後再對Handler進行註冊。
	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.mappedInterceptors);
		initInterceptors();
	}
在SimpleUrlHandlerMapping的父類AbstractHandlerMapping中,detectMappingInterceptors()方法探測ApplicationContext中已經解析過的MappedInterceptor,initInterceptors()方法來初始化攔截器。
	protected void initInterceptors() {
		if (!this.interceptors.isEmpty()) {
			for (int i = 0; i < this.interceptors.size(); i++) {
				Object interceptor = this.interceptors.get(i);
				if (interceptor == null) {
					throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
				}
				if (interceptor instanceof MappedInterceptor) {
					mappedInterceptors.add((MappedInterceptor) interceptor);
				}
				else {
					adaptedInterceptors.add(adaptInterceptor(interceptor));
				}
			}
		}
	}
呼叫initInterceptors()方法將SimpleUrlHandlerMapping中定義的interceptors包裝成HandlerInterceptor物件儲存在adaptedInterceptors陣列中。
	protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
		}
		else {
			for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
				String url = entry.getKey();
				Object handler = entry.getValue();
				// Prepend with slash if not already present.
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String) {
					handler = ((String) handler).trim();
				}
				registerHandler(url, handler);
			}
		}
	}
再回到initApplicationContext()方法中呼叫的registerHandlers()方法,這邊主要是主要是對urlMap中的key值進行了一些處理,要是沒有“/”的就加上"/",去掉空格等處理。這裡的urlMap就是在配置檔案中SimpleUrlHandlerMapping的通過mappings屬性注入的的內容。key是url的某個欄位,value是bean的id。這個方法中的重點是呼叫了registerHandler(url, handler)這個方法,在這個方法是它的父類AbstractUrlHandlerMapping中的方法。
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			if (getApplicationContext().isSingleton(handlerName)) {
				resolvedHandler = getApplicationContext().getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isInfoEnabled()) {
					logger.info("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isInfoEnabled()) {
					logger.info("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isInfoEnabled()) {
					logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}
從這邊就可以清楚的看到,這裡根據SimpleUrlHandlerMapping中的urlMap中的value值通過getBean()方法得到bean物件(通過id查詢)。同時將url的某個欄位作為key值,bean作為value重新存入到AbstractUrlHandlerMapping的urlMap屬性中去,這樣就達到url的某個欄位對應到具體的controller了的目的,當遇到有請求訪問伺服器的時候,就可以根據url找到具體的controller去執行這個請求了。也就是說,HandlerMapping的初始化過程主要分成兩部分,通過initInterceptors()方法將SimpleUrlHandlerMapping中定義的interceptors包裝成HandlerInterceptor物件儲存在adaptedInterceptors陣列中,同時通過registerHandlers()方法將SimpleHandlerMapping中定義的mappings(即URL與Handler的對映)註冊到handlerMap集合中。

具體的時序圖