在SpringMVC原理(5)-目标方法的执行 - 掘金这篇中分析了目标方法的执行流程。
现在来具体分析一下目标方法执行前的参数处理,也就是参数解析、参数绑定、参数类型转换的原理
我们在接口方法上写了参数,怎么就能获取到请求中参数的值?比如这样
java
@GetMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name") String name) {
return "Hello " + name;
}
getMethodArgumentValues()
在上一篇中已经分析过流程了,这一篇不再分析,只分析具体原理。
执行目标方法前,会调用**getMethodArgumentValues()
** 获取参数的值。这个就是核心方法了
java
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1、获取目标方法所有的参数
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 2、遍历目标方法所有的参数
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) {
continue;
}
// 3、使用参数解析器解析判断是否支持解析这种参数
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 4、使用参数解析器来解析参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// 5、如果有异常
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
其实核心就是利用各种参数解析器来解析参数的值,所以我们把参数解析器搞明白就知道了。
默认有27个参数解析器。这里有什么参数解析器,也就意味着我们方法上就能写什么类型的参数
参数解析器-HandlerMethodArgumentResolver
java
public interface HandlerMethodArgumentResolver {
// 判断是否支持处理此类型的参数
boolean supportsParameter(MethodParameter parameter);
// 解析参数的值
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
supportsParameter()
:判断是否支持处理此类型的参数,如果支持那就用这个参数解析器来处理resolveArgument()
:解析参数的值
里面就这两个方法。现在我们知道了参数解析器的结构,让我们一起来看看它是怎么处理的吧。
下面分析5种情况:
- 加了
@RequestParam
的普通类型参数 - 加了
@PathVariable
的参数 - 自定义的对象类型参数
- 加了
@RequestBody
注解的参数 - 文件上传请求类型的参数
@RequestParam
会被RequestParamMethodArgumentResolver
类来解析。
supportsParameter()
java
public boolean supportsParameter(MethodParameter parameter) {
// 方法参数是否被 @RequestParam 注解修饰
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 {
// 返回true
return true;
}
}
// 省略代码
...
}
resolveArgument()
来到父类AbstractNamedValueMethodArgumentResolver
中
java
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 1、构造对象,包括注解配置的 name、required、默认值 这3个属性的值
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
// 2、得到参数名。也可以写占位符、SpEL表达式
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 3、解析参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
// 4、请求没有传递值
if (arg == null) {
// 拿到我们注解配置的值
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
// 处理 @RequestParam注解属性 required=true 的情况
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
// 处理没有值的情况(抛出异常)
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// 处理null值情况
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
// 5、值是空字符串,并且注解中配置了默认值
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
// 6、数据绑定器工厂
if (binderFactory != null) {
// 6.1、通过数据绑定器工厂创建出一个数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 6.2、绑定器通过 Converter 来转换数据类型(如果需要的话)
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());
}
}
// 7、这里是个空方法
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
- 构造一个
NamedValueInfo
对象。里面包括了@RequestParam
中的name
、required
、defaultValue
属性的值
-
调用
resolveEmbeddedValuesAndExpressions()
得到参数名。也可以写占位符、SpEL表达式 -
Object arg = resolveName()
:根据目标方法的参数名来解析请求参数的值 -
如果解析出来的值是
null
,也就是请求没有传递值- 注解
defaultValue
属性的值不为null,就拿到它值 - 否则如果
required=true
,就直接抛出异常了 - 再然后处理
null
值情况
- 注解
-
如果解析出来的是空字符串
""
,并且注解中配置了默认值,就拿到注解中defaultValue
属性的值 -
如果
WebDataBinderFactory
(数据绑定器工厂)不为null
,就会创建一个WebDataBinder
(数据绑定器) -
通过
WebDataBinder
(数据绑定器 )的convertIfNecessary()
来进行数据类型转换- 它又通过
Converter
转换器进行数据类型转换 - 比如String想要转Integer,String想要转Long,String转枚举,Long转日期
- 它又通过
-
handleResolvedValue()
:这里是个空方法
3、 根据目标方法的参数名来解析请求参数的值-resolveName()
java
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
// 1、获取原生请求
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
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);
}
}
if (arg == null) {
// 调用底层api得到请求中参数的值
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
// 单个 or 集合
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
还会用Converter
来转换数据类型,在最后总结的时候说原理。
@PathVariable
会被PathVariableMethodArgumentResolver
类解析
supportsParameter()
java
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 方法参数是否被 @PathVariable 注解修饰
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;
}
resolveArgument()
还是来到父类AbstractNamedValueMethodArgumentResolver
中,逻辑和上面的一样,只是具体实现类变了。
根据目标方法的参数名来解析请求参数的值-resolveName()
typescript
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);
}
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
// 调用底层api从请求域中获取数据
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
最后也会调用Converter
进行数据类型转换
自定义的对象参数
会被ServletModelAttributeMethodProcessor
这个类处理
supportsParameter()
typescript
// 不是必须的,并且不是简单类型
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
// 简单类型有这些
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
resolveArgument()
java
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
// 参数名字
String name = ModelFactory.getNameForParameter(parameter);
// @ModelAttribute注解的处理,这里不用管
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
// 这里会创建一个方法参数的对象,用来封装请求携带的值
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
//
if (bindingResult == null) {
// 创建一个Web数据绑定器,里面封装了刚创建的Java对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 绑定请求参数到Java对象上
bindRequestParameters(binder, webRequest);
}
// @Validated注解校验逻辑
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
// 把封装号的对象返回
return attribute;
}
流程:
-
获取参数名字
-
对
@ModelAttribute
注解的处理 -
createAttribute()
:创建目标方法参数的对象,用来封装请求携带的参数值 -
bindRequestParameters()
:使用WebDataBinder
来绑定数据
bindRequestParameters()
方法
ServletModelAttributeMethodProcessor
java
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
// 绑定数据
servletBinder.bind(servletRequest);
}
bind()
方法
ServletRequestDataBinder
java
public void bind(ServletRequest request) {
// 获取请求参数的值封装为MutablePropertyValues对象
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
// 文件上传请求
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
// 绑定
doBind(mpvs);
}
请求参数会被封装到MutablePropertyValues
对象中:
doBind()
java
// ===WebDataBinder.class===
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
// ===DataBinder.class===
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
// 应用参数的值
applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// 绑定请求参数的值到目标对象上
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
// ===AbstractPropertyAccessor.class===
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}
try {
// 遍历所有的属性值,然后绑定
for (PropertyValue pv : propertyValues) {
// setPropertyValue may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
try {
setPropertyValue(pv);
}
// 省略代码
...
}
}
// 省略代码
...
}
这一套流程走完之后,对象里就有数据了。然后在利用反射执行目标方法的时候,把这个对象传递过去就好了。
到这一步我们就知道了自定义的对象类型的参数是怎么封装的。就是会先创建一个对应的对象,然后通过WebDataBinder
把参数绑定到这个对象上,并会利用Converter
做类型转换。
@RequestBody
会被RequestResponseBodyMethodProcessor
类处理
supportsParameter()
参数被@RequestBody
注解修饰
java
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
resolveArgument()
java
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 使用 HttpMessageConverter 读取请求携带的参数值
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
使用HttpMessageConverter
读取请求携带的参数值,它最终会利用ObjectMapper
的readValue()
来读取数据
通过ObjectMapper
读取到的数据:
默认的HttpMessageConverter
,有10个
文件上传请求类型的参数
文件上传请求:
- 没标注解:会被
RequestParamMethodArgumentResolver
类处理 - 标了
@RequestPart
注解会被RequestPartMethodArgumentResolver
类处理
我这里分析的是RequestParamMethodArgumentResolver
类
supportsParameter()
java
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();
// 参数是否是 MultipartFile 类型
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
// ===MultipartResolutionDelegate.class===
public static boolean isMultipartArgument(MethodParameter parameter) {
// 获取参数类型
Class<?> paramType = parameter.getNestedParameterType();
// MultipartFile或Part类型
return (MultipartFile.class == paramType ||
isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
(Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
}
resolveArgument()
这里我只把关键的拿过来,流程都是和@RequestParam
的解析原理是一样的,不知道的可以再看一下上面
java
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
// 获取原生请求对象,这里获取到的是
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
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);
}
}
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
- 获取原生请求对象,这里获取到的是
- 利用
MultipartResolutionDelegate
工具类来解析文件类型的参数
工具类 :
MultipartResolutionDelegate
java
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
throws Exception {
MultipartHttpServletRequest multipartRequest =
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 ? multipartRequest.getFile(name) : null);
}
else if (isMultipartFileCollection(parameter)) {// 集合 List<MultipartFile>
if (multipartRequest == null && isMultipart) {
multipartRequest = new StandardMultipartHttpServletRequest(request);
}
return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
}
else if (isMultipartFileArray(parameter)) { // 数组 MultipartFile[]
if (multipartRequest == null && isMultipart) {
multipartRequest = new StandardMultipartHttpServletRequest(request);
}
if (multipartRequest != null) {
List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
return multipartFiles.toArray(new MultipartFile[0]);
}
else {
return null;
}
}
else if (Part.class == parameter.getNestedParameterType()) { // Part
return (isMultipart ? request.getPart(name): null);
}
else if (isPartCollection(parameter)) { // List<Part>
return (isMultipart ? resolvePartList(request, name) : null);
}
else if (isPartArray(parameter)) { // Part[]
return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null);
}
else {
return UNRESOLVABLE;
}
}
- 判断参数是否是
MultipartFile
,是的话就从包装好的请求中获取到上传的文件对象 - 参数可以是:
MultipartFile
、List<MultipartFile>
、MultipartFile[]
、Part
、List<Part>
、Part[]
类型
不懂这个原理的,可以去看这篇,具体原理在SpringMVC原理(1)-文件上传请求 - 掘金中说过
总结
流程:
-
遍历判断哪个参数解析器能处理:supportsParameter()
-
调用不同的参数解析器的
resolveArgument()
来解析不同类型的参数- 利用
WebDataBinder
来绑定参数 - 利用
Converter
来转换数据类型 - json格式利用
HttpMessageConverter
来读或写,它又利用jackson包的ObjectMapper
类做
- 利用
-
把参数封装好返回,最后利用反射来传递参数和执行目标方法
HandlerMethodArgumentResolver
参数解析器。
作用:用来把请求参数封装到目标方法上
-
@RequestParam注解:
RequestPartMethodArgumentResolver
类 -
@PathVariable注解:
PathVariableMethodArgumentResolver
类 -
@RequestBody注解:
RequestResponseBodyMethodProcessor
类 -
自定义JavaBean参数:
ServletModelAttributeMethodProcessor
类 -
文件上传请求参数:
- 默认没标会被
RequestParamMethodArgumentResolver
类处理 - 标了
@RequestPart
注解会被RequestPartMethodArgumentResolver
类处理
- 默认没标会被
WebDataBinder
默认使用ExtendedServletRequestDataBinder
两个作用:
-
将请求参数的值绑定到指定的JavaBean里面;
-
调用
Converter
做数据类型转换
Converter
转换器。
作用:用来数据类型转换
比如把request带来参数的字符转为int、long、float
默认的Converter
:
原理:
进行数据类型转换convertIfNecessary()
如果需要的话,利用Converter
进行数据类型转换
java
// ======DataBinder.class==========
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
// =======TypeConverterSupport.class=======
TypeConverterDelegate typeConverterDelegate;
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return convertIfNecessary(value, requiredType,
(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
}
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
// 通过类型转换器的委托来进行类型转换
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
> TypeConverterDelegate
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 省略代码
...
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 (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
// 省略代码
...
}
这里又通过ConversionService
来做。这个类里封装了所有的Converter
- 遍历所有的
Converter
,判断是否能讲请求参数的类型转为目标方法参数的类型 - 调用
Converter
的converter()
来进行类型转换
补充-集合类型
java
@PostMapping("/hello")
@ResponseBody
public List<String> postHello(@RequestParam List<String> list) {
return list;
}
类型转换器前:数组
调用类型转换器后:集合
使用的Converter
:
HttpMessageConverter
消息转换器。
作用 :用来读取json数据(@RequestBody
)和响应json(@ResponseBody
)
原理就是利用jackson
的ObjectMapper
类来读或写
HttpMessageConverter
接口
java
public interface HttpMessageConverter<T> {
// 是否能读
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
// 是否能写
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
// 支持处理的媒体类型
List<MediaType> getSupportedMediaTypes();
// 读
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 写
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
默认的HttpMessageConverter
,有10个:
原理:
java
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
// 获取 Content-Type 的值
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// 遍历所有HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 判断是否支持读
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 读取
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
- 获取
Content-Type
的值 - 遍历所有
HttpMessageConverter
,挨个调用canRead()
判断看谁支持读,支持的话就调用read()
来读取
最终AbstractJackson2HttpMessageConverter
类支持读取这种类型
kotlin
return objectReader.readValue(inputMessage.getBody());