springboot-23-aspectj日志记录及threadlocal内存泄漏
对于请求参数的处理和响应, 如果在代码中体现日志会显得很繁琐, 普遍的解决方案是使用spring的切面方案去解决.
这儿使用的是springboot的切面: http://www.cnblogs.com/wenbronk/p/6848984.html
最开始的aspectj切面解决:
package com.iwhere.easy.travel.aspect; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.Enumeration; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.alibaba.fastjson.JSONObject; @Aspect @Component public class ControllerAspect { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); private String name = "easy-travel-server"; @Pointcut("execution(public * com.wenbronk.controller.*.*(..))") public void controllerLog(){} @Pointcut("execution(public * com.wenbronk.service.*.*(..))") public void serviceLog(){} private ThreadLocal<Long> startTime = new ThreadLocal<>(); private ThreadLocal<String> requestId = new ThreadLocal<>(); private ThreadLocal<String> interfaceName = new ThreadLocal<>(); private ThreadLocal<String> param = new ThreadLocal<>(); private SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); @Before("controllerLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 设置请求开始时间 startTime.set(System.currentTimeMillis()); Date stTimeDate = new Date(startTime.get()); String dateStr = dataFormat.format(stTimeDate); // 设置请求标识 String requestIdStr = UUID.randomUUID().toString(); requestId.set(requestIdStr); // 提取全部参数 paramJson Enumeration<String> paramNames = request.getParameterNames(); JSONObject paramJson = new JSONObject(); while(paramNames.hasMoreElements()){ String paramName = paramNames.nextElement(); paramJson.put(paramName, request.getParameter(paramName)); } // 提取接口标识(url中截取) String requestUrl = request.getRequestURL().toString(); int start = requestUrl.lastIndexOf("/")+1; String interfaceNameStr = null; if (requestUrl.contains("?")){ interfaceNameStr = requestUrl.substring(start, requestUrl.indexOf("?")); } else { interfaceNameStr = requestUrl.substring(start); } param.set(paramJson.toJSONString()); interfaceName.set(interfaceNameStr); // 将requst的唯一标识放置在request中,在其他环节可以穿起来 request.setAttribute("requestId", requestId.get()); } @AfterReturning(returning="rvt",pointcut="controllerLog()") public void doAfterReturning(JoinPoint joinPoint,Object rvt) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); logger.info("finished" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " " + request.getRequestURL().toString() + " " + param.get() + (System.currentTimeMillis() - startTime.get()) + " " + rvt.toString()); } @AfterThrowing(throwing="ex", pointcut="controllerLog()") public void doAfterThrowing(JoinPoint joinPoint, Throwable ex) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 发生地点 int lineNum = 0; String className = null; String methodName = null; StackTraceElement[] st = ex.getStackTrace(); for (StackTraceElement stackTraceElement : st) { lineNum = stackTraceElement.getLineNumber(); className = stackTraceElement.getClassName(); methodName = stackTraceElement.getMethodName(); System.out.println("[类:" + className + "]调用" + methodName + "时在第" + lineNum + "行代码处发生异常!异常类型:" + ex.getClass().getName()); break; } String exceptionMessage = "[类:" + className + "]调用"+ methodName + "时在第" + lineNum + "行代码处发生异常!异常类型:" + ex.getClass().getName(); logger.info("exception" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " " + request.getRequestURL().toString() + " " + param.get() + " " + exceptionMessage); } }
可见这个里面有一个before和after, 然后还有一个异常处理的方法
附: joinpoint的简要api
AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法: 1)JoinPoint java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; Signature getSignature() :获取连接点的方法签名对象; java.lang.Object getTarget() :获取连接点所在的目标对象; java.lang.Object getThis() :获取代理对象本身; 2)ProceedingJoinPoint ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
偶然间看到这个博客
http://blog.csdn.net/lhqj1992/article/details/52451136 https://my.oschina.net/xpbug/blog/113444 https://segmentfault.com/a/1190000000537475
由于此项目采用的是线程池, 所以可能存在内存一直上涨, 一直到线程池max之后达到一个稳定态, 也就发生了我们认为的内存泄漏
之后改成这个方法:
package com.iwhere.scrapy.aspect; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.alibaba.fastjson.JSON; /** * 日志记录 * @author wenbronk * @Date 上午9:33:47 */ @Aspect @Configuration public class LogAspect { private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class); // 定义切点 Pointcut @Pointcut("execution(* com.iwhere.scrapy.controller.*Controller.*(..))") public void excudeService() {} @Around("excudeService()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Long startTime = System.currentTimeMillis(); RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); String url = request.getRequestURL().toString(); String method = request.getMethod(); String uri = request.getRequestURI(); String queryString = request.getQueryString(); // Object target = pjp.getTarget(); // String name = target.getClass().getName(); Signature signature = pjp.getSignature(); String className = signature.getDeclaringTypeName(); String methodName = signature.getName(); LOGGER.info("请求开始, {}#{}() URI: {}, method: {}, URL: {}, params: {}",className, methodName, uri, method, url, queryString); // result的值就是被拦截方法的返回值 Object result = pjp.proceed(); Long endTime = System.currentTimeMillis(); LOGGER.info("请求结束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}, result: {} ", className, methodName, uri, method, url, (endTime - startTime), JSON.toJSONString(result)); return result; } // @AfterThrowing(throwing="ex", pointcut="excudeService()") // public String doAfterThrowing(JoinPoint joinPoint, Throwable ex) throws Throwable { // // 接收到请求,记录请求内容 // ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // HttpServletRequest request = attributes.getRequest(); // // 发生地点 // int lineNum = 0; // String className = null; // String methodName = null; // StackTraceElement[] st = ex.getStackTrace(); // if (ArrayUtils.isNotEmpty(st)) { // lineNum = st[0].getLineNumber(); // className = st[0].getClassName(); // methodName = st[0].getMethodName(); // } // LOGGER.info("Exception: {}#{}() 在第{}行发生{}异常!!!", className, methodName, lineNum, ex.getClass().getName()); // return "exception"; // } }
在里面处理异常, 还是会抛出, 所以单独出一个异常处理
然后还需要加入一个全局异常处理框架:
http://www.cnblogs.com/wenbronk/p/6850785.html
具体效果等待进一步测试
推荐一个好的博客, 关于aspect的 : http://blog.csdn.net/lemon1003657090/article/details/52431584