最近公司的一个小项目进行模块优化,需求是:跟踪记录每个IP请求的源数据和响应数据。当源数据包括requestBody的时候,从HttpServletRequest里面获取requestBody我们采用的是以下代码:
@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
HttpServletRequest request) throws IOException{
Map json =new HashMap();
int size = request.getContentLength();
InputStream is = request.getInputStream();
byte[] reqBodyBytes = StreamUtils.readBytes(is, size);
String res = new String(reqBodyBytes);
System.out.println("requestBody:"+res);
return new ModelAndView(new JsonView(),json);
}
当然也有另外一种更快捷的办法,就是引入一个JAR包apktool.jar,使用以下代码也能获得requestBody:
@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
HttpServletRequest request) throws IOException{
Map json =new HashMap();
String res = IOUtils.toString(request.getInputStream(),"UTF-8");
System.out.println("requestBody:"+res);
return new ModelAndView(new JsonView(),json);
}
现在为了实现跟踪记录的需求,为所有控制器进行AOP拦截:
@Component
@Aspect
public class LogAspect {
protected final Logger log = Logger.getLogger(getClass());
@Around("execution(* demo.web.controller..*.*(..))")
public Object around(ProceedingJoinPoint jp) throws Throwable {
String businessName = jp.getSignature().getName();
String fileName = jp.getSignature().getDeclaringTypeName();
String params = initAssist(jp);
int lastIndex = fileName.lastIndexOf(".");
int cIndex = fileName.lastIndexOf("Controller");
fileName = fileName.substring(lastIndex + 1, cIndex);
String requestLog = "actionLog_request:fileName:" + fileName + "...businessName:" + businessName
+ "...params " + params;
log.info(requestLog);
Object returnValue = null;
try {
returnValue = jp.proceed(jp.getArgs());
if (returnValue != null) {
if (returnValue instanceof ModelAndView) {
JSONObject jsonObj = JSONObject
.fromObject(((ModelAndView) returnValue).getModel());
log.info(requestLog+"||actionLog_return:" + jsonObj.toString());
}
} else {
log.info("return:null");
}
} catch (Exception e) {
// TODO Auto-generated catch block
log.info("abc log err");
e.printStackTrace();
String message = e.getMessage();
// throw e;
}
return returnValue;
}
private String initAssist(JoinPoint jp) {
String result = "";
Class clazz = jp.getTarget().getClass();
String method = jp.getSignature().getName();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(this.getClass()));
CtClass cc;
try {
cc = pool.get(clazz.getName());
CtMethod cm = cc.getDeclaredMethod(method);
Object[] args = jp.getArgs();// 外部传参,不包括内部参数
String[] paramNames = matchParam(cm, cm.getParameterTypes().length);
for (int i = 0; i < args.length; i++) {
if(args[i]!=null){
try {
if(args[i] instanceof String){
result += paramNames[i] + "-->" + args[i].toString() + ",";
}
if(args[i] instanceof HttpServletRequest){
HttpServletRequest req = (HttpServletRequest)args[i];
//获取requestHeader
Map requestHeader = new HashMap();
Enumeration headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames
.nextElement();
String value = req.getHeader(key);
requestHeader.put(key, value);
}
//获取requestBody
String requestBody=IOUtils.toString(req.getInputStream());
//获取requestParameter
Map requestParams = new HashMap();
Enumeration pNames = req.getParameterNames();
while (pNames.hasMoreElements()) {
String key = (String) pNames
.nextElement();
String value = req.getHeader(key);
requestParams.put(key, value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}else{
result += paramNames[i] + "-->" + null + ",";
}
}
} catch (NotFoundException e) {
log.error(clazz.getName(), e);
}
return result;
}
private String[] matchParam(CtMethod cm, Integer arrLength) {
try {
String[] paramNames = new String[arrLength];
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
.getAttribute(LocalVariableAttribute.tag);// 含有内部参数
if (attr == null) {
log.error("");
return null;
}
for (int i = 0, j = 0; i < attr.tableLength(); i++) {
if ("this".equals(attr.variableName(i))) {
continue;
} else {
if (j < paramNames.length) {
paramNames[j] = attr.variableName(i);
j++;
}
}
}
return paramNames;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
}
此时启动服务,请求对应接口调试时候你会发现如下情况:
[demo.web.filter.LogAspect] - requestBody:abc=abc
[demo.web.controller.DemoController] - requestBody:
结论:第二次使用request.getInputStream()是无法获取到任何信息。
为了实现需求,我们使用了springmvc中的注解 @RequestBody ,控制器代码修改为以下所示:
@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
HttpServletRequest request,@RequestBody String requestBodyString) throws IOException{
Map json =new HashMap();
System.out.println("requestBody:"+requestBodyString);
return new ModelAndView(new JsonView(),json);
}
启动服务,请求对应接口调试时候你会发现如下情况:
[demo.web.filter.LogAspect] - requestBody:
[demo.web.controller.DemoController] - requestBody:abc=abc
结论:springmvc已经把request的输入流转换为对应的数据,因此,输入流已经被springmvc读过,程序第二次读流操作必定为空。
现在只需要把日志拦截器拦截requestbody的代码部分换成拦截requestBodyString就可以了。
第一次写博文,有写得不好的地方见谅。