从门面模式到 Slf4j

门面(外观)模式是一种结构模式,其主要目的是当有一组完成 类似功能 的接口时,对外暴露一个统一的外形,使调用方不需要知道它外形之下的具体实现是什么。很像 Java Interface 提供的作用了,但这里可能粒度更大一些,门面后面往往是一些大粒度的子系统。

简介

试想,原先我需要自己调用多个子系统各自部分或全部的功能接口以完成我的需求:

2018-08-30-15356106508820

而使用门面模式重构后,只需要调用门面提供的统一功能入口即可:

2018-08-30-15356107001587

日志框架中的应用

日志系统应该是最常见的门面模式的应用了,我们以 Slf4j 门面框架为例。

介绍

Slf4j 提供了日志接口,方便程序获取具体日志对象,其自带的简单日志记录实现 slf4j-simple、自带的空实现 slf4j-nop、第三方日志框架 Logback 等,均 直接 实现了 Slf4j。第三方日志框架 Log4J、JDK 内置的日志实现 JUL 等并不直接实现 Slf4j,而是由 Slf4j 提供适配层 slf4j-log412、slf4j-jdk14 来实现向 Slf4j 的桥接转换。

SLF4J(Simple Logging Facade for Java)和 JCL(Jakarta Commons Logging)是两个不同的日志门面系统。

2018-08-30-concrete-bindings

以下是各种 binding jar:

15382270074113

最佳实践

获取日志对象:

protected static final Logger logger = LoggerFactory.getLogger(SomeService.class);

关于工厂类:

  • 直接使用 Log4j2 的话,通过 org.apache.logging.log4j.LogManager
  • Commons Logging 使用 org.apache.commons.logging.LogFactory
  • Slf4j 使用 org.slf4j.LoggerFactory

记录日志:

logger.debug("message is {}.", message);

占位符的使用:

slf4j 的规则是,仅仅将 “{ 紧靠 }” 识别为占位符,如果是 {123},则原字输出。

所以,我们需要把 {} 看作一个整体,如果要显示 {},可以用 \ 转义:

logger.debug("fill the \\{} with {}", "3");
logger.debug("File name is C:\\\\{}.", "file.zip");

绑定实际使用的子系统

类加载时,getLogger() 尝试获取 Logger 实例:

image-20200606161315662

getILoggerFactory() 实例化 StaticLoggerBinder 单例,后者在不同日志框架中有不同实现:

image-20200606162010335

performInitialization() 调用 bind() 绑定日志框架实现:

image-20200606161405254

具体的绑定方式是尝试从 classpath:org/slf4j/impl 位置找到 StaticLoggerBinder.class 文件:

image-20200606161431664

没找到则则会报错:

无任何实现

如果找到多个则 Slf4j 也不知道最终会使用哪个,只是会告诉你:

image-20200606162417530

所以一定要注意排查是否有多个当前日志门面的实现,若有,那么系统整体的日志打印将不可控(不论是性能还是日志位置)。

Log4j2 中的 StaticLoggerBinder:

image-20200606161601087