slf4j加载过程(基于logback实现)

主流的Web框架spring boot默认使用slf4j + logback,slf4j 为日志处理提供了统一的接口,比较代表的实现org.apache.log4j、ch.qos.logback等。
主要内容 logback的一般加载过程:

  • slf4j查找具体实现类的原理;
  • 加载配置文件过程(logback是logback.xml);


    logback load.jpg

1. slf4j + logback maven集成


    org.slf4j
    slf4j-api
    1.7.7



    ch.qos.logback
    logback-core
    1.1.7


    ch.qos.logback
    logback-classic
    1.1.7


2. 基本使用

package com.bclz;

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

/**
 * @Program: logback-test
 * @Description: logback测试
 * @Author: c
 * @Date: 2021-08-17 18:36
 */
public class Test {

    private static final Logger LOGGER= LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {

        LOGGER.info("测试普通日志...");
        LOGGER.error("测试错误日志...");
    }

}


3. 源码分析

关键方法分析

  1. getILoggerFactory()

    public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
      INITIALIZATION_STATE = ONGOING_INITIALIZATION;
      performInitialization();
    }
    switch (INITIALIZATION_STATE) {
      case SUCCESSFUL_INITIALIZATION:
        //如果初始化成功,则调用StaticLoggerBinder单例的getLoggerFactory()方法获得LoggerFactory工厂对象
        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://bugzilla.slf4j.org/show_bug.cgi?id=106
        return TEMP_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
    }
    
  2. performInitialization()初始化

    private final static void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        versionSanityCheck();
        }
    }
    
  • bind()

    try {
        //从classpath获取所有slf4j的实现,并将它们的资源路径存放到staticLoggerBinderPathSet中
        Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        //若从classpath中找到了多个slf4j的实现,则打印警告
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        // the next line does the binding 将具体的实现绑定到slf4j
        StaticLoggerBinder.getSingleton();
        //修改初始化状态为初始化成功
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstitutedLoggers();
    } catch (NoClassDefFoundError ncde) {
        //若有多个实现,则会抛此异常
        //jvm在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误(在这里是找不到具体的StaticLoggerBinder)
        ......   
    }
    
    1. findPossibleStaticLoggerBinderPathSet()

      注意在方法中有个经典的资源路径使用:

    • ClassLoader.getSystemResources(...)

    • LoggerFactory.class.getClassLoader().getResources(...)

      这两者由于类加载器的不同,可能会有不同的结果:

      getSystemResources(...)是用AppClassLoader加载的;

      而后者可能是自定义的类加载器,这两个方法可能会有不同的结果(加载jar资源一般推荐后者)。

    ClassLoader loggerFactoryClassLoader = LoggerFactory.class
        .getClassLoader();
    Enumeration paths;
    if (loggerFactoryClassLoader == null) {
        //STATIC_LOGGER_BINDER_PATH="org/slf4j/impl/StaticLoggerBinder.class"
        paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
    } else {
        paths = loggerFactoryClassLoader
            .getResources(STATIC_LOGGER_BINDER_PATH);
    }
    

至此,完成"org/slf4j/impl/StaticLoggerBinder.class"路径的查找,该实现类在logback-classic包中,slf4j的具体实现就是通过这种方式查找绑定的。

  1. StaticLoggerBinder.getSingleton()

该方法返回SINGLETON。
具体在类加载的静态代码块中初始化:

        static {
            SINGLETON.init();
        }
            /**
             * Package access for testing purposes.
             */
            void init() {
                try {
                    try {
                        new ContextInitializer(defaultLoggerContext).autoConfig();
                    } catch (JoranException je) {
                        Util.report("Failed to auto configure default logger context", je);
                    }
                    // logback-292
                    if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                        StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
                    }
                    contextSelectorBinder.init(defaultLoggerContext, KEY);
                    initialized = true;
                } catch (Throwable t) {
                    // we should never get here
                    Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
                }
            }

关键方法上下文初始化器的 new ContextInitializer(defaultLoggerContext).autoConfig();

            public void autoConfig() throws JoranException {
                StatusListenerConfigHelper.installIfAsked(loggerContext);
                //查找默认配置文件  
                URL url = findURLOfDefaultConfigurationFile(true);
                if (url != null) {
                    configureByResource(url);
                } else {
                    Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
                    if (c != null) {
                        try {
                            c.setContext(loggerContext);
                            c.configure(loggerContext);
                        } catch (Exception e) {
                            throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
                                            .getCanonicalName() : "null"), e);
                        }
                    } else {
                        //没有配置文件,则使用打印控制台的默认配置
                        BasicConfigurator basicConfigurator = new BasicConfigurator();
                        basicConfigurator.setContext(loggerContext);
                        basicConfigurator.configure(loggerContext);
                    }
                }
            }

再看看findURLOfDefaultConfigurationFile方法,就是查找具体路径的实现了

       public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
               // 获取当前实例的类加载器
               ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
               //logback.configurationFile
               URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               
               //再找logback.groovy
               url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               
               //再找logback-test.xml
               url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               //最后找logback.xml
               return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
           }

至此xml配置文件加载完毕

你可能感兴趣的:(slf4j加载过程(基于logback实现))