slf4J源码初探
博客专区 > 筷筷 的博客 > 博客详情
slf4J源码初探
筷筷 发表于4个月前
slf4J源码初探
  • 发表于 4个月前
  • 阅读 8
  • 收藏 0
  • 点赞 0
  • 评论 0
摘要: 本文首先介绍了日志实现工具和日志接口的知识;然后以slf4j的Logfactory.getLogger(class c)方法为入口,学习slf4j实现的方法和技巧。

一、日志实现工具和日志接口简介

java日志工具主要包含两个方面:日志接口工具和日志实现工具

  • 日志实现工具
  1. Apache 开发了功能强大的log4j,并申请纳入jdk

  2. sun不接受apache 的申请,而是在jdk1.4中自己开发了Java.util.logging
  3. logback出自log4j的作者之手,用于替代log4j,Logback的内核重写了log4j,在一些关键执行路径上性能提升10倍以上,而且logback不仅性能提升了,初始化内存加载也更小了。 logback非常自然实现了slf4j, logback-classic实现了slf4j,作为slf4j的实现时无需适配器。
  • 日志接口
  1. slf4j 简单日志门面(Simple Logging Facade for Java)。 slf4j同样是日志接口,使用静态绑定的方式支持log4jjdklogback等作为日志实现,也支持静态绑定common-logging作为中间层,然后通过common-logging的动态加载机制选择日志实现。使用SLF4J时,如果需要使用某一种日志实现,那么必须选择正确的适配器,比如:slf4j-log4j12.jar, slf4j- jdk14.jar等
  2. JCL Java通用日志接口 Apche common-logging。  commons-logging支持log4jjdk Java.util.logging作为其日志实现,使用时通过动态加载的方式寻找。 commons-logging启动后在 classpath 如果能找到log4j,则默认使用log4j 作为日志实现,如果没有则使用JDK14Logger 实现,如果JDK版本低于1.4,则使用其内部提供的 SimpleLog 实现。JCL的缺点:Apache Common-Logging使用了ClassLoader寻找和载入底层的日志库。而OSGI中,不同的插件使用自己的ClassLoader。一个线程的ClassLoader在执行不同的插件时,其执行能力是不同的。OSGI的这种机制保证了插件互相独立,然而确使Apache Common-Logging无法工作。
  3. JCL 只提供 log 接口,具体的实现则在运行时动态寻找。slf4j同样也是日志接口,使用静态绑定的方式支持log4jjdklogback等作为日志实现,也支持静态绑定common-logging作为中间层,然后通过common-logging的动态加载机制选择日志实现。

二、日志接口slf4j源码解析

1、引言

在阅读本小节前,最好先了解下log4j,logback,jdk-log,slf4j的知识,编写一个能够打印日志的demo就可以了。

日志接口不是具体的日志解决方案,它只服务于各种各样的日志系统 。而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。如果是在编写供内外部都可以使用的API或者通用类库,那么你真不会希望使用你类库的客户端必须使用你选择的日志类库。

log4j,logback,jdk-log都为slfj提供了实现,在引入这个三个工具的slf4j的后

  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.7</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.16</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.5.6</version>
    </dependency>

这三个jar中都有StaticLoggerBindder这个类。(当依赖的jar包版本出现冲突(groupId和artifactId相同,但version不同)时,优先级规则为:在依赖树中越靠近项目根节点的jar优先级越高,若两个jar在依赖树中所处层次相同则在pom.xml中越早出现的jar优先级越高。

如果两个jar里面的package名和class名都相同,但是groupId或artifactId不同,例如log4j-1.2.17与桥接器log4j-over-slf4j-1.7.21就符合这样的状况,程序加载org.apache.log4j.LogManager类的顺序适用于上述优先级规则。)

logback有些不一样,这点留到我们后面再讲。

至于为什么,我们一步一步看。我们在项目中引入slf4j-api,slf4j-xxx,xxx三个jar包之后,下面代码就可以正确的执行

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    public static Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.info("Hello {}!", "world");
    }
}

在上述代码中并没有引入log4j,logback,jdk-log相关的类,而当我们去掉slf4j-xxx这个jar包之后,就会提示以下错误:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

我们可以猜测,在LoggerFactory或者Loger中引用到了org.slf4j.impl.StaticLoggerBinder。

2.获取LoggerFactory

2.1 首先看getLogger(class a),这个方法又调用了getLogger(String name)的方法

 public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

2.2 代码第一行获取了一个iLoggerFactory,继续点进去看

/**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * 
     * @return the ILoggerFactory instance in use
     */
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

if语句代表,如果没有初始化的话,就进入一个同步块执行performInitialization()的方法,如果init成功则返回一个通过StaticLoggerBindder创建的LoggerFactory。(这里就结合前面提到问题考虑,为什么slf4j-jdk,slf4j-log4j,logback-classic中都包含StaticLoggerBindder类,以及为引入这三个jar时,slf4j提示的异常)。

2.3 performInitialization()方法里面调用了bind()方法,然后进行进行版本检查,这里我们重点关注bind()方法。

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
//找到所有的StaticLoggerBinder 
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
/*如果有多个,则提示载入了多个StaticLoggerBinder 实现,可能引入了slf4j-jdk,slf4j-log4j,
logback-classic中的两个或者三个*/
/*如果三个都引入了,会提示:
SLF4J: Found binding in [jar:file:/E:/maven/repository/
ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-log4j12/1.7.16/slf4j-log4j12-1.7.16.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-jdk14/1.5.6/slf4j-jdk14-1.5.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
*/
//提示有多个地方有StaticLoggerBinder
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
//获取StaticLoggerBinder的大力
            StaticLoggerBinder.getSingleton();
//设置状态,初始化成功
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//report实际的loggerFactory的类,例如 SLF4J: Actual binding is of type 
//[ch.qos.logback.classic.util.ContextSelectorStaticBinder]
            reportActualBinding(staticLoggerBinderPathSet);
//这句应该是处理在初始化过程中阻塞的输入日志事件
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (.....)
           {...  }
    }

代码非常长,不过我们暂时只关心try 块的代码,先理清最主要的脉络。staticLoggerBinderPathSet用来存储 StaticLoggerBindder的路径集合。
如果有多个路径就提示异常。

2.4 然后看找StaticLoggerBindder路径的代码,还是比较好理解的

// We need to use the name of the StaticLoggerBinder class, but we can't reference the class itself.
	private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

	static Set<URL> findPossibleStaticLoggerBinderPathSet()
	{
		// use Set instead of list in order to deal with bug #138
		// LinkedHashSet appropriate here because it preserves insertion order during iteration
		Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
		try
		{
//加载该对象的类或接口的类加载器。
			ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
			Enumeration<URL> paths;
			if (loggerFactoryClassLoader == null)
			{
//找到所有用于加载指定名称类的路径
				paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
			}
			else
			{
//查找具有给定名称的所有资源。
				paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
			}
//将path加入集合中
			while (paths.hasMoreElements())
			{
				URL path = paths.nextElement();
				staticLoggerBinderPathSet.add(path);
			}
		}
		catch (IOException ioe)
		{
			Util.report("Error getting resources from path", ioe);
		}
		return staticLoggerBinderPathSet;
	}

STATIC_LOGGER_BINDER_PATH是类的路径。首先获取到LoggerFactory的类加载器。如果找不到,就调用ClassLoader的类方法,查找所有的资源;如果找到了LoggerFactory的类加载器,就根据类加载器找对应的路径。然后将找到的路径存入staticLoggerBinderPathSet集合中返回。

2.5 回到2.2小节的代码,init成功之后直接返回执行下列代码

 return StaticLoggerBinder.getSingleton().getLoggerFactory();

slf4j中是没有StaticLogerBinder,得去slf4j-jdk,slf4j-log4j,logback-classic中看

我们选择log4j,代码比较简单,简单的单例模式。

public class StaticLoggerBinder implements LoggerFactoryBinder {


    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    public static String REQUESTED_API_VERSION = "1.6.99"; // !final

    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();

    private final ILoggerFactory loggerFactory;

    private StaticLoggerBinder() {
        loggerFactory = new Log4jLoggerFactory();
        try {
            @SuppressWarnings("unused")
            Level level = Level.TRACE;
        } catch (NoSuchFieldError nsfe) {
            Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
        }
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

2.6 小节

slf4j-jdk,slf4j-log4j都有slf4j的slf4j logger接口的实现分别是 JDK14LoggerAdapter和Log4jLoggerAdapter,而logback本身就实现了slf4j,不需要额外的适配器。

后面该写日志桥接方面的知识

共有 人打赏支持
粉丝 2
博文 2
码字总数 10477
×
筷筷
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: