SLF4J和Java SPI
# SL4J介绍
在各种Java开发规范中,针对日志打印,都要求使用SLF4J api,而不是要直接使用日志框架。 那么为什么要使用SLF4J,SLF4J的作用到底是什么,以下引用官网的一段介绍:
The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.
从这段描述可以看出,SLF4J是一个“日志门面”(Logging Facade)它允许用户自由的切换所需的日志框架。
其实SLF4J是典型的面向接口的编程,只是一个日志标准,只提供了一套记录日志的api,并没有日志系统的具体实现,而是通过对接如log4j、java.util.logging等日志框架,来实现日志的输出,各个应用程序在使用日志的的地方全部使用slf4j-api 提供的接口进行编程,系统中具体使用哪个日志框架觉得于引入哪个具体的实现。
以下是SLF4J官网的一张图,揭示了SLF4J和现有主流日志框架的集成方式:
上图中所有的和日志框架的集成都是由SLF4J团队开发的,但是和log4j集成的是log4j1,如果使用log4j2需要使用 log4j-slf4j-impl ,此包为log4j团队实现,具体依赖如下:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>xxx.xx.xx</version>
</dependency>
2
3
4
5
# SLF4J实现方式
日志框架有很多,他们是如何被slf4j发现并绑定的?接下来就从源码层面来看一下SLF4J的实现方式,注: 由于SLF4J 1.8 版本以后修改为使用ServiceLoader来加载具体日志实现,所以此处会分为两部分来介绍。
# SLF4J 1.8之前实现方式
注: 代码基于 1.7.30 版本在使用SLF4J开发时我们使用以下代码,可以看出主要涉及两个类:org.slf4j.Logger 和 org.slf4j.LoggerFactory ,通过源码可以看出Logger是一个接口,定义来标准的日志操作,由 LoggerFactory 来具体确定Logger实现,所以此处主要分析 LoggerFactory代码。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
2
3
4
5
6
7
8
9
getLogger 方法主要有两个步骤:
- 获取 ILoggerFactory 的具体实现
- 通过 ILoggerFactory 的 getLogger 方法获取真实绑定的日志框架
查看 ILoggerFactory 源码发现它是一个接口并且只有 getLogger 一个方法,所以可以判断在正式的日志框架集成的包中需要实现此接口,并返回适配好的 Logger 的实现,接下来分析 getILoggerFactory() 方法,分析SLF4J具体是怎么查找并绑定到具体的日志框架的
/**
* Return a logger named according to the name parameter using the
* statically bound {@link ILoggerFactory} instance.
*
* @param name
* The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
// 获取真实的 ILoggerFactory 的具体实现
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
2
3
4
5
6
7
8
9
10
11
12
13
跟踪代码调用可以看到最终是调用方法 performInitialization(); 执行初始化,而 performInitialization() 方法主要是调用 bind() 方法执行最终的绑定的,如果绑定成功后直接调用 getSingleton() 获取单例,然后调用 getLoggerFactory(); 方法获取具体的实现工厂
/**
* 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:
// 绑定成功后直接调用 getSingleton() 获取单例,然后调用 getLoggerFactory(); 方法获取具体的实现工厂
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");
}
// 执行初始化
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
// 绑定成功后,执行版本检查,检查SLF4J版本和具体实现的版本是否一致
versionSanityCheck();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
查看bind() 方法逻辑,发现SLF4J绑定的方式是通过固定约定来是现实的,要求在具体实现包中包含类 org.slf4j.impl.StaticLoggerBinder ,具体约定如下:
- 全类名必须是 org.slf4j.impl.StaticLoggerBinder
- 必须是单例
- 必须包含静态方法 getSingleton(); 获取单例
- 必须提供一个static final String REQUESTED_API_VERSION对象指定支持的版本
- 必须包含方法 public ILoggerFactory getLoggerFactory() 返回具体的 ILoggerFactory 的实现
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()) {
// 如果不是Android系统,则通过ClassLoader 查找 org/slf4j/impl/StaticLoggerBinder.class
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 如果发现有多个 实现则会抛出 发现多个绑定的信息,此处就是我们在控制台看到的 Class path contains multiple SLF4J bindings.
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
// 通过调用 getSingleton 方法绑定具体的实现
/// 可能会有多个StaticLoggerBinder,选择哪一个由jvm的类加载决定。
StaticLoggerBinder.getSingleton();
// 修改状态为 初始化成功
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
// 报告绑定的 具体的实现是哪个
reportActualBinding(staticLoggerBinderPathSet);
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
} finally {
postBindCleanUp();
}
}
// 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);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
至此可以看出SLF4J加载不同的实现的关键为 org.slf4j.impl.StaticLoggerBinder ,以下是 log4j1 集成相关的源码:参考此源码实现及SLF4J加载过程,我们自己可以实现一套基于SLF4J的日志框架
/**
* Copyright (c) 2004-2011 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.impl;
import org.apache.log4j.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.LoggerFactoryBinder;
/**
* The binding of {@link LoggerFactory} class with an actual instance of
* {@link ILoggerFactory} is performed using information returned by this class.
*
* @author Ceki Gülcü
*/
public class StaticLoggerBinder implements LoggerFactoryBinder {
/**
* The unique instance of this class.
*
*/
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
/**
* Return the singleton of this class.
*
* @return the StaticLoggerBinder singleton
*/
public static final StaticLoggerBinder getSingleton() {
return SINGLETON;
}
/**
* Declare the version of the SLF4J API this implementation is compiled against.
* The value of this field is modified with each major release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.6.99"; // !final
private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();
/**
* The ILoggerFactory instance returned by the {@link #getLoggerFactory}
* method should always be the same object
*/
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# SLF4J 1.8之后实现方式
查看源码会发现和1.7之前主要是区别的地方为 bind() 方法,1.8之后采用Java SPI机制来加载不同的日志实现,由于采用了SPI机制,所以SLF4J要求JDK版本要在1.6以上,
private final static void bind() {
try {
// 使用JavaSPI机制加载具体实现
List<SLF4JServiceProvider> providersList = findServiceProviders();
// 如果发现多个绑定 报告发现多个日志实现
reportMultipleBindingAmbiguity(providersList);
// 判断具体实现
if (providersList != null && !providersList.isEmpty()) {
// 获取第一个绑定
PROVIDER = providersList.get(0);
// SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
// 初始化
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.");
// 调用1.7 之前的方法,但是不会进行绑定,只是输出提示
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
}
postBindCleanUp();
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
// 标准Java SPI查找实现
private static List<SLF4JServiceProvider> findServiceProviders() {
ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
for (SLF4JServiceProvider provider : serviceLoader) {
providerList.add(provider);
}
return providerList;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
通过源码可以看出,1.8之后定义了接口 org.slf4j.spi.SLF4JServiceProvider 用于实现SPI的绑定,去除了原先的基于约定的 StaticLoggerBinder 绑定方式
SLF4JServiceProvider接口源码如下:
package org.slf4j.spi;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.LoggerFactory;
/**
* This interface based on {@link java.util.ServiceLoader} paradigm.
*
* <p>It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x.
*
* @author Ceki G¨lc¨
* @since 1.8
*/
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();
/**
* Return the instance of {@link IMarkerFactory} that
* {@link org.slf4j.MarkerFactory} class should bind to.
*
* @return instance of {@link IMarkerFactory}
*/
public IMarkerFactory getMarkerFactory();
/**
* Return the instnace of {@link MDCAdapter} that
* {@link MDC} should bind to.
*
* @return instance of {@link MDCAdapter}
*/
public MDCAdapter getMDCAdapter();
public String getRequesteApiVersion();
/**
* 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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
以下为log4j1 集成源码:
org.slf4j.log4j12.Log4j12ServiceProvider
package org.slf4j.log4j12;
import org.apache.log4j.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;
public class Log4j12ServiceProvider implements SLF4JServiceProvider {
/**
* Declare the version of the SLF4J API this implementation is compiled against.
* The value of this field is modified with each major release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.8.99"; // !final
private ILoggerFactory loggerFactory;
private IMarkerFactory markerFactory;
private MDCAdapter mdcAdapter;
public Log4j12ServiceProvider() {
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 Log4jLoggerFactory();
markerFactory = new BasicMarkerFactory();
mdcAdapter = new Log4jMDCAdapter();
}
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
public MDCAdapter getMDCAdapter() {
return mdcAdapter;
}
public String getRequesteApiVersion() {
return REQUESTED_API_VERSION;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Java SPI使用案例
SPI是Service Provider Interface 的简称,即服务提供者接口的意思。说白了就是一种扩展机制,我们在相应配置文件中定义好某个接口的实现类,然后再根据这个接口去这个配置文件中加载这个实例类并实例化。
# SPI机制的约定
- 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
- 用ServiceLoader类动态加载META-INF中的实现类
- 如SPI的实现类为Jar则需要放在主程序classPath中
- Api具体实现类必须有一个不带参数的构造方法
# 使用案例
- 新建一个Maven工程 spi-interface ,定义一个接口如下:
public interface Printer {
public void print();
}
2
3
- 创建Maven工程 spi-good-printer ,定义 Printer 的第一个实现,如下:
public class GoodPrinter implements Printer {
public void print() {
System.out.println("good printer");
}
}
2
3
4
5
文件: cc.shixw.spi.api.printer.Printer
cc.shixw.spi.impl.printer.GoodPrinter
- 创建Maven工程 spi-bad-printer,定义 Printer 的第一个实现,如下:
public class BadPrinter implements Printer {
public void print() {
System.out.println("bad printer");
}
}
2
3
4
5
文件: cc.shixw.spi.api.printer.Printer
cc.shixw.spi.impl.printer.BadPrinter
- 创建Maven工程spi-invoker,引入以上依赖,并创建调用类:
public class MyApp {
public static void main(String[] args) {
ServiceLoader<Printer> printerServiceLoader = ServiceLoader.load(Printer.class);
printerServiceLoader.forEach(p-> p.print());
}
}
2
3
4
5
6
7
pom.xml
<dependencies>
<dependency>
<groupId>cc.shixw</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cc.shixw</groupId>
<artifactId>spi-good-printer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cc.shixw</groupId>
<artifactId>spi-bad-printer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 运行MyApp结果如下:
# 参考
SLF4J官网: http://www.slf4j.org/ (opens new window)
SLF4J官方文档: http://www.slf4j.org/docs.html (opens new window)
SLF4J源码: https://github.com/qos-ch/slf4j (opens new window)
LOG4J2源码: https://github.com/apache/logging-log4j2 (opens new window)