最近公司的一个小项目进行模块优化,需求是:跟踪记录每个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就可以了。



第一次写博文,有写得不好的地方见谅。