Bootstrap

微服务学习系列15:日志框架slf4j使用和原理

 

目录

 

前言

一、slf4j

项目Github地址:

slfj4j桥接API

slf4j 项目模块描述

二、slf4j-api

简单使用

代码分析 

三、基础知识

外观模式

适配器模式

什么是桥接模式

什么是SPI

什么是API

四、设计分析

参考



前言

slf4j主要是为了给Java日志访问提供一个标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。


一、slf4j

官网地址:https://www.slf4j.org/manual.html

项目Github地址:

https://github.com/qos-ch/slf4j


slfj4j桥接API

slf4j 项目模块描述

模块描述
slf4j-api日志的统一接口
slf4j-jdk14jul到SLF4J的绑定,将强制SLF4J的调用分配给jul
slf4j-log4j12log4j 到SLF4J的绑定,将强制SLF4J的调用分配给 log4j
jcl-over-slf4j将commons-logging框架的日志桥接到slf4j上来
log4j-over-slf4j将 log4j 框架的日志桥接到slf4j上来

二、slf4j-api

简单使用

slf4j-api是slf4j的api模块,提供了日志输出的API,值得一提的是从slf4j 1.8起,slf4j使用SPI的方式寻找日志实现框架,而在此之前则是通过寻找指定类的方式发现并绑定实现框架。先通过一个简单的入门案例来分析它的实现方式。

public class TestLog {
    private static final Logger logger = LoggerFactory.getLogger(TestLog.class);

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

20:56:33.344 [main] INFO com.test.TestLog - hello world 

代码分析 

先来分析 LoggerFactory.getLogger(TestLog.class) 这行代码,LoggerFactory.getLogger() 返回一个 Logger 对象,我们看看LoggerFactory 类的实现

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

    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 https://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
            }
        }

        return logger;
    }
}

重点关注 getILoggerFactory 方法 ,它返回一个日志工厂类。ILoggerFactory 是一个slf4j 提供的一个接口,可以猜测 到 getILoggerFactory方法应该是拿到了其实现类.

    public static ILoggerFactory getILoggerFactory() {
        return getProvider().getLoggerFactory();
    }

getProvider() 方法返回SLF4JServiceProvider,其中SLF4JServiceProvider同样是slf4j-api中提供的一个接口,那么getProvider肯定是通过某种方法拿到了该接口的实现类。

 static SLF4JServiceProvider getProvider() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        switch (INITIALIZATION_STATE) {
            case 1:
                return SUBST_PROVIDER;
            case 2:
                throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also https://www.slf4j.org/codes.html#unsuccessfulInit");
            case 3:
                return PROVIDER;
            case 4:
                return NOP_FALLBACK_SERVICE_PROVIDER;
            default:
                throw new IllegalStateException("Unreachable code");
        }
    }
 重点关注 performInitialization 方法
  private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }
    }

   bind 方法

 private static final void bind() {
       List<SLF4JServiceProvider> providersList = findServiceProviders();
    }

 从 findServiceProviders 方法中 可以 知道 是通过SPI的方式寻找SLF4JServiceProvider的实现类.

    private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList();
        Iterator var2 = serviceLoader.iterator();

        while(var2.hasNext()) {
            SLF4JServiceProvider provider = (SLF4JServiceProvider)var2.next();
            providerList.add(provider);
        }

        return providerList;
    }

三、基础知识

外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

什么是桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

什么是SPI

SPI全名Service Provider interface,翻译过来就是“服务提供接口”,再说简单就是提供某一个服务的接口, 提供给服务开发者或者服务生产商来进行实现。
Java SPI 是JDK内置的一种动态加载扩展点的实现。
这个机制在一般的业务代码中很少用到(个人接触到的业务没有用到过),但是再底层框架中却被大量使用,包括JDBC、Dubbo、Spring框架、日志接口中都有用到,不同的是有的使用Java原生的实现,有的框架则自己实现了一套SPI机制

当接口是由服务调用方提供,并且由服务提供方进行实现时,服务调用方就可以根据自己的需要选择特定实现,而不用更改业务代码以获取相应的功能,这个就是SPI

什么是API

API 全称Application Programming Interface, 翻译为“应用程序接口”,指的是应用程序为外部提供服务的接口,这个接口通常由服务提供者自行开发,定义好接口后很少改动。

一般应用(模块)之间通过接口进行通讯,服务提供方提供接口并进行实现后,调用方就可以通过调用这个接口拥有服务提供方提供的能力,这个就是API

API 中的接口是服务提供者给服务调用者的一个功能列表,而 SPI 中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。 

四、设计分析

了解了slf4j  的简单使用后,我们来看看它是设计的核心接口类

public interface Logger {}
public interface ILoggerFactory {

    public Logger getLogger(String name);
}
public interface SLF4JServiceProvider {

    /**
     * Return the instance of {@link ILoggerFactory} that 
     * {@link org.slf4j.LoggerFactory} class should bind to.
     * 
     * @return instance of {@link ILoggerFactory} 
     */
    public ILoggerFactory getLoggerFactory();


    /**
     * Initialize the logging back-end.
     * 
     * <p><b>WARNING:</b> This method is intended to be called once by 
     * {@link LoggerFactory} class and from nowhere else. 
     * 
     */
    public void initialize();
}

      我们看看Log4j 的桥接方式

Reload4jServiceProvider 实现 SLF4JServiceProvider

public class Reload4jServiceProvider implements SLF4JServiceProvider {
    public static String REQUESTED_API_VERSION = "2.0.99"; // !final

    private ILoggerFactory loggerFactory;
    private IMarkerFactory markerFactory;
    private MDCAdapter mdcAdapter;

    public Reload4jServiceProvider() {
        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");
        }
    }

    @Override
    public void initialize() {
        loggerFactory = new Reload4jLoggerFactory();
        markerFactory = new BasicMarkerFactory();
        mdcAdapter = new Reload4jMDCAdapter();
    }

    @Override
    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }
}

 

在 resources/META-INF/services 路径下 创建文件org.slf4j.spi.SLF4JServiceProvider 。文件名为 slf4j中SLF4JServiceProvider 的全路径名,在文件 org.slf4j.spi.SLF4JServiceProvider 中存放实现类

org.slf4j.reload4j.Reload4jServiceProvider

系统启动会自动扫描 resources/META-INF/services 下的文件,调用实现类的构造方法,完成对象的初始化。从Reload4jServiceProvider 中 可以获取到ILoggerFactory 的 实现类了。

参考

JavaSPI详解_Java程序V的博客-CSDN博客

;