欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringMVC處理器映射器HandlerMapping詳解

 更新時間:2023年09月09日 09:30:28   作者:這是一條海魚  
這篇文章主要介紹了SpringMVC處理器映射器HandlerMapping詳解,在SpringMVC中會有很多請求,每個請求都需要一個HandlerAdapter處理,具體接收到一個請求之后使用哪個HandlerAdapter進(jìn)行處理呢,他們的過程是什么,需要的朋友可以參考下

前言

在SpringMVC中會有很多請求,每個請求都需要一個HandlerAdapter處理,具體接收到一個請求之后使用哪個HandlerAdapter進(jìn)行處理呢,他們的過程是什么。本文將對此問題進(jìn)行討論

DispatcherServlet在初始化中,會調(diào)用其initHandlerMappings方法注冊HandlerMapping對象并放到其緩存池中,其過程如下:先查詢?nèi)萜髦惺欠裼刑幚砥饔成淦?,如果有就注冊到其緩存池中,如果沒有就安裝默認(rèn)到規(guī)則創(chuàng)建處理器映射器,并注冊到其緩存池中。

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		//detectAllHandlerMappings默認(rèn)為true
		//true標(biāo)志檢測所有handlerMapping,false只獲取“handlerMapping”bean。
		if (this.detectAllHandlerMappings) {
			// 在ApplicationContext中查找所有HandlerMappings,包括祖先上下文。
			Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				//排序
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
			   //只獲取“handlerMapping”bean
				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.
			}
		}
		 //通過注冊,確保我們至少有一個HandlerMapping
         //如果找不到其他映射,則為默認(rèn)HandlerMapping。
		if (this.handlerMappings == null) {
		    //從spring-webmvc下的DispatcherServlet.properties讀取默認(rèn)配置
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

Spring默認(rèn)HandlerMapping有BeanNameUrlHandlerMapping,RequestMappingHandlerMapping,RouterFunctionMapping

在這里插入圖片描述

一、處理器映射器架構(gòu)

處理器映射器使用了策略模式

1、策略接口

HandlerMapping用來查找Handler的。在SpringMVC中會有很多請求,每個請求都需要一個Handler處理,具體接收到一個請求之后使用哪個Handler進(jìn)行處理呢?這就是HandlerMapping需要做的事

HandlerMapping:負(fù)責(zé)映射用戶的URL和對應(yīng)的處理類Handler,HandlerMapping并沒有規(guī)定這個URL與應(yīng)用的處理類如何映射。所以在HandlerMapping接口中僅僅定義了根據(jù)一個URL必須返回一個由HandlerExecutionChain代表的處理鏈,我們可以在這個處理鏈中添加任意的HandlerAdapter實例來處理這個URL對應(yīng)的請求(這樣保證了最大的靈活性映射關(guān)系)。

public interface HandlerMapping {
   ...//忽略一些常量
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

2、請求鏈

public class HandlerExecutionChain {
    //處理器
	private final Object handler;
	//攔截器
	private HandlerInterceptor[] interceptors;
	//攔截器
	private List<HandlerInterceptor> interceptorList;
	//忽略代碼....
}

3、模版類

處理器映射器都是實現(xiàn)AbstractHandlerMapping,該抽象類完成了所有的Handler以及handler里面所有的HandlerMethod的模版操作,但是怎么獲取Handler,這些邏輯都是交給子類自己去實現(xiàn),所以這層抽象可謂也是非常的靈活,并沒有把Handler的實現(xiàn)方式定死。

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {
	//默認(rèn)的Handler,這邊使用的Obejct,子類實現(xiàn)的時候,使用HandlerMethod,HandlerExecutionChain等
	@Nullable
	private Object defaultHandler;
	// url路徑計算的輔助類、工具類
	private UrlPathHelper urlPathHelper = new UrlPathHelper();
	// Ant風(fēng)格的Path匹配模式~  解決如/books/{id}場景
	private PathMatcher pathMatcher = new AntPathMatcher();
	// 保存著攔截器們~~~
	private final List<Object> interceptors = new ArrayList<>();
	// 從interceptors中解析得到,直接添加給全部handler
	private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
	// 跨域相關(guān)的配置~
	private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
	private CorsProcessor corsProcessor = new DefaultCorsProcessor();
	// 最低的順序(default: same as non-Ordered)
	private int order = Ordered.LOWEST_PRECEDENCE;
	@Nullable
	private String beanName;
	/**
	 * Initializes the interceptors.
	 * @see #extendInterceptors(java.util.List)
	 * @see #initInterceptors()
	 */
	@Override
	protected void initApplicationContext() throws BeansException {
		// 給子類擴(kuò)展:增加攔截器,默認(rèn)為空實現(xiàn).RequestMappingHandlerMapping也沒有重寫這個方法
		extendInterceptors(this.interceptors);
		// 找到所有MappedInterceptor(截器是)類型的bean添加到adaptedInterceptors中
		detectMappedInterceptors(this.adaptedInterceptors);
		// 將interceptors中的攔截器取出放入adaptedInterceptors
		// 如果是WebRequestInterceptor類型的攔截器  需要用WebRequestHandlerInterceptorAdapter進(jìn)行包裝適配
		initInterceptors();
	}
	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	    //根據(jù)請求獲取對應(yīng)的處理器,子類實現(xiàn)
		Object handler = getHandlerInternal(request);
		if (handler == null) {
		    //如果獲取不到,到默認(rèn)到處理器中
			handler = getDefaultHandler();
		}
		//如果還沒有處理器,返回null
		if (handler == null) {
			return null;
		}
		// 意思是如果當(dāng)前傳入的handler是個String類型,那就根據(jù)其名字去容器內(nèi)找這個Bean,當(dāng)作一個Handler~
		if (handler instanceof String) {
			String handlerName = (String) handler;
			//到容器中找
			handler = obtainApplicationContext().getBean(handlerName);
		}
          //根據(jù)handler和request構(gòu)造一個請求處理鏈~~
		  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
       // 4.2版本提供了對CORS跨域資源共享的支持  此處暫時略過~
		if (hasCorsConfigurationSource(handler)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}
	@Nullable
	protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
}

接下來最重要的就是以getHandlerInternal()方法為主線,看看其子類們的實現(xiàn)。它主要分為兩大主線: AbstractUrlHandlerMapping AbstractHandlerMethodMapping 。

本文是以AbstractHandlerMethodMapping的子類RequestMappingHandlerMapping為主線

在這里插入圖片描述

二、RequestMappingHandlerMapping的初始化

HandlerMethod映射器都是是處理器映射器的一種類型的映射器。這種類型的映射器有一個模版類AbstractHandlerMethodMapping 所有的HandlerMethod映射器都是實現(xiàn)他的

AbstractHandlerMethodMapping包括其初始化和調(diào)用過程。為了好講解,在這里就將其初始化和調(diào)用過程代碼分開說

HandlerMethod映射器模版類的初始化

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
			new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
	private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
	static {
		ALLOW_CORS_CONFIG.addAllowedOrigin("*");
		ALLOW_CORS_CONFIG.addAllowedMethod("*");
		ALLOW_CORS_CONFIG.addAllowedHeader("*");
		ALLOW_CORS_CONFIG.setAllowCredentials(true);
	}
	private boolean detectHandlerMethodsInAncestorContexts = false;
	@Nullable
	private HandlerMethodMappingNamingStrategy<T> namingStrategy;
	//注冊表,HandlerMapping在容器啟動過程中初始化,把掃描到的handler放到注冊表中
     private final MappingRegistry mappingRegistry = new MappingRegistry();
	@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}
	protected void initHandlerMethods() {
	    //循環(huán)所有的bean
		for (String beanName : getCandidateBeanNames()) {
		    //如果bean名字不是以scopedTarget.開頭
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		//日志輸出
		handlerMethodsInitialized(getHandlerMethods());
	}
	protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
			beanType = obtainApplicationContext().getType(beanName);
		}
		catch (Throwable ex) {
			if (logger.isTraceEnabled()) {
				logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
			}
		}
		//因為這里我們是研究RequestMappingHandlerMapping,所以這局代碼內(nèi)容如下
		//如果beanType不為null,且類上標(biāo)注@Controller注解或者@RequestMapping注解
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}
	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());
		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				//底層使用了MappingRegistry的register方法
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
    //忽略處理器映射器查詢Handler部分代碼.....
}

1、循環(huán)所有的bean,如果bean名字不是以scopedTarget.開頭,那么就判斷他們是否是Handler(類上標(biāo)注@Controller注解或者@RequestMapping注解)

2、如果是Handler,獲取這個類上所有標(biāo)注@RequestMapping的方法信息,以RequestMappingInfo形式

3、把他們儲存到MappingRegistry中

AbstractHandlerMethodMapping.MappingRegistry:內(nèi)部類注冊中心

維護(hù)幾個Map(鍵值對),用來存儲映射的信息, 還有一個MappingRegistration專門保存注冊信息 這個注冊中心,核心是保存了多個Map映射關(guān)系,相當(dāng)于緩存下來。在請求過來時需要查找的時候,可以迅速定位到處理器

class MappingRegistry {
	//對于RequestMappingHandlerMapping來說
	//保存著RequestMappingInfo和MappingRegistration的對應(yīng)關(guān)系~
	private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
	// 對于保存著mapping和HandlerMethod的對應(yīng)關(guān)系~
	//對于RequestMappingHandlerMapping來說
	//保存著RequestMappingInfo和HandlerMethod的對應(yīng)關(guān)系~
	private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
	// 這里的Map不是普通的Map,而是MultiValueMap,它是個多值Map。其實它的value是一個list類型的值
	// 至于為何是多值?有這么一種情況  URL都是/api/v1/hello  但是有的是get post delete等方法   所以有可能是會匹配到多個MappingInfo的
	//對于RequestMappingHandlerMapping來說,保存著URL和RequestMappingInfo的關(guān)系~
	private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
	//對于RequestMappingHandlerMapping來說,保存著URL和HandlerMethod的關(guān)系~
	private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>()
	// 這兩個就不用解釋了
	private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
	// 讀寫鎖~~~ 讀寫分離  提高啟動效率
	private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	//5.1版本其子類只有一個RequestMappingHandlerMapping,T就是RequestMappingInfo
	//handler一般情況下是處理器方法從屬bean的名字
	//method是處理器方法
    public void register(T mapping, Object handler, Method method) {
			if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
				throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
			}
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				//斷言提供的映射是唯一的。			
				validateMethodMapping(handlerMethod, mapping);
				this.mappingLookup.put(mapping, handlerMethod);
				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);
				}
				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}
                //初始化跨域配置
                //使用的是AbstractHandlerMethodMapping的initCorsConfiguration方法,子類實現(xiàn)
				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}
				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

這個注冊中心,核心是保存了多個Map映射關(guān)系,相當(dāng)于緩存下來。在請求過來時需要查找的時候,可以迅速定位到處理器

在其初始化過程中,其主要模版化的2個方法

protected CorsConfiguration initCorsConfiguration(Object handler, Method method, T mapping) {
		return null;
	}
protected abstract boolean isHandler(Class<?> beanType);

三、RequestMappingHandlerMapping映射器模版類的調(diào)用

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
			new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
	private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
	static {
		ALLOW_CORS_CONFIG.addAllowedOrigin("*");
		ALLOW_CORS_CONFIG.addAllowedMethod("*");
		ALLOW_CORS_CONFIG.addAllowedHeader("*");
		ALLOW_CORS_CONFIG.setAllowCredentials(true);
	}
	private boolean detectHandlerMethodsInAncestorContexts = false;
	@Nullable
	private HandlerMethodMappingNamingStrategy<T> namingStrategy;
	//注冊表,HandlerMapping在容器啟動過程中初始化,把掃描到的handler放到注冊表中
     private final MappingRegistry mappingRegistry = new MappingRegistry();
	 //忽略初始化部分代碼.....
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	   //獲取請求路徑
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		//放到請求屬性中
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
		    //根據(jù)請求和路徑獲取對應(yīng)的處理方法,注冊表中取
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	    // Match是一個private class,內(nèi)部就兩個屬性:T mapping和HandlerMethod handlerMethod
		List<Match> matches = new ArrayList<>();
		// 根據(jù)lookupPath去注冊中心里查找RequestMappingInfo,因為一個具體的url可能匹配上多個RequestMappingInfo
		// 至于為何是多值?有這么一種情況  URL都是/api/v1/hello  但是有的是get post delete等方法  等不一樣,都算多個的  所以有可能是會匹配到多個MappingInfo的
		// 所有這個里可以匹配出多個出來。比如/hello 匹配出GET、POST、PUT都成,所以size可以為3
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
		    // 依賴于子類實現(xiàn)的抽象方法:getMatchingMapping()  看看到底匹不匹配,而不僅僅是URL匹配就行
			// 比如還有method、headers、consumes等等這些不同都代表著不同的MappingInfo的
			// 最終匹配上的,會new Match()放進(jìn)matches里面去
			addMatchingMappings(directPathMatches, matches, request);
		}
		// 當(dāng)還沒有匹配上的時候,別無選擇,只能瀏覽所有映射
		// 這里為何要瀏覽所有的mappings呢?而不是報錯404呢?
		// 增加路徑匹配對范圍,如:/rest 匹配 /rest.ssss
		if (matches.isEmpty()) {
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}
        // 只要找到了一個匹配的  就進(jìn)來這里了~~~
		// 請注意:因為到這里   匹配上的可能還不止一個  所以才需要繼續(xù)處理~~
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			//如果匹配到多個,就取第一個
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(" ");
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			//請求域增加一些屬性,子類重寫
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
		   //請求域增加一些屬性,子類重寫
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}	
}
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
	request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
@Nullable
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
	throws Exception {
return null;
}

RequestMappingHandlerMapping調(diào)用

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
	private static final Method HTTP_OPTIONS_HANDLE_METHOD;
	/**
	 * Expose URI template variables, matrix variables, and producible media types in the request.
	 * @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
	 * @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE
	 * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
	 */
	@Override
	protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
		super.handleMatch(info, lookupPath, request);
		String bestPattern;
		Map<String, String> uriVariables;
		Set<String> patterns = info.getPatternsCondition().getPatterns();
		if (patterns.isEmpty()) {
			bestPattern = lookupPath;
			uriVariables = Collections.emptyMap();
		}
		else {
			bestPattern = patterns.iterator().next();
			uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
		}
		request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
		if (isMatrixVariableContentAvailable()) {
			Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
			request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
		}
		Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
		request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
		if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
			Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
			request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
		}
	}
	private boolean isMatrixVariableContentAvailable() {
		return !getUrlPathHelper().shouldRemoveSemicolonContent();
	}
	private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
			HttpServletRequest request, Map<String, String> uriVariables) {
		Map<String, MultiValueMap<String, String>> result = new LinkedHashMap<>();
		uriVariables.forEach((uriVarKey, uriVarValue) -> {
			int equalsIndex = uriVarValue.indexOf('=');
			if (equalsIndex == -1) {
				return;
			}
			int semicolonIndex = uriVarValue.indexOf(';');
			if (semicolonIndex != -1 && semicolonIndex != 0) {
				uriVariables.put(uriVarKey, uriVarValue.substring(0, semicolonIndex));
			}
			String matrixVariables;
			if (semicolonIndex == -1 || semicolonIndex == 0 || equalsIndex < semicolonIndex) {
				matrixVariables = uriVarValue;
			}
			else {
				matrixVariables = uriVarValue.substring(semicolonIndex + 1);
			}
			MultiValueMap<String, String> vars = WebUtils.parseMatrixVariables(matrixVariables);
			result.put(uriVarKey, getUrlPathHelper().decodeMatrixVariables(request, vars));
		});
		return result;
	}
	/**
	 * Iterate all RequestMappingInfo's once again, look if any match by URL at
	 * least and raise exceptions according to what doesn't match.
	 * @throws HttpRequestMethodNotSupportedException if there are matches by URL
	 * but not by HTTP method
	 * @throws HttpMediaTypeNotAcceptableException if there are matches by URL
	 * but not by consumable/producible media types
	 */
	@Override
	protected HandlerMethod handleNoMatch(
			Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
		PartialMatchHelper helper = new PartialMatchHelper(infos, request);
		if (helper.isEmpty()) {
			return null;
		}
		if (helper.hasMethodsMismatch()) {
			Set<String> methods = helper.getAllowedMethods();
			if (HttpMethod.OPTIONS.matches(request.getMethod())) {
				HttpOptionsHandler handler = new HttpOptionsHandler(methods);
				return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
			}
			throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
		}
		if (helper.hasConsumesMismatch()) {
			Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
			MediaType contentType = null;
			if (StringUtils.hasLength(request.getContentType())) {
				try {
					contentType = MediaType.parseMediaType(request.getContentType());
				}
				catch (InvalidMediaTypeException ex) {
					throw new HttpMediaTypeNotSupportedException(ex.getMessage());
				}
			}
			throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
		}
		if (helper.hasProducesMismatch()) {
			Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
			throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));
		}
		if (helper.hasParamsMismatch()) {
			List<String[]> conditions = helper.getParamConditions();
			throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
		}
		return null;
	}
}

RequestMappingHandlerMapping根據(jù)請求獲取對應(yīng)的handlerMethod過程是:

1、獲取請求路徑

2、根據(jù)路徑到注冊表中查詢對應(yīng)路徑的RequestMappingInfo

3、如果匹配到多個,就取第一個。

4、如果匹配不到,就到注冊表中查詢所有RequestMappingInfo,匹配規(guī)則我們可以自定義。

Spring MVC請求URL帶后綴匹配的情況,如/hello.json也能匹配/hello

RequestMappingInfoHandlerMapping 在處理http請求的時候, 如果 請求url 有后綴,如果找不到精確匹配的那個@RequestMapping方法。 那么,就把后綴去掉,然后.*去匹配,這樣,一般都可以匹配,默認(rèn)這個行為是被開啟的。

比如有一個@RequestMapping("/rest"), 那么精確匹配的情況下, 只會匹配/rest請求。 但如果我前端發(fā)來一個 /rest.abcdef 這樣的請求, 又沒有配置 @RequestMapping("/rest.abcdef") 這樣映射的情況下, 那么@RequestMapping("/rest") 就會生效。

這樣會帶來什么問題呢?絕大多數(shù)情況下是沒有問題的,但是如果你是一個對權(quán)限要求非常嚴(yán)格的系統(tǒng),強(qiáng)烈關(guān)閉此項功能,否則你會有意想不到的"收獲"。

究其原因咱們可以接著上面的分析,其實就到了PatternsRequestCondition這個類上,具體實現(xiàn)是它的匹配邏輯來決定的。

public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
	...
	@Override
	@Nullable
	public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
		// patterns表示此MappingInfo可以匹配的值們。一般對應(yīng)@RequestMapping注解上的patters數(shù)組的值
		if (this.patterns.isEmpty()) {
			return this;
		}
		// 拿到待匹配的值,比如此處為"/hello.json"
		String lookupPath = this.pathHelper.getLookupPathForRequest(request);
		// 最主要就是這個方法了,它拿著這個lookupPath匹配~~~~
		List<String> matches = getMatchingPatterns(lookupPath);
		// 此處如果為empty,就返回null了~~~~
		return (!matches.isEmpty() ? new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);
	}
	public List<String> getMatchingPatterns(String lookupPath) {
		List<String> matches = new ArrayList<>();
		for (String pattern : this.patterns) {
			// 最最最重點就是在getMatchingPattern()這個方法里~~~ 拿著lookupPath和pattern看它倆合拍不~
			String match = getMatchingPattern(pattern, lookupPath);
			if (match != null) {
				matches.add(match);
			}
		}
		// 解釋一下為何匹配的可能是多個。因為url匹配上了,但是還有可能@RequestMapping的其余屬性匹配不上啊,所以此處需要注意  是可能匹配上多個的  最終是唯一匹配就成~
		if (matches.size() > 1) {
			matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
		}
		return matches;
	}
	// // ===============url的真正匹配規(guī)則  非常重要~~~===============
	// 注意這個方法的取名,上面是負(fù)數(shù),這里是單數(shù)~~~~命名規(guī)范也是有藝術(shù)感的
	@Nullable
	private String getMatchingPattern(String pattern, String lookupPath) {
		// 完全相等,那就不繼續(xù)聊了~~~
		if (pattern.equals(lookupPath)) {
			return pattern;
		}
		// 注意了:useSuffixPatternMatch 這個屬性就是我們最終要關(guān)閉后綴匹配的關(guān)鍵
		// 這個值默外部給傳的true(其實內(nèi)部默認(rèn)值是boolean類型為false)
		if (this.useSuffixPatternMatch) {
			// 這個意思是若useSuffixPatternMatch=true我們支持后綴匹配。我們還可以配置fileExtensions讓只支持我們自定義的指定的后綴匹配,而不是下面最終的.*全部支持
			if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
				for (String extension : this.fileExtensions) {
					if (this.pathMatcher.match(pattern + extension, lookupPath)) {
						return pattern + extension;
					}
				}
			}
			// 若你沒有配置指定后綴匹配,并且你的handler也沒有.*這樣匹配的,那就默認(rèn)你的pattern就給你添加上后綴".*",表示匹配所有請求的url的后綴~~~
			else {
				boolean hasSuffix = pattern.indexOf('.') != -1;
				if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
					return pattern + ".*";
				}
			}
		}
		// 若匹配上了 直接返回此patter
		if (this.pathMatcher.match(pattern, lookupPath)) {
			return pattern;
		}
		// 這又是它支持的匹配規(guī)則。默認(rèn)useTrailingSlashMatch它也是true
		// 這就是為何我們的/hello/也能匹配上/hello的原因  
		// 從這可以看出,Spring MVC的寬容度是很高的,容錯處理做得是非常不錯的~~~~~~~
		if (this.useTrailingSlashMatch) {
			if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
				return pattern + "/";
			}
		}
		return null;
	}
}

分析了URL的匹配原因,現(xiàn)在肯定知道為何默認(rèn)情況下"/hello.aaaa"或者"/hello.aaaa/“或者”"/hello/""能匹配上我們/hello的原因了吧~~~

Spring和SpringBoot中如何關(guān)閉此項功能呢?

為何要關(guān)閉的理由,上面其實已經(jīng)說了。當(dāng)我們涉及到嚴(yán)格的權(quán)限校驗(強(qiáng)權(quán)限控制)的時候。特備是一些銀行系統(tǒng)、資產(chǎn)系統(tǒng)等等,關(guān)閉后綴匹配事非常有必要的。

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {
	private boolean useSuffixPatternMatch = true;
	private boolean useTrailingSlashMatch = true;
}

可以看到這兩個屬性值都直接冒泡到RequestMappingHandlerMapping這個實現(xiàn)類上來了,所以我們直接通過配置來改變它的默認(rèn)行為就成。

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    // 關(guān)閉后綴名匹配,關(guān)閉最后一個/匹配
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseSuffixPatternMatch(false);
        configurer.setUseTrailingSlashMatch(false);
    }
}
}

**就這么一下,我們的URL就安全了,再也不能后綴名任意匹配了。**在想用后綴匹配,就甩你四個大字:404

到此這篇關(guān)于SpringMVC處理器映射器HandlerMapping詳解的文章就介紹到這了,更多相關(guān)SpringMVC的HandlerMapping內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Commons beanutils組件簡介

    Commons beanutils組件簡介

    這篇文章主要介紹了commons beanutils組件的相關(guān)內(nèi)容,以及部分實例和基本用法,需要的朋友可以參考下
    2017-09-09
  • Java中線程的等待與喚醒_動力節(jié)點Java學(xué)院整理

    Java中線程的等待與喚醒_動力節(jié)點Java學(xué)院整理

    在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當(dāng)前線程進(jìn)入等待狀態(tài),同時,wait()也會讓當(dāng)前線程釋放它所持有的鎖。下面通過本文給大家介紹Java中線程的等待與喚醒知識,感興趣的朋友一起看看吧
    2017-05-05
  • 詳解RocketMQ中的消費(fèi)者啟動與消費(fèi)流程分析

    詳解RocketMQ中的消費(fèi)者啟動與消費(fèi)流程分析

    本文主要介紹了RocketMQ的消費(fèi)者啟動流程,結(jié)合官方源碼和示例,一步步講述消費(fèi)者在啟動和消息消費(fèi)中的的工作原理及內(nèi)容,并結(jié)合平時業(yè)務(wù)工作中,對我們所熟悉的順序、push/pull模式等進(jìn)行詳細(xì)分析,以及對于消息消費(fèi)失敗和重投帶來問題去進(jìn)行分析,需要的朋友可以參考下
    2022-07-07
  • 解讀@ConfigurationProperties的基本用法

    解讀@ConfigurationProperties的基本用法

    這篇文章主要介紹了@ConfigurationProperties的基本用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • 關(guān)于Mybatis的@param注解及多個傳參

    關(guān)于Mybatis的@param注解及多個傳參

    這篇文章主要介紹了關(guān)于Mybatis的@param注解及多個傳參,@Param的作用就是給參數(shù)命名,比如在mapper里面某方法A(int id),當(dāng)添加注解后A(@Param(“userId”) int id),也就是說外部想要取出傳入的id值,只需要取它的參數(shù)名userId就可以了,需要的朋友可以參考下
    2023-05-05
  • idea中項目前端網(wǎng)頁圖標(biāo)不顯示的原因及解決

    idea中項目前端網(wǎng)頁圖標(biāo)不顯示的原因及解決

    這篇文章主要介紹了idea中項目前端網(wǎng)頁圖標(biāo)不顯示的原因及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 基于Springboot疫苗接種行程管理系統(tǒng)的設(shè)計與實現(xiàn)

    基于Springboot疫苗接種行程管理系統(tǒng)的設(shè)計與實現(xiàn)

    本文主要介紹了基于Springboot實現(xiàn)的疫苗接種行程管理系統(tǒng)的示例代碼,系統(tǒng)主要實現(xiàn)個人疫苗接種管理、行程管理、病史管理、風(fēng)險地區(qū)管理、核酸檢測報告結(jié)果上報、疫情新聞管理等功能,需要的可以參考一下
    2022-03-03
  • Java中String類getBytes()方法詳解與完整實例

    Java中String類getBytes()方法詳解與完整實例

    這篇文章主要給大家介紹了關(guān)于Java中String類getBytes()方法詳解與完整實例的相關(guān)資料,getBytes()是Java編程語言中將一個字符串轉(zhuǎn)化為一個字節(jié)數(shù)組byte[]的方法,需要的朋友可以參考下
    2023-10-10
  • 最新評論