在上一篇博文中對log4j的結構有了一定的瞭解,這一篇就深入地瞭解一下其中的LogManager類。首先,先了解一下與LogManager相關的類結構;然後,再仔細地說明每個類的作用及其之間的關係。
1 類圖
圖中的方框代表類,圓圈代表接口,實線箭頭代表關聯關係(箭頭的一方爲非箭頭一方的屬性),虛線箭頭代表依賴關係(箭頭的一方爲非箭頭一方的方法參數或局部變量),實線三角箭頭表示繼承關係。
從圖中可知,LogManager類有RepositorySelector屬性和LoggerRepository屬性,並且依賴於Configurator接口。LoggerRepository類有LoggerFactory屬性。DefaultLoggerFactory類有Logger屬性。接下來簡要介紹一下這些類和接口的作用。
LogManager類將Configurator和LoggerRepository整合在一起,它在初始化的時候(static)找到Log4J配置文件,並且將其解析到LoggerRepository中。
Configurator用來處理相關的配置文件。Log4J支持兩種配置文件:properties文件和xml文件。
RepositorySelector負責緩存和管理LoggerRepository對象。
LoggerRepository是一個Logger的容器,它會調用LoggerFactory創建Logger並緩存Logger實例,從而具有相同名字的Logger實例不會多次創建,以提高性能。調用Configurator對配置文件進行解析,並將解析後的信息添加到LoggerRepository中。同時它也維護了Logger之間的關係,因爲在Log4J中,所有Logger都組裝成以RootLogger爲根的一棵樹,樹的層次由Logger的Name來決定,其中以”.”分隔。
Hierarchy是Log4J中默認對LoggerRepository的實現類,它用於表達其內部的Logger是以層次結構存儲的。在對LoggerRepository接口的實現中,getLogger()方法是其最核心的實現。
2 RootLogger類
在Log4J中,所有Logger都組裝成以RootLogger爲根的一棵樹。
public final class RootLogger extends Logger {
/**
* 使用日誌級別來構造一個日誌記錄器,記錄器名稱默認爲root。
*/
public RootLogger(Level level) {
super("root");
setLevel(level);
}
}
3 LoggerFactory類
這裏使用了創建型設計模式的工廠(Factory)模式。所有需要創建新Logger的方法都可以調用該接口的makeNewLoggerInstance方法。
/**
*實現該接口可以創建新的日誌記錄器實例。
*/
public interface LoggerFactory {
public Logger makeNewLoggerInstance(String name);
}
4 LoggerFactory的實現類
該實現類爲DefaultLoggerFactory。
public final class DefaultLoggerFactory implements LoggerFactory {
public DefaultLoggerFactory() {}
public Logger makeNewLoggerInstance(String name) {
return new Logger(name);
}
}
5 Configurator類
Configurator用來處理相關的配置文件。Log4J支持兩種配置文件:properties文件和xml文件。如下是配置器接口:
public interface Configurator {
}
6 Configurator的抽象實現類
該抽象實現類是ConfiguratorBase類。
/**
* 該類包含了一個大部分配置類都需要的功能。
*/
abstract public class ConfiguratorBase implements Configurator {
}
7 ConfiguratorBase的property實現類
該實現類是PropertyConfigurator類,負責對property屬性文件進行解析。
public class PropertyConfigurator extends ConfiguratorBase
implements ConfiguratorEx {
}
8 ConfiguratorBase的XML實現類
該實現類是JoranConfigurator類。負責對XML配置文件進行解析。
public class JoranConfigurator extends ConfiguratorBase
implements ConfiguratorEx {
}
9 LoggerRepository接口
LoggerRepository它是一個Logger的容器,它會調用LoggerFactory創建Logger並緩存Logger實例,從而具有相同名字的Logger實例不會多次創建,以提高性能。調用Configurator對配置文件進行解析,並將解析後的信息添加到LoggerRepository中。
同時它也維護了Logger之間的關係,因爲在Log4J中,所有Logger都組裝成以RootLogger爲根的一棵樹,樹的層次由Logger的Name來決定,其中以”.”分隔。
除此之外,它還有一個Threshold屬性,用於過濾所有在Threshold級別以下的日誌。以及其他和Logger操作相關的方法和屬性。
public interface LoggerRepository {
// 添加一個事件到repository.
public void addHierarchyEventListener(HierarchyEventListener listener);
public void setThreshold(Level level);
public void setThreshold(String val);
public Level getThreshold();
public Logger getLogger(String name);
public Logger getLogger(String name, LoggerFactory factory);
public Logger getRootLogger();
public abstract Logger exists(String name);
public abstract void shutdown();
public Enumeration getCurrentLoggers();
public abstract void fireAddAppenderEvent(Category logger, Appender appender);
public abstract void resetConfiguration();
}
10 LoggerRepository的子接口
public interface LoggerRepositoryEx extends LoggerRepository {
void addLoggerRepositoryEventListener(LoggerRepositoryEventListener listener);
void removeLoggerRepositoryEventListener(LoggerRepositoryEventListener listener);
void addLoggerEventListener(LoggerEventListener listener);
void removeLoggerEventListener(LoggerEventListener listener);
String getName();
void setName(String repoName);
void fireRemoveAppenderEvent(Category logger, Appender appender);
void fireLevelChangedEvent(Logger logger);
void fireConfigurationChangedEvent();
PluginRegistry getPluginRegistry();
Map getProperties();
String getProperty(String key);
void setProperty(String key, String value);
void setLoggerFactory(LoggerFactory loggerFactory);
LoggerFactory getLoggerFactory();
}
11 LoggerRepositoryEx的實現類
該實現類是Hierarchy類。
Hierarchy是Log4J中默認對LoggerRepository的實現類,它用於表達其內部的Logger是以層次結構存儲的。在對LoggerRepository接口的實現中,getLogger()方法是其最核心的實現。
Hierarchy中用一個Hashtable來存儲所有Logger實例,它以CategoryKey作爲key,Logger作爲value,其中CategoryKey是對Logger中Name字符串的封裝,之所以要引入這個類是出於性能考慮,因爲它會緩存Name字符串的hash code,這樣在查找過程中計算hash code時就可以直接取得而不用每次都計算。
class CategoryKey {
String name;
int hashCache;
CategoryKey(String name) {
this.name = name;
hashCache = name.hashCode();
}
}
getLogger()方法首先根據傳入name創建CategoryKey實例,而後從緩存ht字段中查找:
1. 如果找到對應的Logger實例,則直接返回該實例。
2. 如果沒有找到任何實例,則使用LoggerFactory創建新的Logger實例,並將該實例緩存到ht集合中,同時更新新創建Logger實例的parent屬性。更新parent屬性最簡單的做法是從後往前以’.’爲分隔符截取字符串,使用截取後的字符串從ht集合中查找是否存在Logger實例,如果存在,則新創建的Logger實例的parent即爲找到的實例,若在整個遍歷過程中都沒有找到相應的parent實例,則其parent實例爲root。然而如果一個“x.y.z.w”Logger起初的parent設置爲root,而後出現“x.y.z”Logger實例,那麼就需要更新“x.y.z.w”Logger的parent爲“x.y.z”Logger實例,此時就會遇到一個如何找到在集合中已經存在的“x.y.z”Logger實例子節點的問題。當然一種簡單的做法是遍歷ht集合中所有實例,判斷那個實例是不是“x.y.z”Logger實例的子節點,是則更新其parent節點。由於每次的遍歷會引起一些性能問題,因而Log4J使用ProvisionNode事先將所有的可能相關的子節點保存起來,並將ProvisionNode實例添加到ht集合中,這樣只要找到對應的ProvisionNode實例,就可以找到所有相關的子節點了。比如對“x.y.z.w”Logger實例,它會產生三個ProvisionNode實例(當然如果相應的實例已經存在,則直接添加而無需創建,另外,如果相應節點已經是Logger實例,那麼將“x.y.z.w”Logger實例的parent直接指向它即可):ProvisionNode(“x”), ProvisionNode(“x.y”), ProvisionNode(“x.y.z”),他們都存儲了“x.y.z.w”Logger實例作爲其子節點。
public class Hierarchy implements LoggerRepositoryEx, RendererSupport {
private LoggerFactory loggerFactory;
private final ArrayList repositoryEventListeners;
private final ArrayList loggerEventListeners;
String name;
Hashtable ht; // Loggers所在的Hashtable.
Logger root; // Root logger.
/**
* 構造一個logger hierarchy.
*/
public Hierarchy(final Logger rootLogger) {
ht = new Hashtable();
repositoryEventListeners = new ArrayList(1);
loggerEventListeners = new ArrayList(1);
this.root = rootLogger;
this.objectMap = new HashMap();
// Enable all level levels by default.
setThreshold(Level.ALL);
this.root.setHierarchy(this);
rendererMap = new RendererMap();
rendererMap.setLoggerRepository(this);
properties = new Hashtable();
loggerFactory = new DefaultLoggerFactory();
}
public Logger getLogger(final String loggerName) {
return getLogger(loggerName, loggerFactory);
}
public Logger getLogger(final String loggerName, final LoggerFactory factory) {
CategoryKey key = new CategoryKey(loggerName);
Logger logger;
synchronized (ht) {
Object o = ht.get(key);
if (o == null) {
LogLog.debug("Creating new logger [" + loggerName + "] in repository [" + getName() + "].");
logger = factory.makeNewLoggerInstance(loggerName);
logger.setHierarchy(this);
ht.put(key, logger);
updateParents(logger);
return logger;
} else if (o instanceof Logger) {
LogLog.debug("Returning existing logger [" + loggerName + "] in repository [" + getName() + "].");
return (Logger) o;
} else if (o instanceof ProvisionNode) {
logger = factory.makeNewLoggerInstance(loggerName);
logger.setHierarchy(this);
ht.put(key, logger);
updateChildren((ProvisionNode) o, logger);
updateParents(logger);
return logger;
} else {
// 代碼運行時不會到達這裏。
return null; // 但是這樣可以避免編譯器告警。
}
}
}
}
12 RepositorySelector接口
RepositorySelector負責緩存和管理LoggerRepository對象。
public interface RepositorySelector {
public LoggerRepository getLoggerRepository();
}
13 RepositorySelector的子接口
該子接口爲RepositorySelectorEx 接口。
public interface RepositorySelectorEx extends RepositorySelector {
LoggerRepository detachRepository(String name);
}
14 RepositorySelectorEx的實現類
該實現類爲DefaultRepositorySelector類。
public class DefaultRepositorySelector implements RepositorySelectorEx {
private LoggerRepository defaultRepository;
public DefaultRepositorySelector(final LoggerRepository repository) {
if (repository == null)
throw new NullPointerException();
this.defaultRepository = repository;
}
public LoggerRepository getLoggerRepository() {
return defaultRepository;
}
}
15 LogManager類
LogManager將Configurator和LoggerRepository整合在一起,它在初始化的時候(static)找到Log4J配置文件,並且將其解析到LoggerRepository中。
public class LogManager {
private static RepositorySelector repositorySelector;
private static boolean debug = false;
/**
* 默認的LoggerRepository實例。
*/
public static final LoggerRepository defaultLoggerRepository;
// 初始化部分。
static {
String debugProp = System.getProperty("log4j.debug");
if(Boolean.valueOf(debugProp).booleanValue()) {
debug = true;
}
if(debug) {
System.out.println("**Start of LogManager static initializer");
}
Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
defaultLoggerRepository = hierarchy;
hierarchy.setName(Constants.DEFAULT_REPOSITORY_NAME);
repositorySelector = new DefaultRepositorySelector (defaultLoggerRepository);
String configuratorClassName = OptionConverter.getSystemProperty(Constants.CONFIGURATOR_CLASS_KEY, null);
String configurationOptionStr = OptionConverter.getSystemProperty(Constants.DEFAULT_CONFIGURATION_KEY, null);
if (configurationOptionStr == null) {
if (Loader.getResource(Constants.DEFAULT_XML_CONFIGURATION_FILE) != null) {
configurationOptionStr = Constants.DEFAULT_XML_CONFIGURATION_FILE;
} else if (Loader.getResource(Constants.DEFAULT_CONFIGURATION_FILE) != null) {
configurationOptionStr = Constants.DEFAULT_CONFIGURATION_FILE;
}
}
if(debug) {
System.out.println("*** configurationOptionStr=" + configurationOptionStr);
}
// 整合Configurator和LoggerRepository部分。
IntializationUtil.initialConfiguration(defaultLoggerRepository, configurationOptionStr, configuratorClassName);
String repositorySelectorStr = OptionConverter.getSystemProperty("log4j.repositorySelector", null);
if (repositorySelectorStr == null) {
// 不做任何事情, 默認的repository已經在前面配置好了。
} else if (repositorySelectorStr.equalsIgnoreCase("JNDI")) {
if(debug) {
System.out.println("*** Will use ContextJNDISelector **");
}
repositorySelector = new ContextJNDISelector();
guard = new Object();
} else {
Object r = OptionConverter.instantiateByClassName(repositorySelectorStr, RepositorySelector.class, null);
if (r instanceof RepositorySelector) {
if(debug) {System.out.println("*** Using [" + repositorySelectorStr+ "] instance as repository selector.");
}
repositorySelector = (RepositorySelector) r;
guard = new Object();
} else {
if(debug) {System.out.println("*** Could not insantiate [" + repositorySelectorStr+ "] as repository selector.");
System.out.println("*** Using default repository selector");
}
repositorySelector = new DefaultRepositorySelector(defaultLoggerRepository);
}
}
if(debug) {
System.out.println("** End of LogManager static initializer");
}
}
public static Logger getLogger(String name) {
return repositorySelector.getLoggerRepository().getLogger(name);
}
}