日志门面--Slf4J框架原理

日志门面–Slf4J框架原理

Slf4J作为一种日志门面,拥有极其强大的整合能力,可以与当前流行的Log4J2、Logback完美的融合在一起,

​ 很多初学者不明白为什么要使用日志门面,需要打印什么直接使用日志实现不就可以了吗?

为什么要使用日志门面,有什么好处?

​ 开发者可以通过Slf4J提供的同一套接口去使用不同的日志实现,可能很多萌新还是不太明白这句话的意思,我可以打个简单的比方,现在有这么一个场景,小明同学很喜欢吃棒棒糖,每天都会去隔壁超市买一根棒棒糖,超市每次进货都会在不同的厂家之间选成本最低的一个,超市可以频繁更换厂家,只需要保证能进到相同的棒棒糖就可以了,但是对于小明来说,他并不关心超市的进货厂家,只知道每天都可以拿同样的钱买到同样的棒棒糖,即使厂家在怎么变化,小明也不需要更换任何购买方式。在这里小明可以比作开发者,买棒棒糖其实就是开发者打印日志,超市就是日志门面,厂家是日志实现,开发者不需要关心日志实现是Log4J2还是Logback或者是其他,开发者只需要使用相同的方法进行日志的打印就可以了,即使底层的日志实现更换掉了,开发者也不需要改动任何代码。

日志门面--Slf4J框架原理_第1张图片

Slf4J基本原理

通过slf4j中LoggerFactory类提供的getLogger(Class clazz)()接口获取一个Logger对象,我这里得到的是logback的对象,因为我在项目中引用的是logback实现。下面是demo代码和pom依赖。

public class TestLogback {
   public static void main(String[] args) {
      //获取一个Logger对象
      Logger logger = LoggerFactory.getLogger(TestLogback.class);
      logger.trace("trace");
      logger.debug("debug");
      logger.info("info");
      logger.warn("warn");
      logger.error("error");
   }
}
<dependencies>       
	<dependency>
        <groupId>ch.qos.logbackgroupId>
        <artifactId>logback-classicartifactId>
        <version>1.3.0-alpha12version>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>
dependencies>

进入到getLogger(Class clazz)()方法中,继续往下层跟

public static Logger getLogger(Class<?> clazz) {
    //这里继续往下跟
    Logger logger = getLogger(clazz.getName());
    
    if (DETECT_LOGGER_NAME_MISMATCH) {
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                            autoComputedCallingClass.getName()));
            Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
        }
    }
    return logger;
}

进入上面这个方法之后,可以看到获取Logger对象是分两部,首先获得一个日志工厂接口ILoggerFactory的实现类对象,然后通过日志工厂实现类对象得到对应的Logger。这里的重点是这个日志工厂实现类对象是怎么得到的,也就第一行代码,继续跟进。

public static Logger getLogger(String name) {
    //获取ILoggerFactory的实现类对象
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    //通过工厂类实现对象获得Logger
    return iLoggerFactory.getLogger(name);
}

进入getILoggerFactory()方法之后,看到这里获取ILoggerFactory的实现类对象也是分两部进行的,

  1. 首先是获取一个日志实现的Provider,这一步是slf4j负责的。
  2. 找到对应的Provider后,调用getLoggerFactory()方法获取日志实现的日志工厂,getLoggerFactory()是一个接口方法,需要各家日志实现框架去实现。
public static ILoggerFactory getILoggerFactory() {
    return getProvider().getLoggerFactory();
}

进入getProvider()方法之后,可以看到底层使用静态变量INITIALIZATION_STATE标记初始化状态,该变量有五种状态,最初状态为UNINITIALIZED(尚未初始化),

系统则会进入*performInitialization()*方法进行初始化;

static SLF4JServiceProvider getProvider() {
    //INITIALIZATION_STATE为初始化状态,有五种状态如下:
    //UNINITIALIZED = 0;//尚未初始化
	//ONGOING_INITIALIZATION = 1;//正在初始化
	//FAILED_INITIALIZATION = 2;//初始化失败
	//SUCCESSFUL_INITIALIZATION = 3;//初始化成功,这意味着slf4j找到了一种日志实现
	//NOP_FALLBACK_INITIALIZATION = 4;//没有日志实现
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                //如果没有初始化,则执行performInitialization()进行初始化
                //使用的日志实现是Logback还是Log4j或者是其他,主要就是在这一步
                performInitialization();
            }
        }
    }
     //如果已经初始化完成,则执行直接返回SLF4JServiceProvider的对象
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return PROVIDER;
     //没有使用其他日志实现,则返回一个空实现
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_SERVICE_PROVIDER;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        return SUBST_PROVIDER;
    }
    throw new IllegalStateException("Unreachable code");
}

进入*performInitialization()方法后可以看到一个bind()方法,这个方法就是去给日志门面绑定一个日志实现,具体内容,稍后再看,完成绑定之后,会执行versionSanityCheck()*方法校验日志实现的版本使得否与当前的日志门面版本相符合。

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

下面来看上面说到的*bind()*方法。

private final static void bind() {
        try {
            //查询当前系统加载到的日志实现列表
            List<SLF4JServiceProvider> providersList = findServiceProviders();
            //这个方法的作用是检测日志实现是否为多个,如果是则打印警告信息
            reportMultipleBindingAmbiguity(providersList);
            if (providersList != null && !providersList.isEmpty()) {
                //如果有多个日志实现,则使用第一个。
                PROVIDER = providersList.get(0);
                //该方法则是slf4j定义的api,需要日志实现框架实现该方法。目的就是对日志实现进行初始化工作,这里不是slf4j的工作,就不进行详细分析了。
                PROVIDER.initialize();
                //初始化完成之后,把状态标记为初始化成功
                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                //打印当前使用的日志实现框架
                reportActualBinding(providersList);
            } else {
                /*
                这里我没有看明白,希望大佬有可以留言指导
                */
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("No SLF4J providers were found.");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_PROVIDERS_URL + " for further details.");

                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
            }
            postBindCleanUp();
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }
Up();
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

你可能感兴趣的:(java,后端,开发语言)