SpringMVC源碼解析之RequestMappingHandlerMapping:初始化

SpringMVC源碼解析之Servlet
SpringMVC源碼解析之GenericServlet和HttpServlet
SpringMVC源碼解析之DispatcherServlet:生命週期init()和destroy()
SpringMVC源碼解析之DispatcherServlet:請求處理

一、HandlerMapping

1. 簡介

前面的博客已經提到,HandlerMapping是SpringMVC的策略組件之一,稱作處理器映射器,其作用是根據請求獲取到對應的處理器。

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

HandlerMapping接口只定義了一個方法,即根據請求獲取到對應的處理器。

2. HandlerMapping的類繼承關係

HandlerMapping類圖
SpringMVC提供了不少的多層次的HandlerMapping的實現類,RequestMappingHandlerMapping根據註解RequestMapping進行處理器和請求的匹配,是我們日常開發中最常使用的方式,本文也只關注HandlerMapping到RequestMappingHandlerMapping的鏈路上的接口和類的實現。

3. RequestMappingHandlerMapping類圖

RequestMappingHandlerMapping類圖

二、initApplicationContext方法的解析

AbstractHandlerMapping繼承了WebApplicationObjectSupport接口,通過initApplicationContext方法進行上下文的初始化。

1. 介紹

protected void initApplicationContext() throws BeansException {
	//空的模板方法,可以對interceptors進行管理和操作
	extendInterceptors(this.interceptors);
	//偵測容器中的MappedInterceptor
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}

initApplicationContext方法子這裏的主要作用是初始化攔截器,在AbstractHandlerMapping中有兩個與攔截器相關的屬性,分別是:
interceptors:
SpringMVC中配置的攔截器集合,可以在註冊HandlerMapping時通過屬性進行設置,也可以在子類中通過重寫模板方法extendInterceptors對interceptors進行
adaptedInterceptors:
(1)在detectMappedInterceptors方法中將Spring容器中的MappedInterceptor類型的bean對象加入搭配adaptedInterceptors
(2)在initInterceptors方法中將interceptors中的對象加入到adaptedInterceptors。(對於HandlerInterceptor類型的Interceptor直接加入,WebRequestInterceptor類型的封裝成WebaRequestHandlerInterceptorAdapter加入,其它類型拋出異常)
adaptedInterceptors中的Interceptor是HandlerInterceptor類型,其子類MappedInterceptor類型的攔截器只對匹配的url的請求有效,其它類型的對所有請求有效。在SpringMVC4中,將MappedInterceptor保存在mappedInterceptors中,其它類型的保存在adaptedInterceptors中,SpringMVC5中還依舊保留着getMappedInterceptors等一些相關的方法。
initApplicationContext方法的調用鏈如下圖所示

2.AbstractHandlerMapping#extendInterceptors(List<Object>)

protected void extendInterceptors(List<Object> interceptors) {
}

該方法是一個空的模板方法,子類可以重寫該方法以實現對interceptors屬性的修改(包括添加和刪除)。
在SpringMVC中,沒有子類對該方法進行了重寫。

3.AbstractHandlerMapping#detectMappedInterceptors(List<HandlerInterceptor>)

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(
			BeanFactoryUtils.beansOfTypeIncludingAncestors(
					obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}

該方法用於檢測Spring容器中的MappedInterceptor類型的bean並加入到集合中。

4. AbstractHandlerMapping#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");
			}
			this.adaptedInterceptors.add(adaptInterceptor(interceptor));
		}
	}
}

將interceptors中的對象加入中adaptedInterceptors中。

5. AbstractHandlerMapping#adaptInterceptor(Object)

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
	//HandlerInterceptor類型不處理
	if (interceptor instanceof HandlerInterceptor) {
		return (HandlerInterceptor) interceptor;
	}
	//WebRequestInterceptor類型封裝成適配器WebRequestHandlerInterceptorAdapater
	else if (interceptor instanceof WebRequestInterceptor) {
		return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
	}
	//其它類型拋出異常
	else {
		throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
	}
}

該方法將對象封裝成HandlerInterceptor類型。

三、afterProperties的解析

AbstractHandlerMethodMapping實現了InitializaingBean接口,通過afterProperties方法進行初始化。
AbstractHandlerMethodMapping是一個泛型抽象類,從類名上可以看出其是對方法類型的處理器的抽象基本,泛型T可以簡單理解成匹配條件的封裝類,用來表示處理器使用的請求類型。

1. AbstractHandlerMethodMapping

/**
 * Detects handler methods at initialization.
 */
//初始化,檢測可以作爲處理器的方法
@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}

1.1 initHandlerMethod

//掃描容器裏的beans,檢測並註冊處理器方法
protected void initHandlerMethods() {
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for request mappings in application context: " + getApplicationContext());
	}
	//獲取容器內的bean集合,detectHandlerMethodsInAncestorContexts是否需要檢查祖先容器,默認false
	String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
			obtainApplicationContext().getBeanNamesForType(Object.class));

	for (String beanName : beanNames) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			Class<?> beanType = null;
			try {
				beanType = obtainApplicationContext().getType(beanName);
			}
			catch (Throwable ex) {
				// An unresolvable bean type, probably from a lazy bean - let's ignore it.
				if (logger.isDebugEnabled()) {
					logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
				}
			}
			//抽象方法isHandler方法判斷是否匹配
			if (beanType != null && isHandler(beanType)) {
				//從bean中檢測處理器方法
				detectHandlerMethods(beanName);
			}
		}
	}
	//空的模板方法,進行HandlerMethod的初始化
	handlerMethodsInitialized(getHandlerMethods());
}

該方法用於檢測獲取容器中的處理器並進行初始化。
(1)獲取容器中的bean集合
(2)遍歷bean集合,判斷bean的類型是否匹配。如果匹配,從bean中檢測匹配的方法作爲處理器
(3)對處理器進行初始化

1.2 detectHandlerMethods

//找到handler中的方法作爲處理器
protected void detectHandlerMethods(final Object handler) {
	//確定類型
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		//返回user-defined的類型,主要是從代理類型到實際類型
		final Class<?> userType = ClassUtils.getUserClass(handlerType);
		//找到類中方法和匹配條件的映射關係
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						//抽象方法getMappingForMethod
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isDebugEnabled()) {
			logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
		}
		for (Map.Entry<Method, T> entry : methods.entrySet()) {
			//獲取在AOP中對應的方法,方法本身或對應的在接口上的或代理類上的方法
			Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
			T mapping = entry.getValue();
			//註冊HandlerMethod
			registerHandlerMethod(handler, invocableMethod, mapping);
		}
	}
}

detectHandlerMethods的作用是找到handler對象中的方法作爲處理器。
(1)找到類中可以作爲處理器的方法並獲取匹配條件T組成映射集Map<Method, T>
(2)遍歷映射集,將方法和匹配條件T進行註冊。

1.3 registerHandlerMethod

//對於HanlderMethod的管理由MappingRegistry負責
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

1.4 MappingRegistry

MappingRegistry是AbstractHandlerMapping的內部類,負責對匹配條件集T進行註冊及管理,內部維護着一系列的映射集map和一個讀寫鎖。

//匹配條件T和MappingRegistration的映射關係
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

//匹配條件T與執行器HanlderMethod的映射關係
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

//url與匹配條件集合的映射關係,一個url可以對應多個匹配條件
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

//name與執行器集合的映射關係,一個name可以對應多個執行器,name由HandlerMethodMappingNamingStrategy確定,一般由類名和方法名組成
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

//執行器HandlerMethod與跨域配置CorsConfiguration的映射關係
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

//讀寫鎖
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

對於Registration,同樣也是AbstractHandlerMethodMapping的一個內部類,由匹配條件T及名稱,處理器和匹配的url而成:

//匹配條件
private final T mapping;

//處理器
private final HandlerMethod handlerMethod;

//url集合
private final List<String> directUrls;

//名稱
@Nullable
private final String mappingName;

MappingRegistry的核心方法就是註冊方法regsiter。

//MappingRegistry#registry
public void register(T mapping, Object handler, Method method) {
	//讀寫鎖加寫鎖
	this.readWriteLock.writeLock().lock();
	try {
		//新建HandlerMethod對象
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		//校驗唯一性,一個mapping最多隻能對應一個HandlerMethod
		assertUniqueMethodMapping(handlerMethod, mapping);

		if (logger.isInfoEnabled()) {
			logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
		}
		//加入mappingLookup
		this.mappingLookup.put(mapping, handlerMethod);

		//加入urlLookup
		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			this.urlLookup.add(url, mapping);
		}

		//加入nameLookup
		String name = null;
		if (getNamingStrategy() != null) {
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}

		//加入corsLookup
		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			this.corsLookup.put(handlerMethod, corsConfig);
		}

		//加入registry
		this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}

對於匹配名稱,根據命名策略HandlerMethodMappingNamingStrategy<T>類型的屬性namingStrategy獲得。
對於url集,在getDirectUrls方法通過調用抽象方法getMappingPathPatterns獲得。

private List<String> getDirectUrls(T mapping) {
	List<String> urls = new ArrayList<>(1);
	for (String path : getMappingPathPatterns(mapping)) {
		if (!getPathMatcher().isPattern(path)) {
			urls.add(path);
		}
	}
	return urls;
}

2. RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping是AbstractHandlerMethodMapping<RequestMappingInfo>的子類,是所有匹配條件T爲RequestMappingInfo類型的HandlerMapping的抽象基類。

2.1 namingStrategy

protected RequestMappingInfoHandlerMapping() {
	setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}

可以看到,默認的命名策略是RequestMappingInfoHandlerMethodMappingNamingStrategy,其命名邏輯如下。

//RequestMappingInfoHandlerMethodMappingNamingStrategy#getName
@Override
public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
	//如果已經有name,直接使用
	if (mapping.getName() != null) {
		return mapping.getName();
	}
	StringBuilder sb = new StringBuilder();
	String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
	for (int i = 0 ; i < simpleTypeName.length(); i++) {
		if (Character.isUpperCase(simpleTypeName.charAt(i))) {
			sb.append(simpleTypeName.charAt(i));
		}
	}
	sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
	return sb.toString();
}

首選使用匹配條件的名稱,否則命名方式爲類名中的大寫字母 + # + 方法名。

2.2 getMatchingMapping

protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
	return info.getMatchingCondition(request);
}

匹配的url通過匹配通過RequestMappingInfo獲得。

3. RequestMappingInfo

RequestMappingInfo是RequestMappingInfoHandlerMapping及其子類RequestMappingHandlerMapping的泛型類,用來匹配http請求。

//省略方法
public final class RequestMappingInfo implements RequestCondition\<RequestMappingInfo\> {

	@Nullable
	//名稱
	private final String name;

	//請求pattern,用來匹配url
	private final PatternsRequestCondition patternsCondition;

	//請求方法,如GET|POST等等
	private final RequestMethodsRequestCondition methodsCondition;
	
	//請求參數,必須包含特定參數才能匹配
	private final ParamsRequestCondition paramsCondition;
	
	//請求頭,必須包含特定值才能匹配
	private final HeadersRequestCondition headersCondition;
	
	//consumes,請求的content-type
	private final ConsumesRequestCondition consumesCondition;

	//produces,響應的content-type
	private final ProducesRequestCondition producesCondition;
	
	//自定義的條件
	private final RequestConditionHolder customConditionHolder;
}

一般在日常開發中,RequestMappingInfo與註解RequestMapping中的屬性一一對應,具體如RequestMappingHandlerMapping#createRequestMappingInfo(RequestMapping, RequestCondition<?>)所示

protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

	RequestMappingInfo.Builder builder = RequestMappingInfo
			.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
			.methods(requestMapping.method())
			.params(requestMapping.params())
			.headers(requestMapping.headers())
			.consumes(requestMapping.consumes())
			.produces(requestMapping.produces())
			.mappingName(requestMapping.name());
	if (customCondition != null) {
		builder.customCondition(customCondition);
	}
	return builder.options(this.config).build();
}

4. RequestMappingHandlerMapping

RequestMappingHandlerMapping是RequestMappingInfoHandlerMapping的子類,同樣的將RequestMappingInfo作爲泛型類,並且RequestMappingInfo對象基於註解RequestMapping生成。

1. afterProperties

public void afterPropertiesSet() {
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	super.afterPropertiesSet();
}

RequestMappingHandlerMapping重寫了afterProperties方法
(1)config屬性的初始化
config屬性用於基於RequestMapping註解生成RequestMappingInfo
(2)調用父類的afterProperties方法
即AbstractHandlerMethodMapping類的afterProperties方法

2. getMappingForMethod

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	//基於方法生成匹配條件RequestMappingInfo
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		//基於類生成匹配條件RequestMappingInfo
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			//方法和類上的條件組合
			info = typeInfo.combine(info);
		}
	}
	return info;
}

根據方法和類調用createRequestMappingInfo方法生成RequestMappingInfo對象並組合返回。
對於兩個RequestMappingInfo對象的組合,不同的屬性的組合方式不同,可能是覆蓋(如consumes, produces)、並集(如params, headers, methods)、字符串相加(如pattern),當然也可以自定義實現組合邏輯。

3. createRequestMappingInfo

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	//獲取註解
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	//獲取condition,默認返回null
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));、
	//根據註解生成RequestMappingInfo對象
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

4. 方法調用鏈

afterProperties方法調用鏈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章