工厂类中使用ThreadLocal的陷阱
1. 背景
由于EDI已有的日志结构比较混乱,多个人都写了自己的LoggerHelper工具类。近期的工作主要是写一个新的日志框架,通过SPI方式加载Appender的实现,并替换掉之前的日志内容。
2. 初始实现LoggerFactory
在实现日志框架时,我写了一个LoggerFactory,代码如下:
1 | public class LoggerFactory { |
3. ThreadLocal的使用
写完让阳哥review后,阳哥说这个存在很大隐患:“使用这个类的人,大概率会像使用Log4j一样——把*LoggerFactory.getSessionLogger()*的返回值赋给类的某个成员变量使用”。如下所示:
1 | public class Test { |
4. 两种改进方案
增加中间代理类
1
2
3
4
5
6
7
8
9
10
11public class LoggerFactory {
private static final IAppender appender = AppenderFactory.getAppender();
private static final ThreadLocal<ISessionLogger> loggerThreadLocal =
ThreadLocal.withInitial(() -> new SessionLoggerImpl(appender));
public static ISessionLogger getSessionLogger() {
return (ISessionLogger) Proxy.newProxyInstance(EdiLoggerFactory.class.getClassLoader(),
new Class[]{ISessionLogger.class},
(proxy, method, args) -> method.invoke(loggerThreadLocal.get(), args));
}
}静态方法代态工厂类
1
2
3
4
5
6
7
8
9public class Logger {
private static IAppender appender = AppenderFactory.getAppender();
private static final ThreadLocal<ISessionLogger> loggerThreadLocal =
ThreadLocal.withInitial(() -> new SessionLoggerImpl(appender));
public static void log(String info) {
loggerThreadLocal.get().log(info);
}
}
5. 总结
工厂方法中使用ThreadLocal时需要注意:
1)工厂类获取的实例一般会赋值给成员变量,来供该类的所有方法使用;
2)获取ThreadLocal实例一般赋值给方法内的局部变量,来获取当前线程ThreadLocalMap中的实例;
3)由于工厂类和ThreadLocal的常规使用场景不一致,两者混搭时,就容易出现非预期的结果。