解决org.springframework.web.method.annotation.MethodArgumentTypeMismat ...

锦通  论坛元老 | 2024-11-17 04:58:59 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1019|帖子 1019|积分 3057

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录
一、问题形貌
二、项目概述
        2.1 依靠
        2.2 项目思路
三、问题分析
        3.1 WebDataBinder问题分析
        3.2 TypeHandler  问题分析
        3.3 HTTPMessageConverter 问题分析
四、解决问题
        4.1 WebDataBinder类型转换
        4.2 TypeHandler类型转换
         4.3 HTTPMessageConverter 类型转换
五、总结


一、问题形貌

       在测试接口的时间遇到以下问题:

        背景报错:
  1. 2024-08-10T01:53:38.750+08:00  WARN 6344 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'com.haishi.test.model.enums.ItemType'; Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam com.haishi.test.model.enums.ItemType] for value '1']
复制代码
        controller层执行的代码:
  1.     @Operation(summary = "[根据类型]查询配套信息列表")
  2.     @GetMapping("list")
  3.     public Result<List<FacilityInfo>> listFacility(@RequestParam(required = false) ItemType type) {
  4.         LambdaQueryWrapper<FacilityInfo> queryWrapper = new LambdaQueryWrapper();
  5.         if (type != null) {
  6.             queryWrapper.eq(FacilityInfo::getType, type);
  7.         }
  8.         List<FacilityInfo> list = facilityInfoService.list(queryWrapper);
  9.         return Result.ok(list);
  10.     }
复制代码
二、项目概述

        2.1 依靠

                常见的SpringBoot+web+mybatis-plus+mysql+lombok+knife4j(测试方便)。
                这里尽量利用mybatis-plus而不是mybatis,详细原因之后会解释。
  1. <!-- 继承Spring Boot父项目 -->
  2.     <parent>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-parent</artifactId>
  5.         <version>3.0.5</version>
  6.     </parent>
  7.     <dependencies>
  8.         <!--包含spring web相关依赖-->
  9.         <dependency>
  10.             <groupId>org.springframework.boot</groupId>
  11.             <artifactId>spring-boot-starter-web</artifactId>
  12.         </dependency>
  13.         <!--包含spring test相关依赖-->
  14.         <dependency>
  15.             <groupId>org.springframework.boot</groupId>
  16.             <artifactId>spring-boot-starter-test</artifactId>
  17.             <scope>test</scope>
  18.         </dependency>
  19.         <dependency>
  20.             <groupId>com.github.xiaoymin</groupId>
  21.             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
  22.             <version>4.1.0</version>
  23.         </dependency>
  24.         <!--mybatis-plus-->
  25.         <dependency>
  26.             <groupId>com.baomidou</groupId>
  27.             <artifactId>mybatis-plus-boot-starter</artifactId>
  28.             <version>3.5.3.1</version>
  29.         </dependency>
  30.         <!--mysql驱动-->
  31.         <dependency>
  32.             <groupId>com.mysql</groupId>
  33.             <artifactId>mysql-connector-j</artifactId>
  34.         </dependency>
  35.         <dependency>
  36.             <groupId>org.projectlombok</groupId>
  37.             <artifactId>lombok</artifactId>
  38.             <version>1.18.26</version>
  39.             <scope>provided</scope>
  40.         </dependency>
  41.         <dependency>
  42.             <groupId>org.projectlombok</groupId>
  43.             <artifactId>lombok</artifactId>
  44.         </dependency>
  45.     </dependencies>
复制代码
        2.2 项目思路

        起首让我们来看下数据库

        可以看到在数据库中,type字段的存储类型是tinyint,用1和2来区分类型(前端接受的数据类型与数据库一致)。数据库设计必要极致地压缩空间,必要接纳tinyint,但是在我们编写后端步伐的时间容易逻辑紊乱,特殊是团队开辟的时间。故此在JAVA步伐中利用enum类进行区分是一种规范的操纵。
        对应的实体类FacilityInfo:
  1. @Schema(description = "配套信息表")
  2. @TableName(value = "facility_info")
  3. @Data
  4. public class FacilityInfo extends BaseEntity {
  5.     private static final long serialVersionUID = 1L;
  6.     @Schema(description = "配套所属对象类型")
  7.     @TableField(value = "type")
  8.     private ItemType type;
  9.     @Schema(description = "名称")
  10.     @TableField(value = "name")
  11.     private String name;
  12.     @Schema(description = "图标")
  13.     @TableField(value = "icon")
  14.     private String icon;
  15. }
复制代码
        先看下所有enum类的接口BaseEnum(后续会解释为什么必要这个接口):
  1. public interface BaseEnum {
  2.     Integer getCode();
  3.     String getName();
  4. }
复制代码
        然后是type字段对应的ItemType枚举类:
  1. public enum ItemType implements BaseEnum {
  2.     APARTMENT(1, "公寓"),
  3.     ROOM(2, "房间");
  4.     private Integer code;
  5.     private String name;
  6.     @Override
  7.     public Integer getCode() {
  8.         return this.code;
  9.     }
  10.     @Override
  11.     public String getName() {
  12.         return name;
  13.     }
  14.     ItemType(Integer code, String name) {
  15.         this.code = code;
  16.         this.name = name;
  17.     }
  18. }
复制代码
        即在前端和数据库中的"1"对应后端的ItemType.APARTMENT,"0"则对应ItemType.ROOM。

三、问题分析

        从异常名称分析这是一个类型转换失败导致的异常,那么起首让我们从头至尾了解一下数据是如安在前端发送至数据库,大概从数据库发送至前端的过程中进行类型转换的。


        从上图中我们可以分析出问题重要可能出在WebDataBinder组件,TypeHandler和HTTPMessageConverter上,接下来让我们逐一分析。

        3.1 WebDataBinder问题分析

                WebDataBinder组件重要的作用是将HTTP的请求参数绑定到Controller方法的参数,并实现参数类型的转换。
                很容易猜到的是,这里的问题是从数值类型到枚举类型的转换发生异常。

                WebDataBinder依靠于Converter进行转换,其中虽然有默认常用转换规则的存在,但很明显在此处发生了异常,背景体系发生的异常也是由它抛出。
                默认转换规则中提供了一个String类型到枚举类型的转换规则,根据实例名称到枚举对象实例进行转换,例如可以将一个"APARTMENT"转换为ItemType.APARTMENT,并不适用于当下这种情况,必要自界说Converter做到图中的功能。
                
        3.2 TypeHandler  问题分析

                Mybatis中的TypeHandler用于处理Java的实体对象与数据库之间的数据类型转换

                与WebDataBinder的默认转换规则相似,不能支持此处场景下的枚举类型和数值类型的转换,必要自界说TypeHandler,否则会发生异常。


        3.3 HTTPMessageConverter 问题分析

     HTTPMessageConverter组件负责将Controller方法的返回值转换为HTTP响应体中的JSON字符串,大概反之。

                默认规则与前两者相同,不支持此种类型转换,必要自界说转换规则。
                但与前两者不同的是,由于本项目中HTTPMessageConverter组件只负责将方法返回值转换为响应体字符串,因此假如没有自界说规则,不会报异常,但是前端接受的数据则为"type":"APARTMENT",必要注意检查。
        

四、解决问题

        从问题分析中我们得知,这里虽然只会出现一个报错信息,但想要枚举类类型可以或许按我们所要求的那样在步伐中正常地进行类型转换,我们必要解决三个问题。
        4.1 WebDataBinder类型转换

                既然SpringMVC中的WebDataBinder组件自带的类型转换器无法处理从数字字符串到ItemType类型的转换,那么我们必要手动添加Converter,告诉WebDataBinder组件如何处理源类型到目的类型的转换。
                起首,我们通过实现Spring框架提供的Converter<S,T>接口来编写一个Converter转换器
  1. @Component
  2. public class StringToItemTypeConverter implements Converter<String, ItemType> {
  3.     @Override
  4.     public ItemType convert(String source) {
  5.         ItemType[] values = ItemType.class.getEnumConstants();
  6.         for (ItemType itemType : values) {
  7.             if (itemType.getCode().equals(Integer.valueOf(source))) {
  8.                 return itemType;
  9.             }
  10.         }
  11.         throw new IllegalArgumentException("code:"+source+"非法");
  12.     }
  13. }
复制代码
               拓展:
                当我们有多个枚举类必要编写Converter转换器,那么一个个地去编写转换器无疑过于繁琐,我们则可以通过编写一个ConverterFactory来一劳永逸的解决。
                我们可以看到,目的类型T必须是传入类型或其父类,因此此处利用前面提到的所有枚举类的接口BaseEnum,就能解决所有枚举类的类型Converter转换问题了。
  1. @Component
  2. public class StringToBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
  3.     @Override
  4.     public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
  5.         return new Converter<String, T>() {
  6.             @Override
  7.             public T convert(String source) {
  8.                 T[] values = targetType.getEnumConstants();
  9.                 for (T t : values) {
  10.                     if (t.getCode().equals(Integer.valueOf(source))) {
  11.                         return t;
  12.                     }
  13.                 }
  14.                 throw new IllegalArgumentException("非法的枚举值:"+source);
  15.             }
  16.         };
  17.     }
  18. }
复制代码
        然后向通过Configuration向WebDataBinder组件注册该Converter或ConverterFactory即可
  1. @Configuration
  2. public class WebMvcConfiguration implements WebMvcConfigurer {
  3.     @Autowired
  4.     private StringToBaseEnumConverterFactory factory;
  5.     @Autowired
  6.     private StringToItemTypeConverter converter;
  7.     @Override
  8.     public void addFormatters(FormatterRegistry registry) {
  9. //        registry.addConverter(this.converter);
  10.         registry.addConverterFactory(this.factory);
  11.     }
  12. }
复制代码
        4.2 TypeHandler类型转换

                与WebDataBinder雷同,TypeHandler必要自界说如何进行类型转换。
                假如利用的是mybatis,那么我们必须要手动编写一个TypeHandler类。不过mybatis-plus则提供了一个通用的处理枚举类型的TypeHandler,只必要在ItemType中添加一个@EnumValue注解,mybatis-plus就可以主动完成从ItemType类型到code属性的相互映射。
  1.     @EnumValue
  2.     private Integer code;
复制代码
         4.3 HTTPMessageConverter 类型转换

                与TypeHandler相同,其提供了一个注解@JsonValue,利用该注解Jackson就可以主动完成从ItemType类型到code属性的相互映射。官方文档
  1.     @EnumValue
  2.     @JsonValue
  3.     private Integer code;
复制代码

五、总结

        MVC框架自带的类型转换功能有时间并不能满足我们的需求,这时间我们就必要手动添加响应的类型转换功能。虽然问题有时间看起来会无从动手,但冷静下来细致分析,渐渐排查可能的原因,就能找到正确的解决办法。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

锦通

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表