spirng framework源码解析之spring-jcl(三)

2,438 阅读3分钟

这是我参与8月更文挑战的第三天,活动详情查看:8月更文挑战

功能介绍

jcl全称为Jakarta Commons Logging,是Apache提供通用日志的API, 而spring-jcl模块大概的意思是:属于spring自己的日志API,不过其底层还是采用的是Apache的那套机制。

具体分析

log.java

只定义log接口,具体的实现则是在运行时动态寻找,比较有意思的是为了向下兼容,spring团队将包名与之前的也保持一致了,而并不是以spring进行命名。

// 注意这里的包名
package org.apache.commons.logging;

public interface Log {

   /**是否开启某个级别的日志*/
   boolean isFatalEnabled();

   boolean isErrorEnabled();

   boolean isWarnEnabled();

   boolean isInfoEnabled();

   boolean isDebugEnabled();

   boolean isTraceEnabled();

    /**提供不同级别的日志方法*/
   void fatal(Object message);

   void fatal(Object message, Throwable t);

   void error(Object message);

   void error(Object message, Throwable t);

   void warn(Object message);

   void warn(Object message, Throwable t);

   void info(Object message);

   void info(Object message, Throwable t);

   void debug(Object message);

   void debug(Object message, Throwable t);

   void trace(Object message);

   void trace(Object message, Throwable t);

LogFactor.java

通过LogFactory进行实现,另外还描述到性能的问题,建议使用以下方式进行记录

    if (log.isDebugEnabled()) {
        ... do something expensive ...
        log.debug(theResult);
    }

ok,我们接着往下看LogFactory类的实现参考jcl-ovre-slf4j桥接工具,提供了2种方式进行实现,弃用的直接跳过。

public abstract class LogFactory {

   /**根据类名实例化*/
   public static Log getLog(Class<?> clazz) {
      return getLog(clazz.getName());
   }

    /**根据字符串实例化*/
   public static Log getLog(String name) {
      return LogAdapter.createLog(name);
   }

但是该类为抽象类,最终是利于spi机制进行实例化,即通过一个抽象工厂类LogFactory实现配置文件的加载解析以及实例化日志框架。

LogAdapter.java

而LogAdapter是最核心的部分,负责创建并实例化上面的log接口,在进行实例化LogAdapter时,会自动去调用Class.forName找到该类并加载到jvm中,并且执行对应的静态代码块。接着利用ClassLoader加载LogAdapter,通过ClassNotFoundException异常去判断使用哪个日志框架,默认是使用java.util.logging,如下所示


static {
   if (isPresent(LOG4J_SPI)) {
      if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
         logApi = LogApi.SLF4J_LAL;
      }
      else {
         logApi = LogApi.LOG4J;
      }
   }
   else if (isPresent(SLF4J_SPI)) {
      logApi = LogApi.SLF4J_LAL;
   }
   else if (isPresent(SLF4J_API)) {
      logApi = LogApi.SLF4J;
   }
   else {
      logApi = LogApi.JUL;
   }
}

private static boolean isPresent(String className) {
   try {
      Class.forName(className, false, LogAdapter.class.getClassLoader());
      return true;
   }
   catch (ClassNotFoundException ex) {
      return false;
   }
}

那么它是如何判断使用哪个实现类的呢?我们继续分析

// 由该类中的内部类进行创建
public static Log createLog(String name) {
   switch (logApi) {
      case LOG4J:
      // 实现原理根据LoggerContextFactory找到ContextSelector
      // 再获取ContextAnchor类中的ThreadLocal<LoggerContext>
         return Log4jAdapter.createLog(name);
      case SLF4J_LAL:
      // 根据单例模式获取LocationAwareLogger再实例化Slf4jLog
         return Slf4jAdapter.createLocationAwareLog(name);
      case SLF4J:
      // 根据 slf4j自带的LoggerFactory进行实例化
         return Slf4jAdapter.createLog(name);
      default:
      // 根据jdk全局LogManager实例化Logger的子类RootLogger
         return JavaUtilAdapter.createLog(name);
   }

总结

🆗,spring-jcl模块的解析就告一段落总的来说,那么知识点如下所示

  • 设计模式:适配器模式

提供的统一的接口,然后在适配类中将对日志的操作委托给具体的日志框架.为不同的接口实现互通的目的

public interface Log {}
private static class Log4jLog implements Log, Serializable {}
private static class Slf4jLog<T extends Logger> implements Log, Serializable {}
private static class JavaUtilLog implements Log, Serializable {}

public abstract class LogFactory {}
public class LogFactoryService extends LogFactory {}

final class LogAdapter {}
  • 设计模式:策略模式

严格来说不算是策略模式,但是又有这方面的思想.主要是利用类名称找到对应的上下文,随着策略改变而改变的,并且返回其父类,解决条件分支过多的情况

public static Log createLog(String name) {
   switch (logApi) {
      case LOG4J:
         return Log4jAdapter.createLog(name);
      case SLF4J_LAL:
         return Slf4jAdapter.createLocationAwareLog(name);
      case SLF4J:
         return Slf4jAdapter.createLog(name);
      default:
         return JavaUtilAdapter.createLog(name);
   }
  • 技术点:ThreadLocal

利用LoggerContext类中的ThreadLocal<LoggerContext>获取LoggerContext对象

  • 依赖:最小原则

该模块仅仅只是引入了2个api,做到最小原则的引入依赖,以及相应的描述信息,这点值得笔者去学习

description = "Spring Commons Logging Bridge"

dependencies {
   optional("org.apache.logging.log4j:log4j-api")
   optional("org.slf4j:slf4j-api")
}