Spring Boot 2.7中@RequestParam接收自定义LinkedHashMap子类报类型不匹配
问题:Spring Boot中自定义LinkedHashMap子类作为@RequestParam参数报错
原正常运行代码
@RestController @RequestMapping("/system/test") public class SystemUserController { @GetMapping public ResponseResult index(@RequestParam LinkedHashMap<String, Object> params) { return ResponseResult.success(); } }
自定义子类后报错
封装LinkedHashMap为自定义子类MyLinkedHashMap后,调用接口会触发参数类型不匹配错误:
public class MyLinkedHashMap extends LinkedHashMap<String, Object> {} @RestController @RequestMapping("/system/test") public class SystemUserController { @GetMapping public ResponseResult index(@RequestParam MyLinkedHashMap params) { return ResponseResult.success(); } }
使用环境为Spring Boot 2.7版本,相关问题搜索未获有效解决方案。
报错信息
Controller [com.example.test.TestController] Method [public java.lang.String com.example.test.TestController.index(com.example.test.MyLinkedHashMap)] with argument values: [0] [type=java.util.LinkedHashMap] [value={}] ] with root cause java.lang.IllegalArgumentException: argument type mismatch at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.26.jar:5.3.26] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.26.jar:5.3.26] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.26.jar:5.3.26] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.26.jar:5.3.26] at javax.servlet.http.HttpServlet.service(HttpServlet.java:502) ~[tomcat-embed-core-9.0.73.jar:4.0.FR] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.26.jar:5.3.26] at javax.servlet.http.HttpServlet.service(HttpServlet.java:596) ~[tomcat-embed-core-9.0.73.jar:4.0.FR] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.26.jar:5.3.26] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.26.jar:5.3.26] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.26.jar:5.3.26] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.26.jar:5.3.26] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.26.jar:5.3.26] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.26.jar:5.3.26] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:492) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.73.jar:9.0.73] at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
自定义类功能说明
MyLinkedHashMap包含通用键读取和固定键读取方法,代码如下:
public class MyLinkedHashMap extends LinkedHashMap<String, Object> { public int getInt(String key, int defaultValue) { Object value = get(key); // if(valueIsNull(value)) { // return defaultValue; // } return Integer.valueOf(String.valueOf(value)); } public int getKeyPageNo() { int pageNo = getInt("pageNo", 1); if (pageNo <= 0) { pageNo = 1; } return pageNo; } public int getKeyPageSize() { int pageSize = getInt("pageSize", 10); if (pageSize <= 0 || pageSize > 1000) { pageSize = 10; } return pageSize; } // like... any key public String getKey(String key) { return get(key).toString(); } }
解决方案
原因分析
Spring处理@RequestParam的Map类型参数时,默认实例化LinkedHashMap,但不会自动将其转换为自定义子类。反射调用方法时参数类型需严格匹配,因此触发类型不匹配异常。
解决方法1:自定义转换器
让Spring能够将请求参数转换为自定义Map子类:
- 确保
MyLinkedHashMap存在无参构造器(父类已默认提供,若有自定义构造器需显式声明)。 - 创建转换器类:
import org.springframework.core.convert.converter.Converter; import org.springframework.util.MultiValueMap; public class MyLinkedHashMapConverter implements Converter<MultiValueMap<String, String>, MyLinkedHashMap> { @Override public MyLinkedHashMap convert(MultiValueMap<String, String> source) { MyLinkedHashMap map = new MyLinkedHashMap(); source.forEach((key, values) -> { if (values.size() == 1) { map.put(key, values.get(0)); } else { map.put(key, values); } }); return map; } }
- 注册转换器到Spring容器:
import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new MyLinkedHashMapConverter()); } }
解决方法2:使用组合而非继承(推荐)
避免继承LinkedHashMap,改用组合方式封装,更符合面向对象设计原则:
public class RequestParamsWrapper { private final LinkedHashMap<String, Object> params; public RequestParamsWrapper(LinkedHashMap<String, Object> params) { this.params = params; } public int getInt(String key, int defaultValue) { Object value = params.get(key); if (value == null) { return defaultValue; } try { return Integer.parseInt(value.toString()); } catch (NumberFormatException e) { return defaultValue; } } public int getKeyPageNo() { int pageNo = getInt("pageNo", 1); return pageNo <= 0 ? 1 : pageNo; } public int getKeyPageSize() { int pageSize = getInt("pageSize", 10); if (pageSize <= 0 || pageSize > 1000) { return 10; } return pageSize; } public String getKey(String key) { Object value = params.get(key); return value != null ? value.toString() : null; } }
控制器中使用方式:
@RestController @RequestMapping("/system/test") public class SystemUserController { @GetMapping public ResponseResult index(@RequestParam LinkedHashMap<String, Object> params) { RequestParamsWrapper wrapper = new RequestParamsWrapper(params); // 调用wrapper的方法,如wrapper.getKeyPageNo() return ResponseResult.success(); } }
内容的提问来源于stack exchange,提问作者ClearSeve
相关产品推荐
相关产品推荐

