@PostMapping("/test")
public String test(String token, HttpServletRequest request) {
logger.info("token:" + token);
return "token:" + token;
}
postman发送请求如下
猜想问题可能产生的原因:
spring.servlet.multipart.enabled=true
其实上面检查某些配置的步骤就很接近答案了,最后是这样确定问题产生原因的:将项目的所在模块copy一份,去掉所有拦截器等配置等,只留着启动类和controller,pom中的依赖只留下springboot核心的依赖,删除一切特殊配置如启动类下的某些bean,发现controller能接收到表单提交数据,重新加上启动类的bean后,发现问题复现,最终确定问题为启动类下添加的bean有问题,如下:
@Bean
public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
ServletRegistrationBean<CXFServlet> servletRegistrationBean = new ServletRegistrationBean<>(new CXFServlet(), "/services/*");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
@Bean
public ServletRegistrationBean restServlet(){
//注解扫描上下文
AnnotationConfigWebApplicationContext applicationContext
= new AnnotationConfigWebApplicationContext();
//base package
applicationContext.scan("com.*");
//通过构造函数指定dispatcherServlet的上下文
DispatcherServlet rest_dispatcherServlet
= new DispatcherServlet(applicationContext);
//用ServletRegistrationBean包装servlet
ServletRegistrationBean registrationBean
= new ServletRegistrationBean(rest_dispatcherServlet);
registrationBean.setLoadOnStartup(1);
//指定urlmapping
registrationBean.addUrlMappings("/*");
//指定name,如果不指定默认为dispatcherServlet
registrationBean.setName("rest");
return registrationBean;
}
此处第一个bean注册了一个servlet,名称为dispatcherServlet,从代码可以看出,这个bean目的是给webservice服务用来分发请求的。坏就坏在这里,这个dispatcherServlet与springboot本身的dispatcherServlet重名了!!!springboot本身的dispatcherServlet是用来分发http请求到controller层的。
我们可以合理猜测,原开发者是想将项目用作了webservice服务端,但这样一来却覆盖了springboot中dispatcherServlet,发现controller层请求异常后,下面又注册了一个bean试图解决controller的请求异常问题,但这忽略了我们遇到的问题:虽然controller层接口能请求到,却接收不到表单提交的数据了。
知其然,还要知其所以然。下面还要分析下两者的差异,为什么自己创建的dispatcherServlet会获取不到表单的属性???
查看调用链
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
requestToUse = new HttpMethodRequestWrapper(request, paramValue);
}
}
filterChain.doFilter(requestToUse, response);
}
其中getParameter方法,会调用ApplicationHttpRequest中的getParameter方法
@Override
public String getParameter(String name) {
parseParameters();
String[] value = parameters.get(name);
if (value == null) {
return null;
}
return value[0];
}
其中的parseParameters()方法中的逻辑,则是导致差异的根本原因在处理表单提交时,会进行parseParts操作
protected void parseParameters() {
parametersParsed = true;
....
if ("multipart/form-data".equals(contentType)) {
parseParts(false);
success = true;
return;
}
....
parseParts方法如下:
具体来说,MultipartConfigElement提供了以下配置信息:
getLocation():指定存储上传文件的临时位置。
getMaxFileSize():指定单个上传文件的最大大小(以字节为单位)。
getMaxRequestSize():指定整个请求的最大大小(包括所有上传的文件)(以字节为单位)。
getFileSizeThreshold():指定在内存中存储文件数据的大小阈值。如果上传的文件大小小于这个阈值,那么文件数据会被存储在内存中,否则会被存储在磁盘上。
当Tomcat处理一个multipart/form-data类型的请求时(如文件上传),它会检查MultipartConfigElement中的配置信息来决定如何处理这个请求。例如,如果上传的文件大小超过了getMaxFileSize()或getMaxRequestSize()中指定的值,Tomcat会抛出一个异常来拒绝这个请求。
故此我们需要在创建servlet时,就需要配置MultipartConfigElement这个类
@Bean
public ServletRegistrationBean restServlet(){
//注解扫描上下文
AnnotationConfigWebApplicationContext applicationContext
= new AnnotationConfigWebApplicationContext();
//base package
applicationContext.scan("com.*");
//通过构造函数指定dispatcherServlet的上下文
DispatcherServlet rest_dispatcherServlet
= new DispatcherServlet(applicationContext);
//用ServletRegistrationBean包装servlet
ServletRegistrationBean registrationBean
= new ServletRegistrationBean(rest_dispatcherServlet);
registrationBean.setLoadOnStartup(1);
//指定url_mapping
registrationBean.addUrlMappings("/*");
//指定name,如果不指定默认为dispatcherServlet
registrationBean.setName("rest");
// 配置MultipartConfigElement
MultipartConfigElement multipartConfigElement = new MultipartConfigElement(
"D:\\temp", // 文件存储的临时位置
2097152L, // 单个文件最大大小,单位字节,这里是2MB
4194304L, // 请求的最大大小,单位字节,这里是4MB
10240 // 文件大小阈值,超过这个值会存储在磁盘上,单位字节,这里是10KB
);
registrationBean.setMultipartConfig(multipartConfigElement);
return registrationBean;
}
最后,调用postman发现,成功在controller层获取到了表单中的属性值!!!
这个问题应该在一个月前就遇到了,当时没有找到根本原因,但心里会一直想着,今天总算找到原因了。
所以,能用spring默认的就用默认的配置,自己配置可能根本不知道会漏了哪些东西,导致一些小问题的产生。其实根本原因就是复制粘贴代码时,需要考虑到对项目产生的影响。
如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可