接口参数解析-基于键值对

原文链接:https://blog.csdn.net/f641385712/article/details/98989698

从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值
这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver来实现,它是最为重要的分支(分类)

一、AbstractNamedValueMethodArgumentResolver

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

   // 获取指定控制器方法参数参数名,不是从前台传来的
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();

   //  namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
   // 最终的参数名称,下面的 resolveStringValue 语句就是对该名字进行表达式求值,从而得到解析后的
   // 控制器方法参数名称,此名称是从请求上下文中获取相应参数值的关键信息
   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);
      }
      //子类实现,参为null时但处理方法
      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;
}

从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:

根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)

根据方法参数名称name从HttpServletRequest, Http Headers, URI template variables等等中获取属性值

对arg == null这种情况的处理(非必须)

二、PathVariableMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
   if (!parameter.hasParameterAnnotation(PathVariable.class)) {
      return false;
   }
   if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
      return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
   }
   return true;
}

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

它帮助Spring MVC实现restful风格的URL。它用于处理标注有@PathVariable注解的方法参数,用于从URL中获取值(并不是?后面的参数哦)
注意:如果不是使用@PathVariable进行显示定义属性名,那么属性名就是方法参数名

案例

这个方法中,key是name,如果不在@PathVariable中显示写,那么key就是string ,其value是PathVariableMethodArgumentResolver使用resolveName方法从URL中获取

@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") String string){
    System.out.println(string);
    return string;
}

搜索路径:clyu/hello/test ,控制台输出如下:tset

如果我要接收的是一个list,那怎么办呢,情况如下:

@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") List<String> string){
        System.out.println(string);
        return string.toString();
}

搜索路径:clyu/hello/test ,控制台输出如下:[test]
搜索路径:clyu/hello/test,age ,控制台输出如下:[test, age]

关于@PathVariable的required=false使用注意事项

@PathVariable的required=false使用较少,一般用于在用URL传多个值时,但有些值是非必传的时候使用。
比如这样的URL:/user/{id}/{name},/user/{id},/user

@ResponseBody
@GetMapping("/test/{id}")
public Person test(@PathVariable(required = false) Integer id) { ... }

以为这样写通过/test这个url就能访问到了,其实这样是不行的,会404。正确姿势如下:

@ResponseBody
@GetMapping({"/test/{id}", "/test"})
public Person test(@PathVariable(required = false) Integer id) { ... }

这样/test和/test/1这两个url就都能正常work了~

三、RequestParamMethodArgumentResolver

    /** 
	 * 此处理器能处理如下Case:
	 * 1、所有标注有@RequestParam注解的类型(非Map)/ 注解指定了value值的Map类型(自己提供转换器哦)
	 * 
	 * ======下面都表示没有标注@RequestParam注解了的=======
	 * 
	 * 1、不能标注有@RequestPart注解,否则直接不处理了
	 * 
	 * 2、是上传的request:isMultipartArgument() = true(MultipartFile类型或者对应的集合/数组类型  
	 *   或者javax.servlet.http.Part 对应结合/数组类型)
	 * 
	 *3、useDefaultResolution=true情况下,"基本类型"也会处理
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
				return true;
			}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}

    // 核心方法:根据Name 获取值(普通/文件上传)
	// 并且还有集合、数组等情况
	@Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

		// 这块解析出来的是个MultipartFile或者其集合/数组
		if (servletRequest != null) {
			Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
			if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
				return mpArg;
			}
		}

		Object arg = null;
		MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
		if (multipartRequest != null) {
			List<MultipartFile> files = multipartRequest.getFiles(name);
			if (!files.isEmpty()) {
				arg = (files.size() == 1 ? files.get(0) : files);
			}
		}

		// 若解析出来值仍旧为null,那处理完文件上传里木有,那就去参数里取吧
		// 由此可见:文件上传的优先级是高于请求参数的
		if (arg == null) {
		
			//小知识点:getParameter()其实本质是getParameterNames()[0]的效果
			// 强调一遍:?ids=1,2,3 结果是["1,2,3"](兼容方式,不建议使用。注意:只能是逗号分隔)
			// ?ids=1&ids=2&ids=3  结果是[1,2,3](标准的传值方式,建议使用)
			// 但是Spring MVC这两种都能用List接收  请务必注意他们的区别~~~
			String[] paramValues = request.getParameterValues(name);
			if (paramValues != null) {
				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
			}
		}
		return arg;
	}

1、处理器处理情况说明

  • 所有标注有@RequestParam注解的类型(非Map),注意如果是map,不是这个解析器,而RequestParamMapMethodArgumentResolver

  • 下面都表示没有标注@RequestParam注解的情况

    1、不能标注有@RequestPart注解,否则直接不处理了

    2、MultipartFile类型或者对应的集合/数组类型或者javax.servlet.http.Part 对应结合/数组类型

    3、useDefaultResolution=true情况下(useDefaultResolution在Spring中,普遍为true),"基本类型"也会处理

这里的普通类型不是java的概念,而是Spring的。其底层判断方法是BeanUtils.isSimpleProperty,如果参数类型是CharSequence,Number,Date,URI,URL,Locale,Class,或者是他们子类,或者是他们的数组类型,就可以认为是基本类型

2、参数解析过程

先处理文件信息,如果没有文件信息,在根据request.getParameterValues(name)获取属性值的集合

String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
		arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}

3、案例

因为参数获取可以根据名字获取其值的集合,测试如下:

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( String string){
    System.out.println(string);
    return string;
}

搜索路径:/clyu/hello?string=1,控制台输出 :1
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :1,3

如果接收对象是数组呢

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestParam  List<String> string){
    System.out.println(string);
    return string.toString();
}

搜索路径:/clyu/hello?string=1,控制台输出 :[1]
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :[1,3]
搜索路径:/clyu/hello?string=1,3,控制台输出 :[1,3]

两个请求的URL不一样,但都能正确的达到效果。(@RequestParam Object[] objects这么写两种URL也能正常封装)
对此有如下这个细节你必须得注意:对于集合List而言@RequestParam注解是必须存在的,否则报错如下(因为交给ServletModelAttributeMethodProcessor处理了):但如果你这么写String[] objects,即使不写注解,也能够正常完成正确封装。因为它在spring中属于基本类型

3、RequestHeaderMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return (parameter.hasParameterAnnotation(RequestHeader.class) &&
			!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
	String[] headerValues = request.getHeaderValues(name);
	if (headerValues != null) {
		return (headerValues.length == 1 ? headerValues[0] : headerValues);
	}
	else {
		return null;
	}
}

1、处理器处理情况说明

  • 必须标注@RequestHeader注解,并且不能是Map类型 有的小伙伴会说:@RequestHeader Map headers这样可以接收到所有的请求头啊,其实不是本类的功劳,是RequestHeaderMapMethodArgumentResolver的作用

2、参数解析过程

  • 根据属性名,在请求体中查询值
String[] headerValues = request.getHeaderValues(name);
if (headerValues != null) {
            return (headerValues.length == 1 ? headerValues[0] : headerValues);
} else {
            return null;
}

3、案例

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestHeader  String string){
    System.out.println(string);
    return string.toString();
}

在请求体中添加键值对string :123456,发送请求,控制台输出:123456

Tip:注解指定的value值(key值)是不区分大小写的,逗号分隔是可以被封装成集合/数组的
如果这个信息不是必须写的话,请标注@RequestHeader(required = false)

四、RequestAttributeMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(RequestAttribute.class);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
	return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}

1、处理器处理情况说明

  • 必须标注有@RequestAttribute注解

2、参数解析过程

  • 从请求中获取属性值
request.getAttribute(name, RequestAttributes.SCOPE_REQUEST)

五、SessionAttributeMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(SessionAttribute.class);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
	return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
}

1、处理器处理情况说明

  • 必须标注有@SessionAttribute注解

2、参数解析

  • 从session中获取属性值
request.getAttribute(name, RequestAttributes.SCOPE_SESSION);

六、ServletCookieValueMethodArgumentResolver

//这个方法在其父类AbstractCookieValueMethodArgumentResolver中
@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(CookieValue.class);
}

@Override
@Nullable
protected Object resolveName(String cookieName, MethodParameter parameter,
	NativeWebRequest webRequest) throws Exception {

	HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
	Assert.state(servletRequest != null, "No HttpServletRequest");

	Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
	if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
		return cookieValue;
	}
	else if (cookieValue != null) {
		return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
	}
	else {
		return null;
	}
}

1、处理器处理情况说明

  • 必须标注有@CookieValue注解

2、参数解析过程

  • 从请求Cookie中获取值,根据request.getCookies()

七、MatrixVariableMethodArgumentResolver

1、处理器处理情况说明

  • 必须标注有@MatrixVariable注解
@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
			return false;
		}
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
			return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
		}
		return true;
	}
发布了2 篇原创文章 · 获赞 0 · 访问量 45

猜你喜欢

转载自blog.csdn.net/qq_41071876/article/details/104209857