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


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

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

    标签:源码:springboot

    自定义参数

    @Data
    public class Person {
        private String name;
        private Integer age;
        private Pet pet;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Controller代码

    @RequestMapping("/savePerson")
    @ResponseBody
    public Object savePerson(Person person) {
        System.out.println("person = " + person);
        return person;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    get请求(能收到)

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

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

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

    image-20240310105949983

    控制台输出

    {
        "name": "张三",
        "age": 18,
        "pet": {
            "petName": "黑皇",
            "petAge": 3
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    image-20240310110058133

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

    参数解析流程

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

    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 {
    
    	// ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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);
    	}
       // ...
      
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    数据绑定器

    用于将请求中的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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    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());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    巴拉巴拉又长又丑,中间还有一个数据类型的转换,如果请求过来的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);
    			}
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

    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]]
    
    
    • 1
    • 2
    • 3

    添加自定义转换器

    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;
                        }
                    });
                }
            };
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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

  • 相关阅读:
    【市场解读】掌握MT4外汇交易的核心技巧
    swift内存绑定
    凉鞋的 Unity 笔记 104. 测试所涉及的窗口
    K8S:容器隔离
    从零开始探索C语言(六)----数组
    《C++避坑神器·二十》C++智能指针简单使用
    狂神。SpringBoot员工管理系统项目练习。
    简单几个方法教你怎么把PDF压缩小,试试你就知道
    GCP设置Proxy来连接Cloud SQL
    java的面向对象基础(1) —— 封装
  • 原文地址:https://blog.csdn.net/weixin_41883161/article/details/136565029