问题发现过程
抄代码抄到这么一段关于spring boot中利用Aspectj来记录日志的功能
@Aspect
@Slf4j
@Component
public class LogAspect {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
private ThreadLocal<String> ip = new ThreadLocal<>();
@Pointcut("within(com.xxx.modules.*.controller.*)")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
startTime.set(System.currentTimeMillis());
// 接收到请求,记录请求内容
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
String ipAddr = IPUtils.getIpAddr(request);
// 记录下请求内容
log.info("IP: {}, URL: {}", ipAddr, request.getRequestURL().toString());
log.info("IP: {}, HTTP_METHOD: {}", ipAddr, request.getMethod());
log.info("IP: {}, CLASS_METHOD: {}", ipAddr, joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("IP: {}, ARGS: {}", ipAddr, Arrays.toString(joinPoint.getArgs()));
ip.set(ipAddr);
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) {
// 处理完请求,返回内容
String ipAddr = ip.get();
log.info("IP: {}, RESPONSE: {}", ipAddr, ret);
log.info("IP: {}, SPEND TIME: {}", ipAddr, System.currentTimeMillis() - startTime.get());
ip.remove();
startTime.remove();
}
}
看到ThreadLocal用法这么简单,想着马上把它拿下,于是搜索相关资料,发现ThreadLocal官网解释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
说是一般用在私有静态变量里。。。黑人问号脸???
那意思是代码里 private ThreadLocal<Long> startTime = new ThreadLocal<>(); 都是花里胡哨的东西?
实际测试
稍加更改
@Aspect
@Slf4j
@Component
public class LogAspect {
private Long startTime;
private String ip ;
@Pointcut("within(com.xxx.modules.*.controller.*)")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
startTime = System.currentTimeMillis();
// 接收到请求,记录请求内容
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
ip = IPUtils.getIpAddr(request);
// 记录下请求内容
log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< START REQUEST {} >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", ip);
log.info("IP: {}, URL: {}", ip, request.getRequestURL().toString());
log.info("IP: {}, HTTP_METHOD: {}", ip, request.getMethod());
log.info("IP: {}, CLASS_METHOD: {}", ip, joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("IP: {}, ARGS: {}", ip, Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) {
// 处理完请求,返回内容
log.info("IP: {}, RESPONSE: {}", ip, ret);
log.info("IP: {}, SPEND TIME: {}", ip, System.currentTimeMillis() - startTime);
}
}
为了结果明显,添加thread.sleep(1000),再不停连续点击某请求后,发现打印出的SPEND TIME一如既往的稳定在1003上下(我不确定这么测试是不是正确方法)。
结论
作为非静态私有成员变量,本身就是对象内的值,没必要动用ThreadLocal来存储。
ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。
如有错误,请帮忙指正。