Log4J记录器应该声明为瞬态(transient)吗?

31

我正在使用Java 1.4和Log4J。

我的某些代码涉及序列化和反序列化值对象(POJOs)。

每个POJO都声明了一个记录器。

private final Logger log = Logger.getLogger(getClass());

序列化程序抱怨 org.apache.log4j.Logger 不可序列化。

我应该使用

private final transient Logger log = Logger.getLogger(getClass());

改为使用别的替代方案?


1
你可以问自己以下几个问题,并将其作为编辑添加到你的问题中: 你对将它设置为短暂的有何想法和反对意见?什么是“短暂”的含义? - svrist
1
我应该提到,我喜欢为每个实例而不是每个类设置记录器的想法。我将会有许多相同类的实例由容器(例如Spring应用程序上下文)实例化和管理,并希望能够按bean的基础切换日志级别,而不是按类的基础。在Spring应用程序上下文的示例中,记录器将被声明为以下内容:private final transient Logger log = Logger.getLogger(getBeanName());其中bean不再是POJO并且实现了BeanNameAware。 - Vihung
1
参见 SLF4J FAQ: “类的 Logger 成员应该声明为静态吗?[...] 总之,将 logger 成员声明为静态变量需要更少的 CPU 时间,并且具有稍微较小的内存占用。[...] 但是,实例变量使得可以为每个应用程序创建一个不同的日志记录器环境,即使是在共享库中声明的日志记录器也是如此。也许比先前提到的考虑更重要的是,实例变量对 IOC 友好,而静态变量则不是。” - Arjan
开始使用SLF4J,序列化将自动处理:“从SLF4J版本1.5.3开始,记录器实例可以在序列化时保留。因此,即使日志记录器被声明为实例变量,在主机类的序列化中也不再需要任何特殊操作。在以前的版本中,日志记录器实例需要在主机类中声明为瞬态。”(请参见SLF4J FAQ - Istvan Devai
9个回答

27
如何使用静态记录器?或者您需要为每个类的实例使用不同的记录器引用?静态字段默认情况下不会被序列化; 您可以使用名为serialPersistentFields的私有、静态、最终数组中显式声明要序列化的字段。请参见Oracle文档
补充内容:如果使用getLogger(getClass()),则每个实例将使用相同的记录器。如果要为每个实例使用单独的记录器,则必须在getLogger()方法中区分记录器的名称,例如getLogger(getClass().getName() + hashCode())。然后,应该使用transient属性确保记录器不被序列化。

@ThorbjørnRavnAndersen,您能否解释一下,为什么它不适用于Web应用程序的重新部署?或者这个问题是否已经在像WildFly 8.2等更新的AS中得到解决? - CSchulz
5
除非您的应用程序编写得非常小心,否则静态记录器有一种倾向,会保存类加载器,以便它们无法被垃圾回收。这在例如 maven 构建期间内存使用量稳步增加时表现出来,但当 javac 被 maven 分叉时,这种情况就会消失。 - Thorbjørn Ravn Andersen
这篇文章有一些很好的解释,为什么不要使用静态日志记录器 https://wiki.apache.org/commons/Logging/StaticLog - Joshua H
静态记录器是否可以防止Spark上下文中的并发问题? - Peter

11

记录器必须是静态的;这将使其无法序列化。

除非你有强烈的理由让记录器不是静态的,否则没有理由这样做。


5
如果你想在每个实例中使用不同的记录器名称(例如,在记录器名称末尾添加唯一字符串),或者如果你有一个超类对象中的记录器并希望它使用子类实例的名称,则拥有一个非静态记录器有时是有用的。 - MB.
10
除非你有充分的理由需要这样做,否则没有必要使记录器成为非静态的。这让我感到困惑!;-) - Arjan

9

如果您真的想采用短暂方法,那么在反序列化对象时需要重置日志。做法是实现以下方法:

 private void readObject(java.io.ObjectInputStream in) 
   throws IOException, ClassNotFoundException;
Serializable的javadocs中有关于此方法的信息。
您的实现将类似于:
 private void readObject(java.io.ObjectInputStream in) 
     throws IOException, ClassNotFoundException {
   log = Logger.getLogger(...);
   in.defaultReadObject();
 }

如果您不这样做,那么在反序列化对象后,日志将为空。

5

可以将您的日志记录器字段声明为静态或瞬态。

这两种方法都可以确保在序列化期间,writeObject()方法不会尝试将该字段写入输出流。

通常情况下,日志记录器字段被声明为静态的,但如果您需要它成为实例字段,只需将其声明为瞬态即可,就像对于任何非可序列化字段一样。在反序列化时,日志记录器字段将为null,因此您必须实现一个readObject()方法来正确初始化它。


2
尝试将Logger设置为静态的。这样,您就不必担心序列化问题,因为它由类加载器处理。

2
这类情况,特别是在EJB中,通常最好通过线程本地状态来处理。通常使用情况是您有一个遇到问题的特定事务,并且您需要提升日志记录以调试该操作,以便您可以生成有关问题操作的详细日志记录。跨事务传递一些线程本地状态并使用它来选择正确的记录器。坦率地说,在这种环境下设置INSTANCE级别的日志级别会有什么好处我不知道,因为实例到事务的映射应该是容器级别函数,您实际上无法控制在给定事务中使用哪个实例。
即使在处理DTO的情况下,设计系统时也不一定要求使用特定实例,因为设计很容易发展出使其变成不良选择的方式。一个月后,您可能会决定采用效率考虑(缓存或其他生命周期更改优化)来打破将实例映射到工作单元的假设。

0
如果您希望记录器是每个实例的,则需要将其设置为瞬态,如果您要序列化对象。Log4J记录器不可序列化,在我使用的Log4J版本中不支持,因此如果您不将Logger字段设置为瞬态,则在序列化时会出现异常。

0

日志记录器不可序列化,因此在将它们存储在实例字段中时,必须使用 transient 关键字。如果您希望在反序列化后恢复日志记录器,则可以在序列化过程中存储 Level(String)属性。


0

使用实例记录器有很多好处。一个非常好的用例是,您可以在超类中声明记录器,并在所有子类中使用它(唯一的缺点是来自超类的日志被归因于子类,但通常很容易看出)。

(就像其他人提到的那样,使用静态或瞬态)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接