• springMVC参数绑定源码分析


    一、遇到的问题

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

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

    后端代码:

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

     

    枚举类

    1. @Getter
    2. @AllArgsConstructor
    3. public enum SortTypeEnum {
    4. /**
    5. * 生序
    6. */
    7. ASC(0,"生序"),
    8. /**
    9. * 降序
    10. */
    11. DESC(1,"降序"),
    12. ;
    13. @JsonValue
    14. private final Integer code;
    15. private final String Label;
    16. }

     

    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参数:

    1. {
    2. "type":1
    3. }

    后端代码:

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

    二、源码分析:

    1. get参数绑定

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

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

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

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

    1. public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    2. addScalarConverters(converterRegistry);
    3. addCollectionConverters(converterRegistry);
    4. converterRegistry.addConverter(new ByteBufferConverter((ConversionService)converterRegistry));
    5. converterRegistry.addConverter(new StringToTimeZoneConverter());
    6. converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
    7. converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
    8. converterRegistry.addConverter(new ObjectToObjectConverter());
    9. converterRegistry.addConverter(new IdToEntityConverter((ConversionService)converterRegistry));
    10. converterRegistry.addConverter(new FallbackObjectToStringConverter());
    11. converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService)converterRegistry));
    12. }

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

    org.springframework.boot.SpringApplication#postProcessApplicationContext

    1. protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    2. if (this.beanNameGenerator != null) {
    3. context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", this.beanNameGenerator);
    4. }
    5. if (this.resourceLoader != null) {
    6. if (context instanceof GenericApplicationContext) {
    7. ((GenericApplicationContext)context).setResourceLoader(this.resourceLoader);
    8. }
    9. if (context instanceof DefaultResourceLoader) {
    10. ((DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());
    11. }
    12. }
    13. // 这里会获取上边实现*Factory的转换类,放入conversionService字段中
    14. if (this.addConversionService) {
    15. context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
    16. }
    17. }

    这里会有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

    1. public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    2. HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
    3. if (resolver == null) {
    4. throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    5. } else {
    6. // 走这里参数解析
    7. return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    8. }
    9. }

    绑定参数

    this.bindRequestParameters(binder, webRequest);
    1. protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    2. ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
    3. Assert.state(servletRequest != null, "No ServletRequest");
    4. ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
    5. // 下面参数绑定,先从请求中获取参数
    6. servletBinder.bind(servletRequest);
    7. }

    org.springframework.web.bind.ServletRequestDataBinder#bind

    1. public void bind(ServletRequest request) {
    2. // 请求获取参数
    3. MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    4. MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);
    5. if (multipartRequest != null) {
    6. this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
    7. }
    8. this.addBindValues(mpvs, request);
    9. this.doBind(mpvs);
    10. }

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

    org.springframework.web.util.WebUtils#getParametersStartingWith

     

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

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

     设置属性

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

    参数转换

     

  • 相关阅读:
    三剑客之 awk
    App自动化测试持续集成效率提高50%
    04-jQuery动画
    k8s基础命令及Linux上用Kubectl(k8s)部署Nginx
    java 实用的时间日期工具类
    python:xlrd 读取 Excel文件,显示在 tkinterTable 表格中
    【C++上层应用】7. Web编程*
    网络安全等级保护测评
    【秋招面经搬运】字节一面
    【C++】每周一题——2024.3.3
  • 原文地址:https://blog.csdn.net/wangfenglei123456/article/details/128054271