目录
- 1 问题
- 2 解决
- 2.1 解决方法一
- 2.2 解决方法二
1 问题
代码语言:javascript复制如果使用原生的 HttpServletRequest ,只能读取一次,
如果想要二次读取就会报错。 因此需要能够重复读取
InputStream 的方法。
springboot项目 HttpServletRequest
getRequest().getInputStream()或getReader()
只能读取一次read closed
我在项目中使用controller中方法进行接收数据的时候需要加上
@RequestBody注解接收这种数据,使用POJO对象进行接收,
但是我的项目需要为这个方法增加LogAspect的功能,
这就要求我需要获取到RequestBody中的数据,
但是一个request数据只能读取一次,在方法内部已经读取过了,不能在LogAspect中再次读取
(试过Filter的方法,但是会对原项目中的Filter有冲突)
2 解决
2.1 解决方法一
在Controller的方法中增加参数HttpServletRequest request,使用request.setAttribute()将对象重新放入到request中,在Aspect中使用getAttribute()进行获取就可以避免getInputStream()这个方法出bug了
这种方法很简单,就是使用了之后再赋值回去; 就是从request拿出东西使用之后,再将拿出来的东西放到request里面
2.2 解决方法二
使用过滤器,重写HttpServletRequest ,里面增加缓冲,记录已读取的内容。
具体实现方法是
1 写一个过滤器类
里面的代码是
代码语言:javascript复制package com.ruoyi.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;
/**
* Repeatable 过滤器
* 构建可重复读取inputStream的request
* @author jing
*/
public class RepeatableFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
ServletRequest requestWrapper = null;
// APPLICATION_JSON_VALUE = "application/json";
if (request instanceof HttpServletRequest
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
{
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper)
{
chain.doFilter(request, response);
}
else
{
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy()
{
}
}
在这个过滤器里面就会判断,看request请求是不是json,如果是,就将里面的东西进行缓存,不是就直接放行
缓存的逻辑是重新写在另一个文件里面
代码语言:javascript复制package com.ruoyi.common.filter;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
/**
* 构建可重复读取inputStream的request
* 如果使用原生的 HttpServletRequest ,
* 只能读取一次,如果想要二次读取就会报错。 因此需要能够重复读取 InputStream 的方法。
* request的inputStream只能被读取一次,
* 多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容。
* @author ruoyi
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
{
// 将request 里面的东西 缓存到这个数组里面
private final byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
{
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
body = HttpHelper.getBodyString(request).getBytes("UTF-8");
}
@Override
public BufferedReader getReader() throws IOException
{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException
{
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream()
{
@Override
public int read() throws IOException
{
return bais.read();
}
@Override
public int available() throws IOException
{
return body.length;
}
@Override
public boolean isFinished()
{
return false;
}
@Override
public boolean isReady()
{
return false;
}
@Override
public void setReadListener(ReadListener readListener)
{
}
};
}
}
第二步,写一个过滤器的配置类就可以了
代码语言:javascript复制package com.ruoyi.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.annotation.WebFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
/**
* Filter配置 过滤器配置
*
* @author jing
*/
@Configuration
public class FilterConfig
{
// # 排除链接(多个用逗号分隔)
@Value("${xss.excludes}")
private String excludes;
// # 匹配链接 也就是这个里面的,要防止xss攻击
@Value("${xss.urlPatterns}")
private String urlPatterns;
// xss的过滤器的 注册
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
// 通过其两个属性name以及havingValue来实现的,
// 其中name用来从application.properties中读取某个属性值。
// 如果该值为空,则返回false;
// 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
// 如果返回值为false,则该configuration不生效;为true则生效。
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration()
{
// 创建过滤器的 注册器
FilterRegistrationBean registration = new FilterRegistrationBean();
// 设置过滤器的URL模式 REQUEST:默认值。浏览器直接请求资源
// 浏览器直接请求 资源时会执行过滤
// * 注解配置:
// * 设置dispatcherTypes属性,取值如下:
// 1. REQUEST:默认值。浏览器直接请求资源
// @WebFilter(value="/index.jsp",dispatcherTypes = DispatcherType.REQUEST)//浏览器直接请求index.jsp资源时会执行过滤
// 2. FORWARD:转发访问资源
// @WebFilter(value = "/index.jsp",dispatcherTypes = DispatcherType.FORWARD)//在发生转发请求时,如果转发请求的资源时index.jsp则执行过滤
// 3. INCLUDE:包含访问资源
// 4. ERROR:错误跳转资源
// 5. ASYNC:异步访问资源
// 注意:上述的取值可以同时存在多个,比如:
// @WebFilter(value = "/index.jsp",dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})
//
// * web.xml配置
// * 设置<dispatcher></dispatcher>标签即可
registration.setDispatcherTypes(DispatcherType.REQUEST);//浏览器直接请求 资源时会执行过滤
registration.setFilter(new XssFilter());// 设置具体的过滤器
// 匹配链接 也就是这个里面的,要防止xss攻击
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
registration.setName("xssFilter");
// FilterRegistrationBean.HIGHEST_PRECEDENCE 表这个过滤器在众多过滤器中级别最高,也就是过滤的时候最先执行
// 设置优先过滤的级别,越小越优先。
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<String, String>();
// # 排除链接(多个用逗号分隔)
// 这个key 是随便写的,但是具体的过滤器里面 拿的时候也要对应的MC
initParameters.put("excludes", excludes); //设置初始化的一些参数
registration.setInitParameters(initParameters);
return registration;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}
只需要上面的3个文件,你原封不动的放到你的项目里面,就可以实现构建可重复读取inputStream的request了;