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("/")+;
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 = ;
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访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:
)JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
)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
springboot-23-aspectj日志记录及threadlocal内存泄漏的更多相关文章
- ThreadLocal内存泄漏
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11421437.html 内存泄漏 内存泄漏是指不再使⽤的对象⽆法得到及时的回收,持续占⽤内存空间,从⽽ ...
- 深入分析 ThreadLocal 内存泄漏问题
前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...
- 分析 ThreadLocal 内存泄漏问题
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用 ThreadLocal,就可能会导 ...
- java多线程--------深入分析 ThreadLocal 内存泄漏问题
前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...
- 18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题
1. 造成内存泄漏的原因? threadLocal是为了解决对象不能被多线程共享访问的问题,通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这 ...
- ThreadLocal内存泄漏真因探究(转)
出处: 链接:https://www.jianshu.com/p/a1cd61fa22da ThreadLocal原理回顾 ThreadLocal的原理:每个Thread内部维护着一个ThreadLo ...
- ThreadLocal 内存泄漏问题深入分析
写在前面 ThreadLocal 基本用法本文就不介绍了,如果有不知道的小伙伴可以先了解一下,本文只研究 ThreadLocal 内存泄漏这一问题. ThreadLocal 会发生内存泄漏吗? 先给出 ...
- ThreadLocal内存泄漏需要注意的
前段时间在网上看到了一篇关于ThreadLocal内存泄漏的文章 于是个人也研究了下ThreadLocal 对象,其原理是: ThreadLocal 定义的变量值 会存储在当前线程的一个map集合中 ...
- SpringBoot使用logback日志记录
在resources里的配置文件: logback-spring.xml <?xml version="1.0" encoding="UTF-8" ?&g ...
随机推荐
- POJ2502乘坐地铁上学
POJ2502 题目大意:给你一个二维地图,起点终点,以及多条地铁线路(只有相邻两站才可互通)中的站点坐标.步行以及坐地铁的速度: 思路:难点在于建图,一个多小时磨磨蹭蹭,都是因为思路没捋清楚~~,对 ...
- Android-控制整个APP的异常收集与处理
控制整个App的异常收集与处理,使用前记得要在Application中初始化initCrasHandler CrasHandler APP异常收集类: package common.library.e ...
- PostgreSQL 用户和权限管理
PostgreSQL 用户和权限管理 创建 CREATE ROLE rolename;CREATE USER username;CREATE USER和CREATE ROLE的区别在于,CREATE ...
- jenkins常用插件汇总
jenkins常用插件汇总: Build-timeout Plugin:任务构建超时插件 Naginator Plugin:任务重试插件 Build User Vars Plugin:用户变量获取插件 ...
- WinForm如何去掉右边和下边的白边
系统给的窗体样式都缺乏美感,想要漂亮的UI只能自己做,很容易实现 1.新建窗体,设置FormBorder为None 这时的窗体就只有一个Panel(Form自带的默认Panel),没有边框,没有标题栏 ...
- [ASP.NET]Net Framework环境问题的一种修复方案
一.情况介绍 造价软件基于.net framework 4.0开发,要成功运行需要在目标电脑上安装4.0版本以上的framework.一般情况下xp是没有的,win7系列自带3.5,都需要手动安装4. ...
- 使用ABP框架踩过的坑系列3
从架构角度来讲,ApplicationService究竟应该如何定位,一种说法是直接对应用例UseCase, 也就是直接对应UI, 这个UI是广义的,不仅仅是浏览器的页面,也包括API调用.还是从我曾 ...
- element ui aside — 侧栏导航菜单移入移出折叠效果
效果如图,移入移出控制折叠,点击按钮后移入移出不可控制折叠. 功能是很简单的功能,不过昨天这块还是弄了小一个小时,所以记录下来. 发现的问题: 模版上el-aside写上@mouseenter无效 解 ...
- leetcode 105 106 从前序与中序遍历序列构造二叉树 从中序与后序遍历序列构造二叉树
题目: 105 根据一棵树的前序遍历与中序遍历构造二叉树. 注意:你可以假设树中没有重复的元素. 例如,给出 前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = ...
- ThinkPHP5.0手把手实现手机阿里云短信验证
阿里云短信服务介绍阿里云短信服务就是以前的阿里大于,不过现在融合得到阿里云平台了.首先,你需要注册一个阿里云账号,这个自行解决. 仅用于测试使用官方送的代金券够用了.相关配置1.开通阿里云Access ...