场景描述:
需求:前端发送请求为POST
,请求的context-type
强制为"application/x-www-form-urlencoded"
传输的数据包含userName
,deptName
,groupNanme
三个参数,需要在Filter
内将三个参数的值合并起来为一个新的参数fullName
,然后将该参数加入到request请求传输的数据内,传输给controller
方法
出现的情况:
无论是在filter
还是在controller
内调用request.getInputStream()
,发现获得的InputStream
流内的数据都是null
,这个让我一时间十分懵逼,于是我参考网上资料和博客。
资料:
1.request的inputStream是只能被读取一次,当读取一次后,之后再次读取request的inputStream的话那就只能读取为空。
2.inputStream只能被读取一次的原因是,该类内部定义了一个指针,该指针指向当前读取内容的位置,当第一次读取完毕后,该指针指向数据末尾,所以之后再次读取的话就只能读取为null
3.当请求以表单格式传输数据的时候,数据初始都是保存在inputStream内的
4.当第一次调用request.getParameter()或使用注解@RequestParameter时,会从ParameterMap内获取值,ParameterMap则是SpringMVC从request的inputStream内获取数据创建的(所以当ParameterMap创建的时候就代表request的inputStream已经被读取一次了)
5.request.getParameter()和使用注解@RequestParameter都是从ParameterMap内获取值
6.request.getParameter()是调用getParameter方法从ParameterMap内获取值;注解@RequestParameter是调用getParameterValue方法从ParameterMap内获取值的
7.@RequestBody获取的是请求内body的内容,适用于请求context-type为json的情况,不适用表单上传的情况
8.@RequestParameter获取的是ParameterMap内的数据,适用于context-type为application/x-www-form-urlencoded(表单)的情况
解决过程:
HttpServletRequestWrapper
类的wrapper
类,该类可以作为request
类的包装类,在该类内部将request的inputStream缓存
,然后重写getInputStream
方法,外部调用该方法的时候返回的是缓存的数据而不是父类默认的数据,在filter
放行的时候代替request
请求传输下去。我之后采纳了该方案,但是结果却差强人意,还是获取不到数据,但是理论上应该是获取得到数据才对的,于是我就打上了断点调试程序对request
进行监控。结果我发现当我request
请求进入filter
的时候他的inputStream
的数据就已经为空了,这个时候我又测试发现了ParameterMap
在request
加入filter
的时候就已经有数据了
1)该方案会使request的inputStream可以重复读取
2)该方案内部定义了一个byte[] 数组缓存inputStream内的数据,重写getInputStream方法默认返回byte[]数组的数据
request.getInputStream()
和使用注解@RequestParameter
,ParameterMap
还是会创建读取InputStream
。但是我也算是了解该问题出现的核心问题:request的inputStream的值提前被读取到ParameterMap内,那么我直接从ParameterMap内获取值不就行了,在读取值之后将值缓存到wrapper类的byte[]数组就ok了wrapper
类
private final Map allParameters = new TreeMap<>()
来代替原先的ParameterMap
ParameterMap
从而给内部定义的allParameters
赋值getParameter
,getParameterMap
,getParameterNames
,getParameterValues
几个方法(因为我使用自定义的map
来代替原生的ParameterMap
所以为了之后能正常获取map
内的值,需要重写这些相关的方法)程序代码:
myFilter:
@Slf4j
public class myFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
EnhancedHttpRequest wrapper;
// 注意: 原生parameterMap是添加了final关键字的的,无法对其本身的数据进行更改
Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
//该map用来复制承接parameterMap的数据
Map<String, String[]> parameterMap2 = new HashMap<>();
parameterMap2.putAll(parameterMap);
// fullName = userName+deptName+groupName
Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
StringBuilder builder = new StringBuilder();
while (parameterNames.hasMoreElements()){
String s = parameterNames.nextElement();
String parameterValue = httpServletRequest.getParameter(s);
builder.append(parameterValue);
}
// 将fullName加入到map内
parameterMap2.put("fullName",new String[]{builder.toString()});
// 创建wrapper,将修改过的map传入构造函数,缓存map和inputStream
wrapper = new EnhancedHttpRequest(httpServletRequest,parameterMap2);
// 注意:放行的是wrapper而不是request了
filterChain.doFilter(wrapper,httpServletResponse);
}
}
EnhancedHttpRequest:
@Slf4j
// 自定义一个HttpServletRequest的包装类,在内部实现对request的body内容的特性化处理
public class EnhancedHttpRequest extends HttpServletRequestWrapper
{
// 自定义ParameterMap来代替默认的map
private final Map<String, String[]> allParameters = new TreeMap<>();
// 用于缓存inputStream流的数据 然后重写getInputStream方法,返回该body的数据
private byte[] body;
public EnhancedHttpRequest(HttpServletRequest request,Map<String, String[]> map){
super(request);
allParameters.putAll(map);
// 将map内数据缓存到InputStream内
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, String[]> stringEntry : allParameters.entrySet()) {
String key = stringEntry.getKey();
for(String value:stringEntry.getValue()){
builder.append(key+"="+value+"&");
}
}
String temp = builder.toString();
body = temp.substring(0,temp.length()-1).getBytes();
}
// 重写getInputStream 设置返回的是wrapper类内部缓存的body的数据
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
// ****
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
// 根据key获取参数值
@Override
public String getParameter(final String name)
{
return getParameterMap().containsKey(name)?getParameterMap().get(name)[0]:null;
}
// 返回一个不可修改的ParameterMap集合
@Override
public Map<String, String[]> getParameterMap()
{
return Collections.unmodifiableMap(allParameters);
}
@Override
public Enumeration<String> getParameterNames()
{
return Collections.enumeration(getParameterMap().keySet());
}
@Override
public String[] getParameterValues(final String name)
{
return getParameterMap().get(name);
}
}
controller方法:
@PostMapping(value = "/test/{userId}/{deptId}",consumes = {"application/x-www-form-urlencoded"})
public myResult testMethod(
@PathVariable Long userId,
@PathVariable Long deptId,
@RequestParam String fullName,HttpServletRequest request) throws IOException {
log.info("token:{}",request.getAttribute("token"));
log.info("userId:{},deptId:{},fullName:{}",userId,deptId,fullName);
byte[] b = new byte[1024];
request.getInputStream().read(b);
return new myResult().ok(fullName);
}