Springmvc是如何根據url路徑找到對應的controller方法的

#####################更新:

SpringMVC的請求處理過程中的路徑匹配過程:

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
(spring-webmvc-4.2.3.RELEASE

路徑匹配的過程中有如下代碼:

List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
   addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
   // No choice but to go through all mappings...
   addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

SpringMVC首先對HTTP請求中的path與已註冊的RequestMappingInfo(經解析的@RequestMapping)中的path進行一個完全匹配來查找對應的HandlerMethod,即處理該請求的方法,**這個匹配就是一個Map#get方法**。若找不到則會遍歷所有的RequestMappingInfo進行查找。這個查找是不會提前停止的,直到遍歷完全部的RequestMappingInfo。

這裏主要是springmvc在項目啓動時根據requestMapping中api的訪問路徑封裝了不同的map對象,對於純path請求就是從map中直接get,而對於使用了pathVariable註解的請求則需要去map對象中找到所有滿足條件的對象然後取最優解。

public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

    if (methods == null || params == null || headers == null || consumes == null || produces == null) {
        if (CorsUtils.isPreFlightRequest(request)) {
            methods = getAccessControlRequestMethodCondition(request);
            if (methods == null || params == null) {
                return null;
            }
        }
        else {
            return null;
        }
    }

    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
    if (patterns == null) {
        return null;
    }

    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }

    return new RequestMappingInfo(this.name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}


org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition

在遍歷過程中,SpringMVC首先會根據@RequestMapping中的headers, params, produces, consumes, methods與實際的HttpServletRequest中的信息對比,剔除掉一些明顯不合格的RequestMapping。 如果以上信息都能夠匹配上,那麼SpringMVC會對RequestMapping中的path進行正則匹配,剔除不合格的。

Comparator comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
接下來會對所有留下來的候選@RequestMapping進行評分並排序。最後選擇分數最高的那個作爲結果。 評分的優先級爲:

path pattern > params > headers > consumes > produces > methods
所以使用非RESTful風格的URL時,SpringMVC可以立刻找到對應的HandlerMethod來處理請求。但是當在URL中存在變量時,即使用了@PathVariable時,SpringMVC就會進行上述的複雜流程。

值得注意的是SpringMVC在匹配@RequestMapping中的path時是通過AntPathMatcher進行的,這段path匹配邏輯是從Ant中借鑑過來的。

####END

使用restful風格時,我們往往會在一個controller方法使用同一個路徑,然後定義不同的httpmethod,那麼問題來了,springmvc是怎麼做到的呢?

首先,看下我們的Controller層代碼

@SpringBootApplication
@EnableEurekaServer
@RestController
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }


    @GetMapping("/hi")
    public String hi() {
        StringBuilder sb = new StringBuilder();
        sb.append("<div style=\"color:red;\">aaaaa</div>")
          .append(ESAPI.encoder().encodeForHTML("<script>alert(1)</script>"));

        String jsCode = "<div style=\"color:red;\">aaaaa</div><script>alert(1)</script>";

        String jsEncoder = ESAPI.encoder().encodeForHTML(jsCode);
        System.out.println("###########################################################");
        System.out.println(sb.toString());
        return sb.toString();
    }

    @GetMapping("/hi/{id}")
    public void getId(@PathVariable String id, String name) {
        System.out.println("hello, " + id + "name:" + name);
    }

    @PostMapping("/hi/{id}")
    public void postId(@PathVariable String id, String name) {
        System.out.println("hello, " + id + "name:" + name);
    }
}

根據路徑"/hi/{id}"定義了一個get方法和post方法。

項目啓動時,springmvc會掃描所有類,將加了mapping的註解的方法和url綁定,存進一個全局的mappingLookup,這裏怎麼匹配的就不詳細介紹了。
在這裏插入圖片描述
createHandlerMethod 會幫我們創建一個HandlerMethod對象

	/**
	 * Create an instance from a bean instance and a method.
	 */
	public HandlerMethod(Object bean, Method method) {
		Assert.notNull(bean, "Bean is required");
		Assert.notNull(method, "Method is required");
		this.bean = bean;
		this.beanFactory = null;
		this.beanType = ClassUtils.getUserClass(bean);
		this.method = method;
		this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
		this.parameters = initMethodParameters();
		evaluateResponseStatus();
	}

這個對象包含了我們的方法名稱 method 、方法參數 parameters 、controller對象 bean 等等
項目啓動完成後,mappingLookup包含了spring掃描到的所有路徑和對應的api.
這時,在瀏覽器訪問我們的路徑,http://localhost:8081/hi/2,此時springmvc攔截這個請求,在DispatcherServlet中執行doDispatch方法,
在這裏插入圖片描述
通過調用getHandler方法獲取代理對象,繼續進一步查看此方法:

在這裏插入圖片描述
這裏會循環遍歷七種mappinghandler獲取最終的handler對象
這七個handler都繼承了抽象類AbstractHandlerMapping,此抽象類中的方法getHandler用於獲取handler 對象

	/**
	 * Look up a handler for the given request, falling back to the default
	 * handler if no specific one is found.
	 * @param request current HTTP request
	 * @return the corresponding handler instance, or the default handler
	 * @see #getHandlerInternal
	 */
	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

再看getHandlerInternal方法,不同的實現類會重寫此方法,由於我們用的requestMapping註解,所以來看它的具體實現
在這裏插入圖片描述
先從request對象中獲取我們的url訪問路徑,再調用lookupHandlerMethod找到對應的method對象

	/**
	 * Look up the best-matching handler method for the current request.
	 * If multiple matches are found, the best match is selected.
	 * @param lookupPath mapping lookup path within the current servlet mapping
	 * @param request the current request
	 * @return the best-matching handler method, or {@code null} if no match
	 * @see #handleMatch(Object, String, HttpServletRequest)
	 * @see #handleNoMatch(Set, String, HttpServletRequest)
	 */
	@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		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(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
			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);
		}
	}

this.mappingRegistry.getMappings().keySet() 返回的就是在啓動時封裝的mappingLooku對象
在這裏插入圖片描述
繼續回到上面的lookupHandlerMethod方法,調用addMatchingMappings方法找到一個最合適的matcher
在這裏插入圖片描述
getMatchingMapping 再繼續深入這個方法,會執行getMatchingCondition方法生成一個RequestMappingInfo對象

/**
	 * Checks if all conditions in this request mapping info match the provided request and returns
	 * a potentially new request mapping info with conditions tailored to the current request.
	 * <p>For example the returned instance may contain the subset of URL patterns that match to
	 * the current request, sorted with best matching patterns on top.
	 * @return a new instance in case all conditions match; or {@code null} otherwise
	 */
	@Override
	@Nullable
	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
		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;
		}
		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
		if (consumes == null) {
			return null;
		}
		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
		if (produces == null) {
			return null;
		}
		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
		if (patterns == null) {
			return null;
		}
		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
		if (custom == null) {
			return null;
		}

		return new RequestMappingInfo(this.name, patterns,
				methods, params, headers, consumes, produces, custom.getCondition());
	}

通過查看RequestMappingInfo對象的屬性,能發現這個對象已經根據url找到對應的controller方法了,翻譯一下這段代碼的註釋

/**
 * Checks if all conditions in this request mapping info match the provided request and returns
 * a potentially new request mapping info with conditions tailored to the current request.
 * <p>For example the returned instance may contain the subset of URL patterns that match to
 * the current request, sorted with best matching patterns on top.
 * @return a new instance in case all conditions match; or {@code null} otherwise
 */

/ **
*檢查此請求映射信息中的所有條件是否與提供的請求匹配
並返回具有針對當前請求量身定製潛在請求映射信息

  • 例如,返回的實例可能包含與以下內容匹配的URL請求,在頂部以最佳匹配模式排序。
  • @在所有條件匹配的情況下返回新實例; 或{@code null}否則
    大概意思就是收集所有滿足條件的method方法。
    在這裏插入圖片描述
    再拿到所有滿足條件的method方法後,會對這些方法進行一個排序,然後取一個分數最高的method

在這裏插入圖片描述
排序實現的邏輯:

/**
	 * Compares "this" info (i.e. the current instance) with another info in the context of a request.
	 * <p>Note: It is assumed both instances have been obtained via
	 * {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
	 * content relevant to current request.
	 */
	@Override
	public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
		int result;
		// Automatic vs explicit HTTP HEAD mapping
		if (HttpMethod.HEAD.matches(request.getMethod())) {
			result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
			if (result != 0) {
				return result;
			}
		}
		result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.producesCondition.compareTo(other.getProducesCondition(), request);
		if (result != 0) {
			return result;
		}
		// Implicit (no method) vs explicit HTTP method mappings
		result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
		if (result != 0) {
			return result;
		}
		result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
		if (result != 0) {
			return result;
		}
		return 0;
	}

拿到HandlerAdapter對象後會在doDispatch方法中調用handle方法進行參數的初始化和方法的調用。
在這裏插入圖片描述

	/**
	 * This implementation expects the handler to be an {@link HandlerMethod}.
	 */
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}

invokeHandlerMethod 調用此方法返回modelandView對象
在這裏插入圖片描述
再次進入invokeHandlerMethod方法,最終會調用invocableMethod.invokeAndHandle(webRequest, mavContainer);

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

invokeAndHandle方法中會執行invokeForRequest方法,在此方法中需關注
getMethodArgumentValues方法
在這裏插入圖片描述
getMethodArgumentValues會將request中的參數封裝進行初始化
在這裏插入圖片描述

resolveArgument方法:

	@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
		MethodParameter nestedParameter = parameter.nestedIfOptional();

		Object resolvedName = resolveStringValue(namedValueInfo.name);
		if (resolvedName == null) {
			throw new IllegalArgumentException(
					"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
		}

		Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
		if (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveStringValue(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());

			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

handleResolvedValue方法會將獲取的值賦值給正確的參數,至此,參數的封裝就完成了,在通過doInvoke動態調用api方法。

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