SpringMVC之RequestMappingInfo详解之合并&请求匹配&排序

写在前面

1.RequestMapping 概述

先来看一张图:

SpringMVC之RequestMappingInfo详解之合并&请求匹配&排序

从这张图,我们可以发现几个规律:

  1. @RequestMapping 的注解属性中,除了 name 不是数组,其他注解属性都支持数组。

  2. @RequestMapping 的注解属性中,除了 method 属性类型是枚举类型 RequestMethod,其他注解属性都用的 String 类型。

  3. DefaultBuilder 是 RequestMappingInfo 的私有静态内部类,该类设计上使用了建造者模式。

  4. 在 DefaultBuilder # build() 方法中,除 mappingName 以外的属性都被用来创建 RequestCondition 的子类实例。

  5. DefaultBuilder # build() 返回值是 RequestMappingInfo,该对象的构造函数包含 name 以及多个RequestCondition 的子类。

本节接下来对各个参数逐组进行说明,熟悉的同学可以跳过。

其中,@RequestMapping 的注解属性 RequestMethod[ ] method() 的数组元素会通过构造函数传递给 RequestMethodsRequestCondition ,用于构成成员变量 Set<RequestMethod> methods ,也比较简单,不多赘述。

params 和 headers

params 和 headers 相同点均有以下三种表达式格式:

  1. !param1: 表示允许不含有 param1 请求参数/请求头参数(以下简称参数)

  2. param2!=value2:表示允许不包含 param2 或者 虽然包含 param2 参数但是值不等于 value2;不允许包含param2参数且值等于value2

  3. param3=value3:表示需要包含 param3 参数且值等于 value3

这三种表达式的解析逻辑来自 AbstractNameValueExpression 点击展开

AbstractNameValueExpression(String expression) {
	int separator = expression.indexOf('=');
	if (separator == -1) {
		this.isNegated = expression.startsWith("!");
		this.name = (this.isNegated ? expression.substring(1) : expression);
		this.value = null;
	}
	else {
		this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!');
		this.name = (this.isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator));
		this.value = parseValue(expression.substring(separator + 1));
	}
}

TIPS:HeadersRequestCondition / ParamsRequestCondition 和 HttpServletRequest 是否能够匹配,取决于 getMatchingCondition 方法。该方法返回 null 表示不匹配,有返回值表示可以匹配。

params 和 headers 不同点大小写敏感度不同:

params: 大小写敏感:"!Param" 和 "!param" ,前者表示不允许 Param 参数,后者则表示不允许 param 参数。反映在源码上,即 ParamsRequestCondition 的成员变量 expressions 包含 2 个 ParamExpression 对象。

headers: 大小写不敏感:"X-Forwarded-For=unknown" 和 "x-forwarded-for=unknown" 表达式含义是一样的。反映在源码上,即 HeadersRequestCondition 的成员变量 expressions 仅包含 1 个 HeaderExpression 对象。

headers 的额外注意点:

headers={"Accept=application/*","Content-Type=application/*"}
Accept 和 Content-Type 解析得到的 HeaderExpression 不会被添加到 HeadersRequestCondition 中。

private static Collection parseExpressions(String... headers) {
	Set expressions = new LinkedHashSet<>();
	for (String header : headers) {
		HeaderExpression expr = new HeaderExpression(header);
		if ("Accept".equalsIgnoreCase(expr.name) || "Content-Type".equalsIgnoreCase(expr.name)) {
			continue;
		}
		expressions.add(expr);
	}
	return expressions;
}

consumes 和 produces

consumes 和 produces 不同点:

headers 中 Accept=value 的 value 会被 ProducesRequestCondition 解析。

相对地,headers 中 Content-Type=value 的 value 会被 ConsumesRequestCondition 解析。

ProducesRequestCondition # parseExpressions

private Set parseExpressions(String[] produces, @Nullable String[] headers) {
Set result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
return result;
}


ConsumesRequestCondition # parseExpressions

private static Set parseExpressions(String[] consumes, @Nullable String[] headers) {
	Set result = new LinkedHashSet<>();
	if (headers != null) {
		for (String header : headers) {
			HeaderExpression expr = new HeaderExpression(header);
			if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) {
				for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
					result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
				}
			}
		}
	}
	for (String consume : consumes) {
		result.add(new ConsumeMediaTypeExpression(consume));
	}
	return result;
}

consumes 和 produces 相同点:均有正反 2 种表达式

  • 肯定表达式:"text/plain"

  • 否定表达式:"!text/plain"

常见的类型,可以从 org.springframework.http.MediaType 引用,比如 MediaType.APPLICATION_JSON_VALUE = "application/json"。


MimeTypeUtils.parseMimeType 这个静态方法可以将字符串转换为 MimeType:

public static MimeType parseMimeType(String mimeType) {
	// 验证成分是否齐全
	if (!StringUtils.hasLength(mimeType)) {
		throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
	}
	// 如果包含分号(;),那么就取分号之前的部分进行解析
	int index = mimeType.indexOf(';');
	String fullType = (index >= 0 ? mimeType.substring(0, index) : mimeType).trim();
	if (fullType.isEmpty()) {
		throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
	}
	// 遇上单个星号(*)转换成全通配符(*/*)
	if (MimeType.WILDCARD_TYPE.equals(fullType)) {
		fullType = "*/*";
	}
	// 斜杠左右两边分别是 type 和 subType
	int subIndex = fullType.indexOf('/');
	if (subIndex == -1) {
		throw new InvalidMimeTypeException(mimeType, "does not contain '/'");
	}
	// subType 为空,抛出异常
	if (subIndex == fullType.length() - 1) {
		throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'");
	}
	String type = fullType.substring(0, subIndex);
	String subtype = fullType.substring(subIndex + 1, fullType.length());
	// type 为 *, subType 不为 * 是不合法的通配符格式, 例如  */json 
	if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) {
		throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)");
	}
	// 解析参数部分
	Map<string, string=""> parameters = null;
	do {
		int nextIndex = index + 1;
		boolean quoted = false;
		while (nextIndex < mimeType.length()) {
			char ch = mimeType.charAt(nextIndex);
			if (ch == ';') {
				// 双引号之间的分号不能作为参数分割符,比如 name="Sam;Uncle" ,扫描到分号时,不会退出循环
				if (!quoted) {
					break;
				}
			}
			else if (ch == '"') {
				quoted = !quoted;
			}
			nextIndex++;
		}
		String parameter = mimeType.substring(index + 1, nextIndex).trim();
		if (parameter.length() > 0) {
			if (parameters == null) {
				parameters = new LinkedHashMap<>(4);
			}
			// 等号分隔参数key和value
			int eqIndex = parameter.indexOf('=');
			// 如果没有等号,这个参数不会被解析出来,比如 ;hello; ,其中 hello 就不会被解析为参数 
			if (eqIndex >= 0) {
				String attribute = parameter.substring(0, eqIndex).trim();
				String value = parameter.substring(eqIndex + 1, parameter.length()).trim();
				parameters.put(attribute, value);
			}
		}
		index = nextIndex;
	}
	while (index < mimeType.length());
	try {
		// 创建并返回一个 MimeType 对象
		return new MimeType(type, subtype, parameters);
	}
	catch (UnsupportedCharsetException ex) {
		throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetNam
	}
	catch (IllegalArgumentException ex) {
		throw new InvalidMimeTypeException(mimeType, ex.getMessage());
	}
}
上一篇:Shell编程中变量用法


下一篇:sqlparser