项目中使用Spring Boot AOP做日志记录。当接口使用@RequestParam接收参数时,可以通过request.getParameterMap()来获得全部Parameter参数;
而当接口使用@RequestBody接收参数时,用同样的方法获取参数会出现流已关闭的异常,这是因为Spring已经对@RequestBody提前进行处理,而HttpServletReqeust获取输入流时仅允许读取一次,所以会报java.io.IOException: Stream closed。
解决思路:重新构建ServletRequest,读取输入流后进行缓存,然后重写进流里面,使请求输入流支持二次读取;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RepeatedlyReadRequestWrapper(HttpServletRequest request)
throws IOException {
super(request);
body = HttpHelper.readBytes(request.getReader(), "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 boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/**", filterName = "ReqeustBodyFilter ")
public class ReqeustBodyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String requestUri = URLUtil.getPath(req.getRequestURI());
String method = req.getMethod();
String contentType = request.getContentType();
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest
&& !MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)// 文件上传时不可以过滤器包装request,会报错Required request part 'file' is not present
) {
if (HttpHelper.readBytes(request.getReader(), "utf-8") != null) {
requestWrapper = new RepeatedlyReadRequestWrapper((HttpServletRequest) request);
}
}
if (null == requestWrapper) {
// 过滤器包装request不需要,将返回原来的request
chain.doFilter(request, response);
} else {
// 过滤器包装request成功
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
LOGGER.info("----------初始化结束-------------");
}
}
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import java.io.*;
import java.nio.charset.Charset;
public class HttpHelper {
public static 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();
}
/**
* Description: 复制输入流
*
* @param inputStream
*
* @return
*/
public static 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;
}
/**
* 通过BufferedReader和字符编码集转换成byte数组
* @param br
* @param encoding
* @return
* @throws IOException
*/
public static byte[] readBytes(BufferedReader br, String encoding) throws IOException{
String str = null,retStr="";
while ((str = br.readLine()) != null) {
retStr += str;
}
if (org.apache.commons.lang3.StringUtils.isNotBlank(retStr)) {
return retStr.getBytes(Charset.forName(encoding));
}
return null;
}
}
@Aspect
@Component
public class OperLogAspect {
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
* <功能详细描述>
*
* @see [类、类#方法、类#成员]
*/
@Pointcut("@annotation(com.bw.dsm.config.aop.OperLog)")
public void operLogPoinCut() {
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
* <功能详细描述>
*
* @see [类、类#方法、类#成员]
*/
@Pointcut("execution(* com.bw.dsm.controller..*.*(..))")
public void operExceptionLogPoinCut() {
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
* <功能详细描述>
*
* @param joinPoint,切片点
* @param keys,返回值
* @see [类、类#方法、类#成员]
*/
@AfterReturning(value = "operLogPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
/ 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
if (request != null) {
String params = HttpHelper.getBodyString(request);
// ....
}
}
}
}