5.7 利用Supplier创建日志消息

问题

用户希望创建由日志级别(log level)控制是否可见的日志消息。

方案

使用 java.util.logging.Logger 类新增的各种日志方法,它们传入 Supplier 作为参数。

讨论

目前,Logger 类中的日志方法(如 infowarningsevere 等)包括两种重载形式,一种传入单个 String 作为参数,另一种传入 Supplier 作为参数。

例 5-20 显示了各种日志方法的签名。7

7读者或许奇怪,Java 日志框架的设计者为什么不采用与其他日志 API 相同的日志级别(Trace、Debug、Info、Warn、Error、Fatal)。这是一个非常好的问题,如果读者找到答案,也请告诉作者。

例 5-20 Logger 类中各种日志方法的重载形式

  1. void fine(String msg)
  2. void fine(Supplier<String> msgSupplier)
  3.  
  4. void finer(String msg)
  5. void finer(Supplier<String> msgSupplier)
  6.  
  7. void finest(String msg)
  8. void finest(Supplier<String> msgSupplier)
  9.  
  10. void info(String msg)
  11. void info(Supplier<String> msgSupplier)
  12.  
  13. void warning(String msg)
  14. void warning(Supplier<String> msgSupplier)
  15.  
  16. void severe(String msg)
  17. void severe(Supplier<String> msgSupplier)

每种方法的第一种重载形式(传入 String)是 Java 1.4 引入的,而第二种重载形式(传入 Supplier)是 Java 8 引入的,它在标准库中的实现如例 5-21 所示。

例 5-21 Logger 类的实现细节

  1. public void info(Supplier<String> msgSupplier) {
  2. log(Level.INFO, msgSupplier);
  3. }
  4.  
  5. public void log(Level level, Supplier<String> msgSupplier) {
  6. if (!isLoggable(level)) {
  7. return;
  8. }
  9. LogRecord lr = new LogRecord(level, msgSupplier.get());
  10. doLog(lr);
  11. }

❶ 如果不显示消息则返回

❷ 调用 get 方法以便在 Supplier 中检索消息

上述实现并非构建一个永远不会显示的消息,而是检查消息是否是“可记录的”(loggable)。如果所提供的消息是一个简单的字符串,程序将评估它是否已被记录。在上述日志方法中,第二种重载形式(传入 Supplier)支持在消息前添加空括号和箭头(() ->)以将其转换为 Supplier,且仅当日志级别合适时才会调用它。例 5-22 显示了 info 方法的两种重载形式。

例 5-22 在 info 方法中使用 Supplier

  1. private Logger logger = Logger.getLogger(this.getClass().getName());
  2. private List<String> data = new ArrayList<>();
  3.  
  4. // 用数据填充列表
  5.  
  6. logger.info("The data is " + data.toString());
  7. logger.info(() -> "The data is " + data.toString());

❶ 无论是否显示 Info 消息,都会构建参数

❷ 仅当日志级别显示 Info 消息时,才会构建参数

可以看到,消息在列表的每个对象上调用 toString 方法。在第一条 logger.info 语句中,无论程序是否显示 Info 消息,都会创建结果字符串;在第二条 logger.info 语句中,在消息前添加 () -> 就能将日志参数转换为 Supplier,这意味着只有使用消息时才会调用 Supplierget 方法。

采用相同类型的 Supplier 替换参数的技术称为延迟执行(deferred execution),可以在任何对象创建成本较高的上下文中使用。

另见

延迟执行是 Supplier 的主要用例之一,有关 Supplier 接口的讨论请参见范例 2.2。