起因:一直在使用slf4j+log4j(logback)打印日志,虽然知道slf4j是一个门面接口,但对于slf4j是如何在运行期连接具体实现的一直不是很清楚,
刚好趁着春节回来有时间,仔细看了一下,顺便记录下。
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; }
下面我们看一下查找StaticLoggerBinder的实现细节,还是有几个地方挺有意思的:
这是因为如果LoggerFactory.class是有BootstrapClassLoader加载的,那么我们在程序中获取到的classLoader是null;
此时,使用系统记载器,也就是AppClassLoader进行记载。由于双亲委派模型,最后也可以使用BootstrapClassLoader进行加载。
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); }
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; }