從slf4j的動態加載說起


起因:一直在使用slf4j+log4j(logback)打印日志,雖然知道slf4j是一個門面接口,但對於slf4j是如何在運行期連接具體實現的一直不是很清楚,

剛好趁着春節回來有時間,仔細看了一下,順便記錄下。

入口:使用slf4j聲明一個logger的方法
 
protected Logger logger = LoggerFactory.getLogger(this.getClass());
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}

關鍵的方法是:getILoggerFactory,注意上面的LoggerFactory是在slf4j中定義的。

 

實現:最主要的就是動態查找slf4j的具體實現。 這里如何查找呢?實際上是依賴一個默認的約定的,也就是所有slf4j的實現都實現了一個類:org.slf4j.impl.StaticLoggerBinder

在logback/log4j中都可以查找到此類。

這樣,slf4j的LoggerFactory在查找具體實現時,實際上就是在工程的classpath中查找StaticLoggerBinder這個類;

如果找到這個類,說明是有slf4j具體實現的;如果找到多個,說明有多個slf4j具體實現。

只要找到這個類,我們就可以直接加載StaticLoggerBinder這個類,而這個類中實現了getILoggerFactory方法。

  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;
    }
 
至此,只要slf4j的LoggerFactory確定在classpath中能夠找到StaticLoggerBinder,也就代表能夠找到具體的實現。最后,直接調用StaticLoggerBinder的方法即可獲得LoggerFactory。
 

下面我們看一下查找StaticLoggerBinder的實現細節,還是有幾個地方挺有意思的:

1:為什么獲取classLoader時要判斷null? 

這是因為如果LoggerFactory.class是有BootstrapClassLoader加載的,那么我們在程序中獲取到的classLoader是null;

此時,使用系統記載器,也就是AppClassLoader進行記載。由於雙親委派模型,最后也可以使用BootstrapClassLoader進行加載。

 

2:AppClassLoader是如何初始化的?URLClassLoader?

AppClassLoader和ExtClassLoader都是繼承自 URLClassLoader,URLClassLoader封存了一些url,用於查找resource;

其初始化的過程是這樣的:

BootstrapClassLoader是JVM的一部分,有jvm負責初始化;然后會初始化Launcher,Launcher中又具體初始化了AppClassLoader和ExtClassLoader,

並且將AppClassLoader的父記載器設為ExtClassLoader。具體細節如下:

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if(var2 != null) {
            SecurityManager var3 = null;
            if(!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if(var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

  

3: getResource是如何實現的?利用class對象的getResource與classLoader的getResource有何區別?

getResource類似與雙親委派模型,首先在父ClassLoader中查找,找不到再在自己身上查找。

URLClassLoader由於自己定義了urlpath,所以很好查找。

具體細節如下:

 public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }  

需要注意,classLoader中的getResource中的參數類於:"org/slf4j/impl/StaticLoggerBinder.class"

我們再看看class中的getResouce實現:

  public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }

 可以看到,除了name = resolveName(name),class中的getResource還是依賴於ClassLoader實現的,下面我們詳細看下resolveName這個方法:

它干了兩件事:一是如果name是以"/"開頭的,那么去掉"/",這個很好理解,目的是於classLoader一致;

如果那么不是以"/"開頭,那么將class所在的包名字填加到那么前面,並且將"."替換為"/".

    private String resolveName(String name) {
        if (name == null) {
            return name;
        }
        if (!name.startsWith("/")) {
            Class<?> c = this;
            while (c.isArray()) {
                c = c.getComponentType();
            }
            String baseName = c.getName();
            int index = baseName.lastIndexOf('.');
            if (index != -1) {
                name = baseName.substring(0, index).replace('.', '/')
                    +"/"+name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM