1. 背景
由于EDI已有的日志结构比较混乱,多个人都写了自己的LoggerHelper工具类。近期的工作主要是写一个新的日志框架,通过SPI方式加载Appender的实现,并替换掉之前的日志内容。
2. 初始实现LoggerFactory
在实现日志框架时,我写了一个LoggerFactory,代码如下:
public class LoggerFactory {
private static IAppender appender = AppenderFactory.getAppender();
private static final ThreadLocal<ISessionLogger> loggerThreadLocal =
ThreadLocal.withInitial(() -> new SessionLoggerImpl(appender));
public static ISessionLogger getSessionLogger() {
return loggerThreadLocal.get();
}
}
3. ThreadLocal的使用
写完让阳哥review后,阳哥说这个存在很大隐患:“使用这个类的人,大概率会像使用Log4j一样——把*LoggerFactory.getSessionLogger()*的返回值赋给类的某个成员变量使用”。如下所示:
public class Test {
private final ISessionLogger logger = LoggerFactory.getSessionLogger();
public void func() {
logger.log("anything");
}
}
4. 两种改进方案
- 增加中间代理类
public 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));
}
}
- 静态方法代态工厂类
public 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的常规使用场景不一致,两者混搭时,就容易出现非预期的结果。