在一个可序列化的Java类中,使用Logger的正确方法是什么?

17

我在正在开发的系统中有一个以下(经过修改)的类,Findbugs 生成了一个 SE_BAD_FIELD 警告,我试图理解为什么它会这样说,在我按照我想的方式修复它之前。我感到困惑的原因是,警告似乎表明我在类中没有使用其他非可序列化实例字段,但 bar.model.Foo 也是不可序列化的,并且以完全相同的方式使用(就我所知),但 Findbugs 没有为它生成任何警告。

import bar.model.Foo;

import java.io.File;
import java.io.Serializable;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo implements Serializable {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final File file;
    private final List<Foo> originalFoos;
    private Integer count;
    private int primitive = 0;

    public Demo() {
        for (Foo foo : originalFoos) {
            this.logger.debug(...);
        }
    }

    ...

}

我最初的解决方案是在使用它时从工厂获取一个日志记录器引用:

public DispositionFile() {
    Logger logger = LoggerFactory.getLogger(this.getClass());
    for (Foo foo : originalFoos) {
        this.logger.debug(...);
    }
}

不过那似乎并不是特别高效的,你有什么想法吗?


2
Ceki报告称,对于logback,getLogger(...)方法足够快,可以在需要时调用,而不仅仅是在静态字段中使用。 - Thorbjørn Ravn Andersen
请查看jcabi-logLogger类,它是SLF4J的包装器。 - yegor256
如果使用SLF 1.5.3或更高版本,请阅读Ceki的答案 - Markus Pscheidt
5个回答

19
首先,不要过早地进行优化。也许LoggerFactory.getLogger()足够快,并且对执行时间没有显着的负面影响。如果有疑问,请进行分析。
其次,findbugs没有抱怨使用Foo的原因是因为该类没有Foo类型的字段,它有一个List类型的字段。泛型在编译时被擦除,在字段定义方面实际上没有对Foo的引用。在运行时,如果您尝试序列化Demo类的实例,则Foo不可序列化会导致异常,但findbugs无法知道这一点。
我的第一反应是将Logger作为静态字段而不是实例字段。在这种情况下应该可以正常工作。
public class Demo implements Serializable {
   private static final Logger logger = LoggerFactory.getLogger(Demo.class);

   // .. other stuff
}

挑剔一下:Findbugs可能会知道这一点:字段的声明类型(包括任何类型参数/通配符)被写入类文件,因此可用于静态分析。 - meriton
1
啊,不行:Findbugs并不知道一个List实际上将会持有T类型的非瞬态引用。(例如,由Collections.emptyList()返回的列表就不是这样的)。 - meriton
此外,如果包括一个静态字段建议的代码片段,将使得这个答案可接受。 :) - Tim Visher
当对记录器进行基准测试时,您会发现它花费的时间远远超过查找记录器的时间,主要是将日志条目写入磁盘或控制台。在我看来,通常会将其设置为私有静态常量(应该使用大写字母),以使日志行更短。有人知道为什么人们将常量设置为大写字母,但不适用于记录器吗? - Peter Lawrey
3
在这种情况下,FindBugs会误导你。 SLF4J记录器支持开箱即用的序列化。 - Ceki
显示剩余2条评论

7

我不希望事情跑偏,但您是否考虑过日志记录器的常规初始化方式?

private static final Logger logger = LoggerFactory.getLogger(Demo.class);

如果你不需要为每个实例使用不同的记录器(这很不寻常),问题就会消失。
顺便说一下,SL4J的作者在对像commons-logging这样的Log4J包装器的批评中说:

往往这些包装器的质量令人怀疑,因此禁用(或未使用)日志记录语句的成本要比直接使用log4j高出1000倍。包装类中最常见的错误是在每个日志请求上调用Logger.getLogger方法。这保证会破坏您的应用程序性能。真的!!!

这表明你提出的每次需要时获取记录器的替代想法并不被推荐。

公正地说,该文章的下一句话是“当然,并非所有的包装器都质量低劣。例如,commons-logging API 就是一个合理实现的典范。” - matt b
同样可以说,除非您需要动态记录器名称或与子类共享实例等情况,否则记录器应始终为静态的,因此对此给予+1。 - matt b

6
在这种情况下,FindBugs会误导您,因为org.slf4j.Logger接口没有标记为java.io.Serializable。然而,随SLF4J一起提供的SLF4J日志记录器实现都支持开箱即用的序列化。试试看。你会发现它可以工作。
以下是SLF4j FAQ的摘录:
与静态变量相反,默认情况下,实例变量会被序列化。从SLF4J版本1.5.3开始,记录器实例可以幸存序列化。因此,即使将记录器声明为实例变量,在主机类的序列化中也不再需要任何特殊操作。在以前的版本中,记录器实例需要在主机类中声明为短暂的。
另请参见http://slf4j.org/faq.html#declared_static

也许他正在使用早于1.5.3版本的软件,如果是这样的话。 - skaffman
1
FindBugs可能会感到困惑,因为即使在SLF4J版本1.5.3及更高版本中,org.slf4j.Logger接口也没有标记为Serializable。 - Ceki

3

我的初步反应是想知道在对象中序列化Logger实例是否有意义。当您稍后对其进行反序列化时,期望Logger的环境正确吗?我认为我宁愿选择这个方案并结束它:

private transient Logger logger = LoggerFactory.getLogger(this.getClass());

2
只要您采取措施在反序列化时重新创建它,就可以保证其不会丢失。 - user207421

0

不建议在可序列化的类中放置记录器。

根据我的经验,这将消耗太多内存。

并且当您缓存此类时,很容易导致OutOfMemory错误。

保持清理您的可序列化类。

但是如果您在本地或开发环境中,则可以自由使用。


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