参数解析主要代码:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (this.logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
this.logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
实现逻辑,判断handler方法方法参数中的参数有多少个,然后new一个数组出来,用来盛放对参数解析后的值:
如果没有参数就直接进行了返回,如果有参数,那么先来找哪种参数解析支持当前类型的解析;如果支持了会使用响应的参数解析器来进行解析;
如果没有找到对应的参数解析器,那么会抛出没有合适的参数解析器。
那么下面接下来例举一些常用的注解:
1、@RequestPram
@RequestMapping("testPram")
public String testPram(@RequestParam("num") Integer num){
System.out.println("接收到的请求参数是:"+num);
return "success";
}
打上断点来进行观察:
然后看一下如何判断支持就进行对应的操作的:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
if (result == null) {
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
看一下上面的判断,这种设计思想还是值得学习的。首先从缓存中来进行获取得到有没有对应类型的参数解析器,如果有的话,直接返回,如果没有,那么获取得到所有的参数解析器来进行解析。
RequestParamMethodArgumentResolver
看一下对对应注解的解析:
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
return true;
} else {
RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
return requestParam != null && StringUtils.hasText(requestParam.name());
}
} else if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
} else {
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
} else {
return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false;
}
}
}
首先判断是不是带有RequestParam,然后判断是不是Map类型的,也就是参数数据类型是不是Map数据类型的(也是支持的)
当然Map类型的不常用,上面在代码里面写的就是RequestParam,参数类型不是Map类型的。
拿到了对应的参数解析器之后,看看如何进行解析:
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
} else {
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}
紧接着跟下去:
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} else {
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = this.resolveStringValue(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = this.resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException var11) {
throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
} catch (TypeMismatchException var12) {
throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
}
}
this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
}
首先解析到在@RequestParam中给方法参数写的别名,然后获取得到这个别名,然后从UrlPathHelper中将request请求中携带的数据缓存下来,在之后的操作中都会从缓存中来获取得到数据。
得到了参数名称之后开始来对参数的值来进行确定
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
Object arg;
if (servletRequest != null) {
arg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (arg != MultipartResolutionDelegate.UNRESOLVABLE) {
return arg;
}
}
arg = null;
MultipartRequest 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;
}
}
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
return arg;
}
可以看到@RequestParam注解还是支持MultipartRequest,即文件上传。但是一般不要使用@RequestParam来上传文件,而是使用@RequestPart来进行文件上传,因为在项目中遇到过一次使用@ReqeustParam注解会出现问题,使用@RequestPart来进行文件上传就没有问题。
继续上面的分析,拿到参数判断之后,是否是文件上传请求;如果不是文件上传请求,会走到:
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
首先判断这个参数的是否有多个?如果只有一个,那么就返回第一个;如果有多个,那么就返回所有的值;
解析到对应的值之后,首先需要意识到的是拿到了是原始的值,还需要将值来进行转换;
因为浏览器端发送过来的是string数据类型的,但是我们的参数上是对应的integer类型的。
转换的代码在这一步中:
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
获取得到绑定器来进行转换,也就是将请求的参数转换到方法上的参数。这里可以拿到方法参数上的数据类型,也可以获取得到传递过来的字符串,那么找到对应的参数转换器来进行转换即可。
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException {
return this.getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException var14) {
conversionAttemptEx = var14;
}
}
}
相同的设计思想,首先判断哪个转换服务可以来进行转换,如果支持转换的话,才会来进行对应的转换操作。非常容器理解。
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumberConverterFactory.StringToNumber(targetType);
}
最终来到了这个类中,将string类型的参数转换成Integer数据类型的。
2、@RequestPart
看一下文件上传使用的类来进行操作
对应的操作代码:
@RequestMapping("upload")
public String upload(@RequestPart("files")MultipartFile[] multipartFile){
for (MultipartFile file : multipartFile) {
String originalFilename = file.getOriginalFilename();
File file1 = new File("C:\\Users\\lig\\Desktop\\tmp\\"+originalFilename);
if (!file1.exists()){
file1.mkdirs();
}
try {
file.transferTo(file1);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败");
}
}
return "文件上传成功";
}
看一下对应的解析代码:
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional();
String name = this.getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null;
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
这里首先会来判断是否是文件上传请求,然后判断是的话,就直接从request中取出来对应的文件:
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request) throws Exception {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
boolean isMultipart = multipartRequest != null || isMultipartContent(request);
// 参数是不是MultipartFile类型的
if (MultipartFile.class == parameter.getNestedParameterType()) {
if (multipartRequest == null && isMultipart) {
multipartRequest = new StandardMultipartHttpServletRequest(request);
}
return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFile(name) : null;
// 是不是List<MultipartFile>
} else if (isMultipartFileCollection(parameter)) {
if (multipartRequest == null && isMultipart) {
multipartRequest = new StandardMultipartHttpServletRequest(request);
}
// 取出来对应值的文件
return multipartRequest != null ? ((MultipartHttpServletRequest)multipartRequest).getFiles(name) : null;
// 是否是MultipartFile[]类型的
} else if (isMultipartFileArray(parameter)) {
if (multipartRequest == null && isMultipart) {
multipartRequest = new StandardMultipartHttpServletRequest(request);
}
if (multipartRequest != null) {
List<MultipartFile> multipartFiles = ((MultipartHttpServletRequest)multipartRequest).getFiles(name);
return multipartFiles.toArray(new MultipartFile[0]);
} else {
return null;
}
// 是不是part类型的、List<Part>、part[]
} else if (Part.class == parameter.getNestedParameterType()) {
return isMultipart ? request.getPart(name) : null;
} else if (isPartCollection(parameter)) {
return isMultipart ? resolvePartList(request, name) : null;
} else if (isPartArray(parameter)) {
return isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null;
} else {
return UNRESOLVABLE;
}
}
3、@RequestBody
{
"id":11,
"name":"liguang",
"money":220
}
后端:
@RequestMapping("requestbody")
public String reqeustBody(@RequestBody Dog dog){
System.out.println(dog);
return "接收requestbody数据类型的成功";
}
关键代码就这么一句:
if (isUnicode) {
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
} else {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
return this.objectMapper.readValue(reader, javaType);
}
这里是jacson的使用方式,非常简单。