掘金 后端 ( ) • 2024-03-28 11:20

springboot源码解析之自定义参数解析

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家

标签:源码:springboot

自定义参数

@Data
public class Person {
    private String name;
    private Integer age;
    private Pet pet;
}

Controller代码

@RequestMapping("/savePerson")
@ResponseBody
public Object savePerson(Person person) {
    System.out.println("person = " + person);
    return person;
}

get请求(能收到)

/savePerson?name=李四&age=18&pet.petName=黑皇&pet.petAge=3

person = Person(name=李四, age=18, pet=Pet(petName=黑皇, petAge=3))

post请求-前端传参形式form-data(能收到)

image-20240310105949983

控制台输出

{
    "name": "张三",
    "age": 18,
    "pet": {
        "petName": "黑皇",
        "petAge": 3
    }
}

post请求-前端传参形式json(收不到)

为啥呢?因为不管是get请求还是post请求,传给后端的都是key-value对,json对象是一个整体

image-20240310110058133

person = Person(name=null, age=null, pet=null)

参数解析流程

先直接上结论吧,数据解析的过程太长了

  1. 从参数解析器组里拿到自定义参数的解析器ServletModelAttributeMethodProcessor(循环遍历参数解析器组,找到能支持的)
  2. 然后通过反射创建出一个空的参数对象(这里就是Person)
  3. 创建数据绑定器,数据绑定器里面封装了刚刚创建的对象Person,还有参数的名称person,以及请求(WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);),在创建数据绑定器的过程中还会给数据绑定器设置数据转换器(http传输的key-value均为字符串,需要将字符串解析为参数所需要的类型,比如将age = "18"转为age=18)
  4. 通过数据绑定器将请求中的数据绑定到刚刚创建的Person对象里

26个默认参数解析器

  1. org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@78b6a2d
  2. org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@6e058e2e
  3. org.springframework.web.method.annotation.MapMethodProcessor@4739b98d
  4. org.springframework.web.method.annotation.ModelMethodProcessor@3ba3a6c
  5. org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@5a90bb5a
  6. org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@2b585515
  7. org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@1a758e21
  8. org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@19275a1e
  9. org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@1e07c615
  10. org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2c2cd73f
  11. org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@7a9ffe46
  12. org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@680b4f35
  13. org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@4a73a9f5
  14. org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@4ba4c6f9
  15. org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@4c005168
  16. org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@2081310e
  17. org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@39d2ae1f
  18. org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@1d213998
  19. org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@1376fd7e
  20. org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@32cde714
  21. org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@54d87fc5
  22. org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@a7ba90b
  23. org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@21628d4d
  24. org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@578f7858
  25. org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@4f092a2e
  26. org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@275003f9

注意有两个ServletModelAttributeMethodProcessor对象org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@54d87fc5和org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@a7ba90b,一个负责解析ModelAttribute注解,一个负责解析自定义类型,且解析ModelAttribute注解注解的顺序在前面

自定义参数的解析器

ServletModelAttributeMethodProcessor

public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {

	// ...

}

ModelAttributeMethodProcessor

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

   // ...

   private final boolean annotationNotRequired;


   @Override
   public boolean supportsParameter(MethodParameter parameter) {
       // 参数上有ModelAttribute注解或者没有ModelAttribute注解并且不是简单类型(在参数解析器组中有两个ServletModelAttributeMethodProcessor对象,一个annotationNotRequired == false,优先级高,一个annotationNotRequired == true 优先级低,自定义参数解析用的是annotationNotRequired == true 的)
      return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
   }
    
    @Override
	@Nullable
	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");

        // 获取到参数的name
		String name = ModelFactory.getNameForParameter(parameter);
		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 {
                // 通过反射创建一个参数类型的空对象 测试代码会在这里创建出一个空Person对象 
				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) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
            // 这里binder的具体类型为ExtendedServletRequestDataBinder
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                    // 将请求中的数据 绑定到 binder中的target中,也就是 person对象上
					bindRequestParameters(binder, webRequest);
				}
				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;
	}
    // 调用绑定器的绑定
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
		((WebRequestDataBinder) binder).bind(request);
	}
   // ...
  

}

数据绑定器

用于将请求中的key-value数据通过类型转换,反射绑定到new 出来的参数对象上,数据绑定的流程还是很麻烦的

ExtendedServletRequestDataBinder

image-20240308143302663

ExtendedServletRequestDataBinder中重要属性说明

  1. target:要绑定的目标对象(传给方法的参数对象),也就是代码里面的attribute,这里就是Person对象

  2. objectName:参数名称

  3. conversionService:消息转换器,http请求,超文本传输请求,一切皆文本(也不知道这么理解对不对),收到的参数不管是数字还是日期,都是字符串的形式,需要通过转换器转为实际方法入参所需要的,默认有124个

    image-20240308144151810

数据绑定器的数据绑定过程

public class WebRequestDataBinder extends WebDataBinder {
    public void bind(WebRequest request) {
        // 这里就能拿到 请求参数中的key-value对,不管是get请求还是post请求的form-data格式的,都是key-value形式的
		MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
		if (isMultipartRequest(request) && request instanceof NativeWebRequest) {
			MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
			if (multipartRequest != null) {
				bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
			}
			else {
				HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null) {
					bindParts(servletRequest, mpvs);
				}
			}
		}
        // 绑定数据
		doBind(mpvs);
	}
    @Override
	protected void doBind(MutablePropertyValues mpvs) {
		checkFieldDefaults(mpvs);
		checkFieldMarkers(mpvs);
		super.doBind(mpvs);
	}
}
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
    protected void doBind(MutablePropertyValues mpvs) {
		checkAllowedFields(mpvs);
		checkRequiredFields(mpvs);
        // 应用属性和属性的值
		applyPropertyValues(mpvs);
	}
    protected void applyPropertyValues(MutablePropertyValues mpvs) {
		try {
			// Bind request parameters onto target object.
			getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
		}
		catch (PropertyBatchUpdateException ex) {
			// Use bind error processor to create FieldErrors.
			for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
				getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
			}
		}
	}
    protected ConfigurablePropertyAccessor getPropertyAccessor() {
		return getInternalBindingResult().getPropertyAccessor();
	}
}
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
    @Override
	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()));
		for (PropertyValue pv : propertyValues) {
			try {
				// This method 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.
                // pv里面就封装了key-value信息
				setPropertyValue(pv);
			}
			catch (NotWritablePropertyException ex) {
				if (!ignoreUnknown) {
					throw ex;
				}
				// Otherwise, just ignore it and continue...
			}
			catch (NullValueInNestedPathException ex) {
				if (!ignoreInvalid) {
					throw ex;
				}
				// Otherwise, just ignore it and continue...
			}
			catch (PropertyAccessException ex) {
				if (propertyAccessExceptions == null) {
					propertyAccessExceptions = new ArrayList<>();
				}
				propertyAccessExceptions.add(ex);
			}
		}

		// If we encountered individual exceptions, throw the composite exception.
		if (propertyAccessExceptions != null) {
			PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
			throw new PropertyBatchUpdateException(paeArray);
		}
	}
    @Override
	public void setPropertyValue(PropertyValue pv) throws BeansException {
		setPropertyValue(pv.getName(), pv.getValue());
	}
}

巴拉巴拉又长又丑,中间还有一个数据类型的转换,如果请求过来的age = "18"是数字类型,调用转换服务转为实际参数需要的类型age = 18

BeanWrapperImpl

public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
    @Override
		public void setValue(final @Nullable Object value) throws Exception {
            // 拿到set方法 这里拿到了person对象的setAge方法
			final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
					((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
					this.pd.getWriteMethod());
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					ReflectionUtils.makeAccessible(writeMethod);
					return null;
				});
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
							writeMethod.invoke(getWrappedInstance(), value), acc);
				}
				catch (PrivilegedActionException ex) {
					throw ex.getException();
				}
			}
			else {
                // 设置方法为可访问的
				ReflectionUtils.makeAccessible(writeMethod);
                // 调用person的setAge方法给 person对象赋值
				writeMethod.invoke(getWrappedInstance(), value);
			}
		}
}

解析自定义参数的调用栈还是蛮长的

image-20240308151430825

自定义转换器Converter

当前端传的参数为"/savePerson?name=李四&age=18&pet=黑皇,3"时,spring试图将字符串"黑皇,3"转为Pet对象,默认的转换器里面没有一个将字符串转为pet对象的,所以就会报错

2024-03-10 11:40:58 - WARN - [io-8088-exec-4] .support.DefaultHandlerExceptionResolver. logException 199 : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'person' on field 'pet': rejected value [黑皇,3]; codes [typeMismatch.person.pet,typeMismatch.pet,typeMismatch.com.lxw.study.entity.Pet,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.pet,pet]; arguments []; default message [pet]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.lxw.study.entity.Pet' for property 'pet'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.lxw.study.entity.Pet' for property 'pet': no matching editors or conversion strategy found]]

添加自定义转换器

package com.lxw.study.config;

import com.lxw.study.entity.Pet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {
                        if (StringUtils.hasText(source)) {
                            Pet pet = new Pet();
                            pet.setPetName(source.split(",")[0]);
                            pet.setPetAge(Integer.parseInt(source.split(",")[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
}

再次请求就ok了,注意Converter的包是org.springframework.core.convert.converter.Converter