新做的一个功能,一个中间服务项目,需要A公司调用服务去B公司拿数据回去,流转起来太繁杂所以公司想把服务做成一个项目,把请求的甲方数据存起来备份以便不时之需.
所以就需要在请求的时候利用切面做一个日志的输出顺便保存到库里,而在取请求参数的时候就出问题了,相同的取参数再请求数据的时候没事,我加了切面之后在切面里请求也没事,两个一起的时候,报空指针了.
后来发现是使用request.getInputStream()的锅,先在切面调用一次之后,到了controller时,对象属性都为空,但是在实际的开发中往往需要多次读取。这样就需要我们将流写入进去,提供后续使用。
1:由于inputStream只能被读取一次,这时需要将流中数据存储起来,以便后续使用,需要继承HttpServletRequestWrapper类。
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* Request请求参数获取处理类
*/
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*
* @param request
* @return
*/
private String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(request.getInputStream());
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
public String getBodyString() {
return new String(body, Charset.forName("UTF-8"));
}
/**
* Description: 复制输入流
*
* @param inputStream
* @return
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}
@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 boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
2.过滤器(springboot项目,没有新建一个)
@ServletComponentScan
@WebFilter(urlPatterns = "/attend/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("-----------------------Execute filter start---------------------");
// 防止流读取一次后就没有了, 所以需要将流继续写出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
}
注意:@WebFilter(urlPatterns = "/attend/*",filterName = "channelFilter")
第一个参数是需要修改成为你需要拦截的url路径,filterName是当前filter名称
3.启动类注册一下(配一下过滤器的路径)
4.最后切面类里直接拿参数
PS:可能有的人拿到的返回数据为空的,我重写的BodyReaderHttpServletRequestWrapper类里面有两个重载方法,一个是私有的,请求的时候调用public方法,这样就可以拿到数据.
5.这里面最大的坑:如果你都写好了还报空指针.去看一下Filter里@WebFilter(urlPatterns = "/attend/*",filterName = "channelFilter")
这个注解拦截的路径对不对.基本上是没拦住没有重新塞request造成的.
完毕 OVER ! ! !