什么时候使用静态的Log对象 已翻译 100%

Rory_Ye 投递于 2013/05/13 10:45 (共 17 段, 翻译完成于 05-19)
阅读 2042
收藏 5
1
加载中

这里有在日志使用中比较常见的两种模式:

public class Foo {
  private Log log = LogFactory.getLog(Foo.class);
  ....
}

public class Foo {
  private static Log log = LogFactory.getLog(Foo.class);
  ....
}

使用static修饰符在某些情况下是有好处的. 但是有些情况下确实也是一个非常糟糕的主意,并且可能会有意想不到的后果. 本文就是来描述如何恰当的使用它.

skyline520
翻译于 2013/05/14 15:48
2

static的问题

使用static的结果是显而易见的: 只有一个日志对象在所有类的实例间共享.这显示是比较高效的内存利用; 无论创建多少实例只需要一个引用(4 or 8 字节) . 这样CPU也是非常高效的; CPU只需要在类被第一次引用的时候查找日志实例即可.

当编写独立应用代码时使用static是个不错的主意.

然而当java 代码作为可能发布在一个容器(例如 j2ee 服务器)中时,这是时问题就会出现了. 通常情况下容器都是创建java 类加载器级别的对象,像每一个发布在容器内的引用都有自己的类加载器但是所有发布的应用的顶级有些共享的类加载器. 在这种情况下, 当一个类包含部署在“应用”级别的日志实例的引用时(例如 在一个servlet 或者 j2ee 容器的“webapp”目录级别) 这样仍然没有问题; 如果多个应用发布类然后它们各自拥有类副本并且和其它应用没有任何交互,这时类就会出现问题.

skyline520
翻译于 2013/05/17 13:08
1

然而考虑到一个使用"private static Log log = ..." 的类通过一个应该独立的多个"应用"的父类加载器发布. 在这种情况下,日志成员变量只初始化一次, 因为这里只有一个类的副本. 初始化(通常)发生在任何代码第一次尝试实例化这个类或者调用它的静态方法的时候. 当类的初始化发生时, 日志成员变量应赋什么值? 选项是:

  • 引用容器级别部分配置信息层次的底层日志对象, 不会关联到任何特定的"应用"
  • 引用容器级别部分配置信息层次的底层日志对象莫名其妙的关联到当前的应用
  • 引用一个每次方法被调用时可以确定"当前应用"的"代理"对象, 并且那时会委托给适当的底层日志对象.
skyline520
翻译于 2013/05/17 18:01
1

第一个选项意味着不能在每一个应用级别配置日志记录.无论如何配置日志,它都将同样适用于容器内的每个应用,并且那些应用的输出将被混在一起.这是一个重大问题.

第二个选项意味着日志对象将被调用它的第一个应用所配置.容器内的其他应用将发送它们的输出到配置第一个应用配置的地址.显然这是一个重大问题.

第三个选项允许每个应用配置日志、正确的过滤器和输出必须的日志信息.然而性能的代价是非常大的; 这也不是一个可以接受的方案.

skyline520
翻译于 2013/05/16 21:17
1

注意这个讨论没有涉及到"线程上下文类加载器"或其他技术要点. 问题不是在于细节的实现: 当日志对象在多个应用间共享时,不可能为每一个应用级别配置并且拥有可靠的性能.

这个问题的根源就是共享数据 (类的静态成员变量) 被所谓独立的"应用"共享. 如果没有类在应用之间共享那么就不存在这个问题. 但是现实是(令我沮丧的是) 容器提供商鼓励使用共享类,并且应用开发者继续使用它.

另外一种解决方案是: 避免在有可能发布在同一个共享类路径下的任何代码中使用"static"引用日志对象.

skyline520
翻译于 2013/05/16 21:35
1

SLF4J是否也有这个问题?

是的。SLF4J 也遇到了上面提到的那些问题, 同样的建议:避免将可能部署到一个共享的classpath的代码里的log对象申明为静态的。

这里有很多可能的方案。首先我们需要定义一个术语"TCCL-aware"。当一个日志库初始化的时候使用线程上下文类加载器来定位它的配置文件,而不是通过日志库本身的类加载器来定位,它就是"TCCL-aware"的。例如, 在其标准形式中, log4j 不是"TCCL-aware"的。但是可以通过以下描述的一个"Contextual RepositorySelector"使其成为"TCCL-aware"的:


Rory_Ye
翻译于 2013/05/15 10:10
1

现在来了解一些可能出现的情况:

假设SLF4J部署在一个共享的级别, 在类共享路径有一个包含static 日志记录器引用的类, 并且在应用程序的类路径有个包含static 日志对象的类.当底层日志类库不被线程上下文类加载器所感知的时日志只从“共享”的类路径读取配置, 这适用于部署在容器内的所有的应用的输出. 输出是正确的,但是不支持单个应用的日志调试,并且所有应用程序的输出会混合在一起.当底层日志类库被线程上下文类加载器感知时, 每个应用都单独配置. 此外, 那些部署在应用级别的类的输出也是正确的. 然后部署在共享级别的类将会使用第一次调用这个类的应用的信息初始化自己的日志对象; 当其它的应用调用相同的类时日志输出将定位到第一个应用. 这样的情况是很糟糕的.

skyline520
翻译于 2013/05/18 08:38
1

如果SLF4J同时在共享和应用的级别部署, 应用程序选择第一父类加载器的行为和上述是相同的. 即使第一子类加载器已经选择, 如果底层日志类库是被线程上下文类加载器感知的这时事情一般会比变的糟糕. 然而如果第一子类加载器已经选择并且底层日志类库没有被线程上下文类加载器感知这时结果一般在忍受范围之内的: 共享类的输出只能是全局配置, 不管哪个应用调用的所有的都会输出到一个相同的地址.

不幸的是大多数情况下,类库的作者并不能指定用户代码的部署位置, 将会选择什么样的加载顺序, 或者是选择哪种底层日志类库. 因此要避免使用 static 日志对引用SLF4J 和 commons-logging.

skyline520
翻译于 2013/05/18 09:11
1

在不正确的使用时SLF4J出现问题的几率要比commons-logging 低,然而. 原因是:

  • 目前没有太多类库是被“线程上下文类加载器感知的”,并且
  • 目前使用SLF4J的类库只有很小一部分部署在共享类路径中.

Commons-logging 本身是线程上下文类加载器感知的它可以允许不被线程上下文类加载器感知的应用级别的配置; 事实上因此每个使用commons logging的类库都是作为"线程上下文类加载器感知的". Commons-logging 广泛应用于频繁通过共享类加载器部署的类库中.

注意这不作为批判 SLF4J的部分.正如前面所提到的, 问题是由于根本的隔离日志和共享静态日志对象引用的应用间的冲突,因此SLF4J 面临着和commons-logging 在这些情况下的同样的约束.

skyline520
翻译于 2013/05/19 08:50
1

java.util.logging 是否也有这个问题?

是的。 使用java.util.logging api的代码也会遇到上面提到的这些问题,同样的建议:避免将可能部署到一个共享的classpath的代码里的log对象申明为静态的。

java1.4的java.util.logging 包是一个支持多个实现的API。Java运行时默认只提供了一个很简单的实现,但是比如J2EE服务器这类容器通常有更多可选的复杂实现。

当使用默认实现的时候, 不存在“静态”的问题,因为默认实现不是容器感知的。但是因为前面到的那些原因,这会给为每个应用程序单独配置带来副作用。

Rory_Ye
翻译于 2013/05/16 11:51
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(0)

返回顶部
顶部