springMVC参数绑定源码分析
创始人
2024-02-17 08:03:33

一、遇到的问题

1. 在请求时get方法路径传参,接收时,用枚举接收,出现参数绑定错误

请求路径:http://localhost:9104/api/sent/test2?type=0

后端代码:

@GetMapping("/test2")public String openNewFile2(FileDTO param) {System.out.println("=====" + param);return "222";}
@Data
public class FileDTO {private SortTypeEnum type;
}

 

枚举类:

@Getter
@AllArgsConstructor
public enum SortTypeEnum {/*** 生序*/ASC(0,"生序"),/*** 降序*/DESC(1,"降序"),;@JsonValueprivate final Integer code;private final String Label;
}

 

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'fileDTO' on field 'type': rejected value [0]; codes [typeMismatch.fileDTO.type,typeMismatch.type,typeMismatch.com.example.demoes.enums.SortTypeEnum,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [fileDTO.type,type]; arguments []; default message [type]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.example.demoes.enums.SortTypeEnum' for property 'type'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [com.example.demoes.enums.SortTypeEnum] for value '0'; nested exception is java.lang.IllegalArgumentException: No enum constant com.example.demoes.enums.SortTypeEnum.0]\r\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:745)\r\n

经过排查发现,在StringToEnumConverterFactory---convert

public T convert(String source) {return source.isEmpty() ? null : Enum.valueOf(this.enumType, source.trim());
}

这个代码的时候,出现错误,上边代码是通过枚举的name去转换成枚举,实现参数绑定。

如果页面传枚举下标就会出错。

正确请求方式:

http://localhost:9104/api/sent/test2?type=ASC

这样就能绑定到参数上了。

2. 如果是post请求,就可以需要body传参

请求路径:

http://localhost:9104/api/sent/test4

body参数:

{"type":1
}

后端代码:

@PostMapping("/test4")public String openNewFile5(@RequestBody FileDTO param) {System.out.println("=====" + param);return "222";}

二、源码分析:

1. get参数绑定

在项目启动时会初始化默认的参数转换类:org.springframework.boot.autoconfigure.BackgroundPreinitializer.ConversionServiceInitializer

private static class ConversionServiceInitializer implements Runnable {private ConversionServiceInitializer() {}
// 会执行这个方法,初始化public void run() {new DefaultFormattingConversionService();}}
public DefaultFormattingConversionService(@Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) {if (embeddedValueResolver != null) {this.setEmbeddedValueResolver(embeddedValueResolver);}// 开始注册默认的DefaultConversionService.addDefaultConverters(this);
//这个方法也会执行if (registerDefaultFormatters) {addDefaultFormatters(this);}}

继续走到下面org.springframework.core.convert.support.DefaultConversionService#addDefaultConverters

这个方法会注册很多数据类型转换类

 public static void addDefaultConverters(ConverterRegistry converterRegistry) {addScalarConverters(converterRegistry);addCollectionConverters(converterRegistry);converterRegistry.addConverter(new ByteBufferConverter((ConversionService)converterRegistry));converterRegistry.addConverter(new StringToTimeZoneConverter());converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());converterRegistry.addConverter(new ObjectToObjectConverter());converterRegistry.addConverter(new IdToEntityConverter((ConversionService)converterRegistry));converterRegistry.addConverter(new FallbackObjectToStringConverter());converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService)converterRegistry));}

在spring容器启动过程中,有个后置处理器

org.springframework.boot.SpringApplication#postProcessApplicationContext

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {if (this.beanNameGenerator != null) {context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", this.beanNameGenerator);}if (this.resourceLoader != null) {if (context instanceof GenericApplicationContext) {((GenericApplicationContext)context).setResourceLoader(this.resourceLoader);}if (context instanceof DefaultResourceLoader) {((DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());}}
//  这里会获取上边实现*Factory的转换类,放入conversionService字段中if (this.addConversionService) {context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());}}

这里会有166个转换类,被加载好放进去 

下面进行请求访问

第一次 请求会初始化servlet

org.springframework.web.servlet.DispatcherServlet#initStrategies---

注册本地的转换

initLocaleResolver(context);

 会创建bean--org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

将上边启动时创建好的ConversionService放入下边的字段中

org.springframework.beans.PropertyEditorRegistrySupport#setConversionService

由请求转发类到参数解析

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument

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);}}

绑定参数

this.bindRequestParameters(binder, webRequest);
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);}

org.springframework.web.bind.ServletRequestDataBinder#bind

public void bind(ServletRequest request) {
// 请求获取参数MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);if (multipartRequest != null) {this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);}this.addBindValues(mpvs, request);this.doBind(mpvs);}

 从请求获取参数的方法,都是用String接收的

org.springframework.web.util.WebUtils#getParametersStartingWith

 

会走到:org.springframework.validation.DataBinder#applyPropertyValues

参数绑定解析结果,找到要绑定的属性

 设置属性

org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)

参数转换

 

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...