【问题标题】:Should a Log4J logger be declared as transient?应该将 Log4J 记录器声明为瞬态吗?
【发布时间】:2010-09-10 01:46:04
【问题描述】:

我正在使用带有 Log4J 的 Java 1.4。

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

我的每个 POJO 都声明了一个记录器

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

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

我应该使用

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

改为?

【问题讨论】:

  • 您可以问自己几个问题并添加为您的问题的编辑。你的想法是什么,反对让它短暂?瞬态是什么意思?
  • 我应该提到,我喜欢为每个实例而不是为每个类一个记录器的想法。我将有许多由容器(例如 Spring 应用程序上下文)实例化和管理的同一类的实例,并且希望基于每个 bean 而不是基于每个类来切换日志记录级别。在 Spring 应用程序上下文的示例中,记录器将被声明为类似于 private final transient Logger log = Logger.getLogger(getBeanName()); bean 不再是 POJO 并实现 BeanNameAware
  • 另见the SLF4J FAQ“类的记录器成员应该声明为静态的吗?[...] 总之,将记录器成员声明为静态变量需要更少的 CPU 时间并且具有内存占用略小。[...] 但是,实例变量可以为每个应用程序创建不同的记录器环境,即使对于在共享库中声明的记录器也是如此。也许比前面提到的考虑更重要的是,实例变量是 IOC 友好的,而静态变量不是。”
  • 开始使用 SLF4J 并自动处理序列化:“从 SLF4J 版本 1.5.3 开始,记录器实例在序列化过程中仍然存在。因此,主机类的序列化不再需要任何特殊操作,即使当记录器被声明为实例变量时。在以前的版本中,记录器实例需要在宿主类中声明为瞬态。”(参见 [SLF4J 常见问题解答] (slf4j.org/faq.html))

标签: java logging serialization log4j


【解决方案1】:

使用静态记录器怎么样?或者您是否需要为类的每个实例使用不同的记录器引用?静态字段默认不序列化;您可以使用名为serialPersistentFieldsObjectStreamField 的私有、静态、最终数组显式声明要序列化的字段。 See Oracle documentation

新增内容: 当您使用getLogger(getClass()) 时,您将在每个实例中使用相同的记录器。如果要为每个实例使用单独的记录器,则必须在 getLogger() 方法中区分记录器的名称。例如getLogger(getClass().getName() + hashCode())。然后,您应该使用瞬态属性来确保记录器未序列化。

【讨论】:

  • @ThorbjørnRavnAndersen 您能否解释一下,为什么它不适合重新部署 Web 应用程序?还是用 WildFly 8.2 等较新的 AS 解决了这个问题?
  • 静态记录器倾向于 - 除非您的应用程序经过非常仔细的编码 - 保持类加载器,因此它们不会被垃圾收集。这显示在例如在 maven 构建期间内存使用量稳步增加,当 javac 被 maven 分叉时,内存使用量就会消失。
  • 这篇文章有一些很好的解释为什么不使用静态记录器wiki.apache.org/commons/Logging/StaticLog
  • 静态记录器是否可以防止 Spark 上下文中的并发问题?
【解决方案2】:

如果您真的想要采用瞬态方法,则需要在反序列化对象时重置日志。这样做的方法是实现方法:

 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();
 }

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

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

      通常记录器字段被声明为静态,但如果您需要它作为实例字段,只需将其声明为瞬态,就像它通常对任何不可序列化的字段所做的那样。但是,在反序列化时,logger 字段将为 null,因此您必须实现 readObject() 方法才能正确初始化它。

      【讨论】:

        【解决方案5】:

        这类情况,尤其是在 EJB 中,通常最好通过线程本地状态来处理。通常,用例就像您有一个遇到问题的特定事务,您需要提升日志记录以调试该操作,以便您可以生成有关问题操作的详细日志记录。在事务中携带一些线程本地状态并使用它来选择正确的记录器。坦率地说,我不知道在这种环境中在实例上设置级别会有什么好处,因为实例到事务的映射应该是容器级别的函数,您实际上无法控制在一个实例中使用哪个实例无论如何给定交易。

        即使在处理 DTO 的情况下,将系统设计为需要给定特定实例的方式通常也不是一个好主意,因为设计很容易演变成一个糟糕的选择.从现在开始一个月后,您可能会决定效率考虑(缓存或其他一些改变生命周期的优化)将打破您关于将实例映射到工作单元的假设。

        【讨论】:

          【解决方案6】:

          记录器不可序列化,因此在将它们存储在实例字段中时必须使用瞬态。 如果您想在反序列化后恢复记录器,您可以将级别(字符串)存储在您的对象中,该对象确实会被序列化。

          【讨论】:

            【解决方案7】:

            如果您希望 Logger 是每个实例的,那么是的,如果您要序列化您的对象,您可能希望它是瞬态的。 Log4J Loggers 不可序列化,在我使用的 Log4J 版本中也不行,所以如果你不让你的 Logger 字段是瞬态的,你会在序列化时遇到异常。

            【讨论】:

              【解决方案8】:

              尝试将 Logger 设为静态。比你不必关心序列化,因为它是由类加载器处理的。

              【讨论】:

                【解决方案9】:

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

                没有理由让记录器成为非静态的,除非您有充分的理由这样做。

                【讨论】:

                • 如果您想为每个实例使用不同的记录器名称(例如,将实例唯一的字符串附加到记录器名称的末尾),或者如果您在超类对象中有一个记录器,并且您希望它使用子类实例的名称。
                • “没有理由让记录器成为非静态的,除非你有充分的理由这样做。”——这让我头疼! ;-)
                猜你喜欢
                • 2013-09-28
                • 1970-01-01
                • 2015-01-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-09-09
                相关资源
                最近更新 更多