之前写过一篇博客,是通过拦截器的方式对所有http请求进行日志打印,但是这有一个严重的问题,那就是body数据只能从流里取出来一次,虽然通过复制的方式解决了这个问题,却又引入新的问题。
项目进行流读取及流复制的操作是一件非常消耗CPU资源的一件事,当并发数不高的时候服务会很正常,但是并发数高起来之后就需要更多的CPU核数来支持服务运行。正因为上述考虑,随后将日志从拦截器中改为了aop,这样就不存在多次读取流的问题,本质上切面拿的是controller层方法入参,跟servlet就无关了。
具体代码如下:
@Component
@Aspect
@Slf4j
public class LogCollectionAop {
@Resource(name = "headKafkaTemplate")
private KafkaTemplate<String, String> headKafkaTemplate;
@Value("${spring.kafka.header-topic}")
private String headerTopic;
@Pointcut("execution(* com.sohu.mp.appletbackend.controller.*.*(..))")
public void log(){}
@Before("log()")
//只有@Around注解才能配合使用ProceedingJoinPoint,其他方法用JoinPoint。少了proceed()方法
public void doBefore(JoinPoint pjp) throws Throwable {
ServletRequestAttributes servletAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
assert servletAttributes != null;
HttpServletRequest request = servletAttributes.getRequest();
getHeaderLog(request,pjp);
}
private void getHeaderLog(HttpServletRequest request, JoinPoint pjp){
//取参数,打印参数
if (request.getMethod().equals("GET")){
//get请求如果也用下边的方式取参数,会没有参数名称,所以还是从servlet中取
getParam = request.getQueryString();;
}
if (request.getMethod().equals("POST")){
//这么取,取出来的是方法的入参,不仅仅是body里的参数,body只是其中一项
Object[] arguments = pjp.getArgs();
StringBuilder sb = new StringBuilder();
Arrays.stream(arguments).forEach(sb::append);
body = StringUtils.trimAllWhitespace(sb.toString());
}
}
}
当然也能取出来参数名,但是取出来的是你命名的变量名,而不是@RequestParam(value="")中的value。不需要通过反射取参数名,通过下边这种方式即可。
Object[] args = pjp.getArgs();
MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
//获取参数名
Parameter[] ps = methodSignature.getMethod().getParameters();
String[] parameterNames = new String[ps.length];
for (int i=0;i<ps.length;i++){
parameterNames[i] = ps[i].getName();
}
// 通过map封装参数和参数值
HashMap<String, String> paramMap = new HashMap();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], StringUtils.trimAllWhitespace(String.valueOf(args[i])));
}