• Spring MVC注解Controller源码流程解析--定位HandlerMethod



    引言

    Spring MVC注解Controller源码流程解析–映射建立

    上一篇中,我们对映射建立的过程做了详细的分析,既然映射关系已经建立完毕了,那么下面就是当请求来临时,如何通过请求去映射集合中寻找出对应的HandlerMethod,然后再交给RequestMappingHandlerAdapter完成请求最终处理。

    如果是通过请求路径去映射集合中通过精确匹配进行查询的话,其实实现起来就很简单了,但是因为要加入@RequestMapping中相关请求限制,包括通配符匹配和占位符匹配等等内容,会让寻找HandlerMethod的过程变的不那么简单,但是也没有那么复杂,下面我们就来看看。


    定位HandlerMethod

    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		        ....
    		        //检查是否是文件上传请求,如果是的话,就返回封装后的MultipartHttpServletRequest
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				//通过当前请求定位到具体处理的handler--这里是handlerMethod
    				mappedHandler = getHandler(processedRequest);
    				....
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们本节的重点就在getHandler方法中:

    	@Nullable
    	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    getHandler方法中会遍历所有可用的HandlerMapping,然后尝试通过当前请求解析得到一个handler,如果不为空,说明找到了,否则借助下一个HandlerMapping继续寻找。

    前面已经说过了,注解Controller的映射建立是通过RequestMappingHandlerMapping完成的,那么寻找映射当然也需要通过RequestMappingHandlerMapping完成,因此我们这里只关注RequestMappingHandlerMapping的getHandler流程链即可。


    getHandler方法主要是由AbstractHandlerMapping顶层抽象基类提供了一个模板方法实现,具体根据request寻找handler的逻辑实现,是通过getHandlerInternal抽象方法交给子类实现的。

    	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	    //这里调用到的是RequestMappingInfoHandlerMapping子类提供的实现
    		Object handler = getHandlerInternal(request);
    		//如果没找到,尝试寻找兜底的默认handler
    		if (handler == null) {
    			handler = getDefaultHandler();
    		}
    		//如果还是兜底也不管用,就返回null
    		if (handler == null) {
    			return null;
    		}
    		// 考虑到handler本身可能是一个多例bean,因此不能提前进行实例化,所以spring在handlerMapping的抽象父类AbstractHandlerMapping中
    		//对这一情况进行处理,就是我们下面看到的
    		//当然,考虑到handler存在多种实现,如果某种自定义实现情况下,强制要求handler为单例的,这里也支持
    		//因为没有在handler不为字符串情况下抛出异常
    		if (handler instanceof String) {
    			String handlerName = (String) handler;
    			handler = obtainApplicationContext().getBean(handlerName);
    		}
    
    		 ...
            
            //构建拦截器链
    		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    
    		...
    		//跨域处理
    		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
    			...
    		}
    
    		return executionChain;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    RequestMappingInfoHandlerMapping提供的getHandlerInternal实现

    • RequestMappingInfoHandlerMapping主要作为RequestMappingInfo,Request和HandlerMethod三者之间沟通的桥梁,RequestMappingInfo提供请求匹配条件,判断当前Request是否应该交给当前HandlerMethod处理
    	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    		request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    		try {
    		    //调用父类AbstractHandlerMethodMapping的方法
    			return super.getHandlerInternal(request);
    		}
    		finally {
    		     //把下面这个方法进行内联后,等价于 : request.removeAttribute(MEDIA_TYPES_ATTRIBUTE);
    			ProducesRequestCondition.clearMediaTypesAttribute(request);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    清除Request相关属性,主要是因为Request对象会被复用,因此使用前,需要清空上一次的数据,这也算是对象复用增加的代码复杂性吧。


    AbstractHandlerMethodMapping提供的getHandlerInternal实现

    RequestMappingInfoHandlerMapping重写了父类的getHandlerInternal方法,但只是对Request对象复用进行了相关数据清除工作,核心还是在AbstractHandlerMethodMapping提供的getHandlerInternal实现中。

    	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    	    //initLookupPath默认是返回Context-path后面的路径
    	    //eg1: 没有设置context-path,请求路径为localhost:5200/volunteer/back/admin/pass/login,那这里返回的就是/volunteer/back/admin/pass/login
    	    //eg2: 上面的例子中设置了context-path为/volunteer,那这里返回的就是/back/admin/pass/login
    		String lookupPath = initLookupPath(request);
    		//获取读锁
    		this.mappingRegistry.acquireReadLock();
    		try {
    		   //通过请求路径去映射集合中寻找对应的handlerMethod
    			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    		}
    		finally {
    			this.mappingRegistry.releaseReadLock();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    initLookupPath方法中默认会返回的请求路径为剥离掉context-path后的路径,并且后续拦截器中进行路径匹配时,匹配的也是剥离掉context-path后的路径,这一点切记!


    根据请求路径去映射集合中寻找HandlerMethod

    lookupHandlerMethod是本文的核心关注点,该方法会通过Request定位到对应的HandlerMethod后返回。

    具体处理过程,又可以分为三种情况:

    • 精确匹配到一个结果
    • 需要进行最佳匹配
    • 没有匹配到任何结果

    因为这部分逻辑比较复杂,因此我们对三种情况分开讨论。


    精确匹配到一个结果

    	@Nullable
    	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    		List<Match> matches = new ArrayList<>();
    		//先通过请求路径去pathLookup集合中尝试进行精准匹配--这里的T指的是RequestMappingInfo
    		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    		//精准匹配到了结果
    		if (directPathMatches != null) {
    			//将结果添加进matches集合中--还会经过RequstMappingInfo的条件校验环节
    			addMatchingMappings(directPathMatches, matches, request);
    		}
    		//如果上面精确匹配没有匹配到结果----
    		if (matches.isEmpty()) {
    		     //将register的keySet集合保存的所有RequestMappingInfo都加入matches集合中去
    		     //然后依次遍历每个RequstMappingInfo,通过其自身提供的getMatchingCondition对当前requst请求进行条件匹配
    		     //如果不满足条件,是不会加入到当前matches集合中去的
    			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    		}
    		
    		if (!matches.isEmpty()) {
    		    //获取matches集合中第一个元素
    			Match bestMatch = matches.get(0);
    			//如果matches集合元素大于0,说明需要进一步进行模糊搜索
    			if (matches.size() > 1) {
    				...
    			}
    			//在request对象的属性集合中设置处理当前请求的HandlerMethod
    			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
    			//处理当前最佳匹配
    			handleMatch(bestMatch.mapping, lookupPath, request);
    			return bestMatch.getHandlerMethod();
    		}
    		else {
    		     //没有匹配结果
    			return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    addMatchingMappings将得到的匹配结果RequestMappingInfo加入matches集合,但这个过程中还需要进行一些特殊处理,例如:

        @PostMapping({PASS+"login",PASS+"log"})
    
    • 1

    此时PostMapping会映射到两种请求路径上,此时这里需要做的就是,搞清楚到底是哪一个路径匹配上了当前请求,然后修改RequestMappingInfo对应的patterns集合,将多余的请求路径去除掉。

    还有就是一个请求路径可能会映射到多个RequestMappingInfo上,例如:
    在这里插入图片描述
    请求路径相同,只是请求方法不同。

    	private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    	    //遍历每个RequestMappinginfo
    		for (T mapping : mappings) {
    		     //判断当前RequestMappingInfo是否能够真正映射到当前请求上
    			T match = getMatchingMapping(mapping, request);
    			//如果返回值不为空,表示可以映射,否则跳过处理下一个
    			if (match != null) {
    				matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    getMatchingMapping的判断还是通过RequestMappingInfo自身提供的条件进行进行匹配的:

    	@Override
    	protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    		return info.getMatchingCondition(request);
    	}
    
    • 1
    • 2
    • 3
    • 4

    检查当前RequestMappingInfo 中的所有条件是否与提供的请求匹配,并返回一个新的RequestMappingInfo,其中包含针对当前请求量身定制的条件。

    例如,返回的实例可能包含与当前请求匹配的 URL 模式的子集,并以最佳匹配模式在顶部进行排序。

    	@Override
    	@Nullable
    	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    	    //检查@RequestMapping注解提供的method请求方式是否与当前请求匹配,如果不匹配返回null
    		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    		if (methods == null) {
    			return null;
    		}
    		//判断设置的请求参数匹配条件是否匹配
    		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    		if (params == null) {
    			return null;
    		}
    		//请求头条件
    		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    		if (headers == null) {
    			return null;
    		}
    		//Consume条件检查
    		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    		if (consumes == null) {
    			return null;
    		}
    		//Produce条件检查
    		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
    		if (produces == null) {
    			return null;
    		}
    		//PathPatternsRequestCondition一般为null
    		PathPatternsRequestCondition pathPatterns = null;
    		if (this.pathPatternsCondition != null) {
    			pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
    			if (pathPatterns == null) {
    				return null;
    			}
    		}
    		//@PostMapping({PASS+"login",PASS+"log"})的情况处理 
    	    //RequestMappingInfo中的patterns数组中如果存在多个请求路径,需要判断当前请求是具体映射到了那个路径上
    	    //然后重新构造一个patternsCondition后返回,该patternsCondition内部包含的只有匹配当前请求路径的那个pattern
    		PatternsRequestCondition patterns = null;
    		if (this.patternsCondition != null) {
    			patterns = this.patternsCondition.getMatchingCondition(request);
    			if (patterns == null) {
    				return null;
    			}
    		}
    		//自定义请求限制
    		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    		if (custom == null) {
    			return null;
    		}
    		//上面这些条件其中一个不通过,那么返回的结果就为null
    		//最后构造一个全新的RequestMappingInfo返回,该RequestMappingInfo中包含的都是匹配上当前请求路径的信息,排除了其他非匹配上的信息
    		return new RequestMappingInfo(this.name, pathPatterns, patterns,
    				methods, params, headers, consumes, produces, custom, this.options);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    如果不清楚@ReuqestMapping注解中各个属性的作用,那么把上面每个条件判断过程看一遍就明白了。


    handleMatch法主要是针对模糊匹配出来的结果进行相关处理,例如: URI template variables,matrix variables和producible media types处理等等…

    上面这些名词关联的注解有: @PathVariable , @MatrixVariable ,producible media types对应的是@RequestMapping中produces设置。

    	@Override
    	protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
    		super.handleMatch(info, lookupPath, request);
            //一般返回的就是@RequestMapping注解中的patterns属性,注意@RequestMapping注解可以映射到多个URL上
            //这里返回的就是patterns属性对应的patternsCondition请求匹配条件对象
    		RequestCondition<?> condition = info.getActivePatternsCondition();
    		//condition默认实现为patternsCondition,因此这里直接走else分支
    		if (condition instanceof PathPatternsRequestCondition) {
    			extractMatchDetails((PathPatternsRequestCondition) condition, lookupPath, request);
    		}
    		else {
    		    //抽取匹配细节,该方法内部会完成对上面这些模板变量,矩阵变量的处理
    			extractMatchDetails((PatternsRequestCondition) condition, lookupPath, request);
    		}
            //如果我们设置了@RequestMapping注解中的produces属性,那么这里会进行处理
    		if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
    			Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
    			//设置到request对象的属性集合中,不用想,肯定会在响应的时候用到
    			request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    父类AbstractHandlerMethodMapping中的handleMatch方法,主要是将lookup设置到当前请求对象的属性集合中去:

    	protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
    		request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    	}
    
    • 1
    • 2
    • 3

    对模板变量和矩阵变量的抽取
    	private void extractMatchDetails(
    	       //传入的patternsCondition主要作用在于其内部的patterns属性集合,该集合封装了@RequestMapping注解的patterns属性值内容
    			PatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
    
    		String bestPattern;
    		Map<String, String> uriVariables;
    		//如果patterns属性集合为空--说明我们直接标注了一个@RequestMapping注解,但是没有指定任何属性限制
    		if (condition.isEmptyPathMapping()) {
    		    //那就不存在什么模糊匹配了,bestPattern 就是当前请求路径
    			bestPattern = lookupPath;
    			//模板变量和矩阵变量当然也就不存在了,直接一个空集合
    			uriVariables = Collections.emptyMap();
    		}
    		//我们需要考虑是否存在相关模板变量或者矩阵变量
    		else {
    		    //patterns集合中第一个属性为最佳匹配--这个在addMatchingMappings中被处理完成,不清楚回头看一下
    			bestPattern = condition.getPatterns().iterator().next();
    			//解析模板变量,eg: "/hotels/{hotel}" and path "/hotels/1" --> 返回的map就是"hotel"->"1"
    			uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
    			//关于矩阵变量的处理---这里不展开,感兴趣自己debug看一下源码
    			if (!getUrlPathHelper().shouldRemoveSemicolonContent()) {
    				request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables));
    			}
    			uriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
    		}
    		//设置最佳匹配路径和URL模板变量集合到request对象的属性集合中去
    		request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
    		request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    关于模板变量和矩阵变量的解析细节这里不多展开了,感兴趣可以按照当前思路自行debug源码。


    最佳匹配

    	@Nullable
    	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    		List<Match> matches = new ArrayList<>();
    		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    		if (directPathMatches != null) {
    			addMatchingMappings(directPathMatches, matches, request);
    		}
    		if (matches.isEmpty()) {
    			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    		}
    		if (!matches.isEmpty()) {
    			Match bestMatch = matches.get(0);
    			//如果能够处理当前请求的RequestMappingInfo存在多个,下面就需要进行最佳匹配
    			if (matches.size() > 1) {
    				Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
    				matches.sort(comparator);
    				bestMatch = matches.get(0);
    				if (logger.isTraceEnabled()) {
    					logger.trace(matches.size() + " matching mappings: " + matches);
    				}
    				if (CorsUtils.isPreFlightRequest(request)) {
    					for (Match match : matches) {
    						if (match.hasCorsConfig()) {
    							return PREFLIGHT_AMBIGUOUS_MATCH;
    						}
    					}
    				}
    				else {
    					Match secondBestMatch = matches.get(1);
    					if (comparator.compare(bestMatch, secondBestMatch) == 0) {
    						Method m1 = bestMatch.getHandlerMethod().getMethod();
    						Method m2 = secondBestMatch.getHandlerMethod().getMethod();
    						String uri = request.getRequestURI();
    						throw new IllegalStateException(
    								"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
    					}
    				}
    			}
    			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
    			handleMatch(bestMatch.mapping, lookupPath, request);
    			return bestMatch.getHandlerMethod();
    		}
    		else {
    			return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    是如何完成最佳匹配的,这个过程本文不展开论述,感兴趣自行研究。


    匹配失败

    如果没有寻找当前一个RequestMappingInfo能够处理当前request,那么进入handleNoMatch阶段。

    handleNoMatch会再次迭代所有 RequestMappingInfo,至少通过 URL 查看是否有任何匹配,并根据不匹配的内容引发异常。

    说人话就是找出不匹配的原因,然后抛出对应的异常,告诉用户。

    	@Override
    	protected HandlerMethod handleNoMatch(
    			Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
            //借助PartialMatchHelper来分析那些部分匹配的请求,是因为什么原因而无法匹配成功的
            //部分匹配的意思就是请求路径匹配上了,但是因为其他条件匹配失败了,例如: 请求头限制等
    		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())) {
    				Set<MediaType> mediaTypes = helper.getConsumablePatchMediaTypes();
    				HttpOptionsHandler handler = new HttpOptionsHandler(methods, mediaTypes);
    				return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
    			}
    			throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
    		}
           //consume条件不满足
    		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));
    		}
            //produces条件不满足
    		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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    PartialMatchHelper会首先获取到所有请求路径匹配成功的RequestMappingInfo:

    		public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
    			for (RequestMappingInfo info : infos) {
    				if (info.getActivePatternsCondition().getMatchingCondition(request) != null) {
    					this.partialMatches.add(new PartialMatch(info, request));
    				}
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    PartialMatch的构造方法会判断当前RequestMappingInfo不匹配的原因是什么:

    			public PartialMatch(RequestMappingInfo info, HttpServletRequest request) {
    				this.info = info;
    				this.methodsMatch = (info.getMethodsCondition().getMatchingCondition(request) != null);
    				this.consumesMatch = (info.getConsumesCondition().getMatchingCondition(request) != null);
    				this.producesMatch = (info.getProducesCondition().getMatchingCondition(request) != null);
    				this.paramsMatch = (info.getParamsCondition().getMatchingCondition(request) != null);
    			}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    相关has*Mismatch就是遍历partialMatches集合,然后挨个判断是否存在对应的不匹配原因:

    		public boolean hasMethodsMismatch() {
    			for (PartialMatch match : this.partialMatches) {
    				if (match.hasMethodsMatch()) {
    					return false;
    				}
    			}
    			return true;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    小结

    到此为止,根据request请求去HandlerMethod注册中心寻找对应HandlerMethod的过程就分析完毕了,下一节,会对handlerMethod的调用过程进行分析。

  • 相关阅读:
    Java抽象工厂模式之总有你想不到的知识
    django数据库mysql迁移问题
    Redis的BitMap实现分布式布隆过滤器
    数据结构- 顺序表-单链表-双链表 --【求个关注!】
    04-子类一定要重写接口中定义的方法吗?
    电脑重装系统后Word表格自动换行的方法
    669. Trim a Binary Search Tree
    MES管理系统的设计与实施
    Spring AOP 编程原理和实现
    package-info.java
  • 原文地址:https://blog.csdn.net/m0_53157173/article/details/126668671