這是前幾天碰到的一個由 SLF4J 引發的異常
Exception in thread "main" java.lang.IllegalAccessError: tried to access field
org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
at org.slf4j.LoggerFactory.<clinit>(LoggerFactory.java:60)
在網上搜尋時,找到了 SLF4J FAQ 這篇文件,其中就有對這個異常的描述,當時是把問題解決了。為了將這個問題的解決過程描述方便,我先寫了之前的一篇文章,簡單介紹了一些 SLF4J 的知識。因為這個異常的產生原因是由於 slf4j-api.jar 與 slf4j-log4j12.jar 版本不相容所致,其中 slf4j-log4j12.jar 是 SLF4J 提供的五種 binding jar 之一。具體的說,是由於使用了較老版本的 slf4j-api.jar(在我的應用裡引的版本是 1.4.1),和較新版本的 slf4j binding jar(在我的應用裡引了 slf4j-log4j12-1.5.6.jar)。
錯誤的出處是: 我定義了一個類,為了使用 log4j 列印日誌,呼叫 org.slf4j.LoggerFactory 建立了一個 Logger,並作為類的 static 屬性,除此之外無其他的成員屬性,程式碼如下:
public class Foo {
private static final Logger logger = LoggerFactory.getLogger(Foo.class);
// ... methods
}
在獲取 Logger 時拋了前面的異常。
下面通過原始碼分析下異常的來源:
在 slf4j-api 的早期版本(1.5.5 及之前)中,org.slf4j.LoggerFactory 中包含一個 ILoggerFactory 的 static 屬性,並且通過 static 塊進行初始化。
public final class LoggerFactory {
static ILoggerFactory loggerFactory;
static {
try {
loggerFactory = StaticLoggerBinder.SINGLETON.getLoggerFactory();
} catch(NoClassDefFoundError ncde) {
//...
}
}
在早期版本的 slf4j binding 實現中,org.slf4j.impl.StaticLoggerBinder 類中的 SINGLETON 成員是被定義為 public 的。
public class StaticLoggerBinder {
/**
* The unique instance of this class.
*/
public static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public ILoggerFactory getLoggerFactory() {
//...
}
}
所以,如果 slf4j-api.jar 和 slf4j-log4j12.jar 都使用早期版本,將不會報錯。
從 SLF4J 1.5.6 開始, slf4j binding 實現中的 org.slf4j.impl.StaticLoggerBinder 類的 SINGLETON 被定義為了 private. 並且從這個版本開始,slf4j api 中 org.slf4j.LoggerFactory 不再使用 StaticLoggerBinder.SINGLETON.getLoggerFactory() 的方式獲取 ILoggerFactory 的例項,而是通過 StaticLoggerBinder.getSingleton.getLoggerFactory() 的方式對 ILoggerFactory 例項進行初始化。具體的實現要比早期版本複雜的多,在此不詳細列程式碼了。
所以,如果使用早期版本的 slf4j-api.jar 和新版本的 slf4j binding jar,由於 org.slf4j.LoggerFactory 無法訪問新版本 binding 實現中的 StaticLoggerBinder.SINGLETON 私有屬性,就會產生前面的異常資訊。