源码阅读-logback的StaticLoggerBinder如何提供ILoggerFactory的实现类
上一篇博客介绍了slf4j-api的核心类和接口,以及如何和日志实现框架对接的。简而言之就是通过下面这行代码:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
每个实现门面slf4j-api的日志实现框架都提供了org.slf4j.impl.StaticLoggerBinder类,通过委托该类返回一个ILoggerFactory的实现类,从而和具体的日志实现框架进行绑定。
这篇博客就来讲述一下,logback的StaticLoggerBinder如何提供ILoggerFactory的实现类。
从图中可以看到,StaticLoggerBinder实现了LoggerFactory接口,提供了getLoggerFactory()方法。LoggerContext实现了ILoggerFactory接口,提供了getLogger(String name)方法。StaticLoggerBinder拥有成员变量LoggerContext和ContextSelectorStaticBinder。StaticLoggerBinder委托ContextInitializer来初始化LoggerContext和委托ContextSelectorStaticBinder来选择一个上下文。
下面就来具体看看StaticLoggerBinder的代码 :
/** * The unique instance of this class. * 饿汉单例 * */ private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); /** * 私有静态对象 * 只有该类能访问的对象 * 用来做权限控制 后续只能该类来调用 */ private static Object KEY = new Object(); /** * 初始化该对象 */ static { SINGLETON.init(); } /** * 该对象是否初始化 */ private boolean initialized = false; /** * ILoggerFactory的实现类 */ private LoggerContext defaultLoggerContext = new LoggerContext(); /** * 上下文选择器绑定者 * 通过 把defaultLoggerContext传递给ContextSelectorStaticBinder * ContextSelectorStaticBinder 选择创建不同的ContextSelector 来获取不同环境的LoggerContext * 选择策略 ContextSelector接口的实现有 ContextJNDISelector 和 DefaultContextSelector 策略模式的应用 * ContextSelectorStaticBinder 作用主要是来选择策略 * 这种实现方式可以用来借鉴 */ private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() { //为上下文设置名称 defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); }
/**
*提供getSingleton()方法是对接slf4j的强制要求
*/
public static StaticLoggerBinder getSingleton() { return SINGLETON; } /** * Package access for testing purposes. * 重置 */ static void reset() { SINGLETON = new StaticLoggerBinder(); SINGLETON.init(); } /** * Package access for testing purposes. * * 1.委托ContextInitializer对象初始化LoggerContext * 2.判断上下文是否有状态监听器 如果没有就用StatusPrinter打印上下文中的警告和错误状态 * 3.把defaultLoggerContext传递给ContextSelectorStaticBinder,选择一个contextSelector 初始化成员变量 * 在getLoggerFactory()方法中通过contextSelectorBinder.getContextSelector().getLoggerContext()来获取loggerContext */ void init() { try { try { //委托ContextInitializer类对defaultLoggerContext进行初始化 //这里如果找到了任一配置文件,就会根据配置文件去初始化LoggerContext,如果没找到,会使用默认配置。 new ContextInitializer(defaultLoggerContext).autoConfig(); } catch (JoranException je) { Util.report("Failed to auto configure default logger context", je); } // logback-292 if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); } //对ContextSelectorStaticBinder类进行初始化 contextSelectorBinder.init(defaultLoggerContext, KEY); initialized = true; } catch (Exception t) { // see LOGBACK-1159 Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); } } /** * LoggerFactoryBinder接口的实现方法 获取ILoggerFactory的实现类 * 1.判断是否已经初始化 若没有则返回defaultLoggerContext * 2.若已经初始化 则通过contextSelectorBinder 返回loggerContext * @return */ public ILoggerFactory getLoggerFactory() { //如果initialized是false,那么会直接返回defaultLoggerContext if (!initialized) { return defaultLoggerContext; } //否则就委托刚才提到的ContextSelectorStaticBinder返回一个ContextSelector if (contextSelectorBinder.getContextSelector() == null) { throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL); } return contextSelectorBinder.getContextSelector().getLoggerContext(); } public String getLoggerFactoryClassStr() { return contextSelectorBinder.getClass().getName(); }
其中init()和getLoggerFactory()为核心方法。
这个初始化方法init()里做了2件事
第一件事是委托ContextInitializer类对defaultLoggerContext进行初始化。这里如果找到了任一配置文件,就会根据配置文件去初始化LoggerContext,如果没找到,会使用默认配置。关于如何根据配置文件进行配置上下文的,在后面的博客中介绍,这里先略过。
第二件事是对ContextSelectorStaticBinder类进行初始化。
我们再来大致看一下ContextSelectorStaticBinder的代码:
/** * 饿汉单例 */ static ContextSelectorStaticBinder singleton = new ContextSelectorStaticBinder(); /** * 上下文选择器 */ ContextSelector contextSelector; /** * 用来做权限控制 */ Object key;
/** * FOR INTERNAL USE. This method is intended for use by StaticLoggerBinder. * * 内部使用,这个方法是给StaticLoggerBinder类来调用的 * 这用权限控制的方式可以用来借鉴 * @param defaultLoggerContext * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { /** * key 用来做权限控制 当key对象初始化后不能变更 * 在StaticLoggerBinder中private static Object KEY = new Object();初始化一个静态对象key * 则static 只有StaticLoggerBinder类能够调用该init() */ if (this.key == null) { this.key = key; } else if (this.key != key) { throw new IllegalAccessException("Only certain classes can access this method."); } String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR); if (contextSelectorStr == null) { contextSelector = new DefaultContextSelector(defaultLoggerContext); } else if (contextSelectorStr.equals("JNDI")) { // if jndi is specified, let's use the appropriate class contextSelector = new ContextJNDISelector(defaultLoggerContext); } else { contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr); } } /** * Instantiate the context selector class designated by the user. The selector * must have a constructor taking a LoggerContext instance as an argument. * * @param defaultLoggerContext * @param contextSelectorStr * @return an instance of the designated context selector class * @throws ClassNotFoundException * @throws SecurityException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ static ContextSelector dynamicalContextSelector(LoggerContext defaultLoggerContext, String contextSelectorStr) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> contextSelectorClass = Loader.loadClass(contextSelectorStr); Constructor cons = contextSelectorClass.getConstructor(new Class[] { LoggerContext.class }); return (ContextSelector) cons.newInstance(defaultLoggerContext); } public ContextSelector getContextSelector() { return contextSelector; }
如果系统参数中配置了JNDI,这里会得到一个ContextJNDISelector,实际应用中,一般会得到一个DefaultContextSelector,并且把已经初始化完成的defaultLoggerContext传给新创建的这个DefaultContextSelector。
总结一下这个大致流程:
1、StaticLoggerBinder委托ContxetInitializer去初始化上下文,这个时候会去读取配置文件,并根据配置文件对LoggerContext进行初始化
2、然后初始化ContextSelectorStaticBinder,选择一个合适的ContextSelector并把defaultLoggerContext传递给它。
3、调用getLoggerFactory()方法,若未初始化返回defaultLoggerContext,否则委托ContextSelectorStaticBinder获取一个ContextSelector返回一个上下文。