什么时候适合使用 SLF4J 的“动态绑定”功能?
When is the “dynamic binding” functionality of SLF4J appropriate to use?
我对 SLF4J 很感兴趣,因为它似乎是唯一使用这种所谓的“动态绑定 的 Java 库(至少我可以解释) ]” 的 类 在运行时定义行为。
我的意思是,如果您在编译类路径中包含 slf4j-api
,您现在可以访问所有 API 类(Loggers
和 LoggerFactories
, 等)包含在该 JAR 中,但它们的实际运行时行为是无操作(什么也不做)除非 你包含一个“SLF4J 绑定”在运行时类路径上,例如 slf4j-simple
(将日志语句发送到 STDOUT
和 STDERR
),或 slf4j-log4j
,然后需要 Log4J 配置等。
就像我上面所说的,这种动态绑定行为似乎是 SLF4J 项目所独有的。
我想知道为什么?一般来说,在日志记录之外,什么样的场景需要这种动态绑定作为解决方案?对我来说,它似乎是经典依赖注入(Spring,Guice)的替代方案,几乎将注入推迟到即时(“JIT”)确定哪些匹配 类 可用运行时类路径。
所以我问:这个解决方案是否唯一保证只能解决日志记录问题?如果是这样,为什么?如果不是,那么还有哪些其他问题值得采用这种方法作为解决方案?
接口和实现与动态定位实现提供者的工厂 class 的分离并不是 SLF4J 独有的。 Java 和 Java EE 中的许多 API 都使用了该模式。例如,javax.xml.parsers.SAXParserFactory 和 javax.json.Json.
SLF4J 的方法有一些独特之处:
默认实现是空操作。对于大多数 APIs,你需要实现来做一些有意义的事情,所以 API 认为如果没有提供者这是一个致命错误,但是 SLF4J 选择让他们的默认实现成为一个空操作。我个人会选择让默认实现最少登录到 System.err。
工厂实现使用 static class 名称来查找实现 class(因此所有实现都具有相同的 class name) 而不是使用反射。您可以通过转至 https://github.com/qos-ch/slf4j/find/master 并在页面上的任意位置键入 t
字符以打开文件查找器并输入 StaticLoggerBinder 来观察这一点:您会发现所有绑定实现都使用相同的 class 姓名.
这种方法的缺点是你只能有一个实现(相对于你可以加载多个实现的反射 classes),并且你必须将实现打包到与接口(相对于你可以从上下文 class 加载器加载实现的反射)。然而,后者实际上被认为是一个优势,因为应用程序服务器往往会错误处理 interface/implementation 拆分,如果应用程序服务器和应用程序都包含同一库的副本,这会导致问题,这是 commons-logging 的一个非常常见的问题, log4j 等。该问题并非日志记录所独有,因为当应用程序服务器尝试进行 XML 解析但意外找到应用程序时,人们经常(或曾经)遇到 XML 解析库的 ClassCastException已经打包自己XML解析APIs.
这种方法的另一个优点是性能。由于工厂 API 静态引用实现,因此没有反射开销来定位和提供者,也没有虚拟方法开销来调用提供者。实际上,JIT 通常最终会认识到工厂 API 无论如何只会调用一个实现,因此它会对其进行优化。然而,这也许就是日志记录不同于其他 API 的原因:对于日志记录,您通常从 class 静态初始化器调用工厂 API,这意味着您调用工厂 API 很多次当你的应用程序第一次启动然后再也没有。这对 JIT 来说是最糟糕的情况:当它意识到需要优化对提供者的调用时,您的应用程序已经完成了对记录器工厂的调用 API.
我对 SLF4J 很感兴趣,因为它似乎是唯一使用这种所谓的“动态绑定 的 Java 库(至少我可以解释) ]” 的 类 在运行时定义行为。
我的意思是,如果您在编译类路径中包含 slf4j-api
,您现在可以访问所有 API 类(Loggers
和 LoggerFactories
, 等)包含在该 JAR 中,但它们的实际运行时行为是无操作(什么也不做)除非 你包含一个“SLF4J 绑定”在运行时类路径上,例如 slf4j-simple
(将日志语句发送到 STDOUT
和 STDERR
),或 slf4j-log4j
,然后需要 Log4J 配置等。
就像我上面所说的,这种动态绑定行为似乎是 SLF4J 项目所独有的。
我想知道为什么?一般来说,在日志记录之外,什么样的场景需要这种动态绑定作为解决方案?对我来说,它似乎是经典依赖注入(Spring,Guice)的替代方案,几乎将注入推迟到即时(“JIT”)确定哪些匹配 类 可用运行时类路径。
所以我问:这个解决方案是否唯一保证只能解决日志记录问题?如果是这样,为什么?如果不是,那么还有哪些其他问题值得采用这种方法作为解决方案?
接口和实现与动态定位实现提供者的工厂 class 的分离并不是 SLF4J 独有的。 Java 和 Java EE 中的许多 API 都使用了该模式。例如,javax.xml.parsers.SAXParserFactory 和 javax.json.Json.
SLF4J 的方法有一些独特之处:
默认实现是空操作。对于大多数 APIs,你需要实现来做一些有意义的事情,所以 API 认为如果没有提供者这是一个致命错误,但是 SLF4J 选择让他们的默认实现成为一个空操作。我个人会选择让默认实现最少登录到 System.err。
工厂实现使用 static class 名称来查找实现 class(因此所有实现都具有相同的 class name) 而不是使用反射。您可以通过转至 https://github.com/qos-ch/slf4j/find/master 并在页面上的任意位置键入
t
字符以打开文件查找器并输入 StaticLoggerBinder 来观察这一点:您会发现所有绑定实现都使用相同的 class 姓名.这种方法的缺点是你只能有一个实现(相对于你可以加载多个实现的反射 classes),并且你必须将实现打包到与接口(相对于你可以从上下文 class 加载器加载实现的反射)。然而,后者实际上被认为是一个优势,因为应用程序服务器往往会错误处理 interface/implementation 拆分,如果应用程序服务器和应用程序都包含同一库的副本,这会导致问题,这是 commons-logging 的一个非常常见的问题, log4j 等。该问题并非日志记录所独有,因为当应用程序服务器尝试进行 XML 解析但意外找到应用程序时,人们经常(或曾经)遇到 XML 解析库的 ClassCastException已经打包自己XML解析APIs.
这种方法的另一个优点是性能。由于工厂 API 静态引用实现,因此没有反射开销来定位和提供者,也没有虚拟方法开销来调用提供者。实际上,JIT 通常最终会认识到工厂 API 无论如何只会调用一个实现,因此它会对其进行优化。然而,这也许就是日志记录不同于其他 API 的原因:对于日志记录,您通常从 class 静态初始化器调用工厂 API,这意味着您调用工厂 API 很多次当你的应用程序第一次启动然后再也没有。这对 JIT 来说是最糟糕的情况:当它意识到需要优化对提供者的调用时,您的应用程序已经完成了对记录器工厂的调用 API.