如何更有效地學習開源項目的代碼?個人覺得如下幾點必不可少。
1.在下載源代碼之後,首先編譯通過、要跑起來正常運行;
2. 找到項目在正常運行時的入口點,從入口點所在的那個源文件開始閱讀,逐步把握整個項目是如何運轉的;
3.嘗試理解系統的內部結構,有多少組成部分,主要模塊是哪些?輔助模塊又是哪些?
4.從實際需要出發,修改這個項目,滿足自己的某一個小的需求。
5.看看相關的討論與心得,是否與自己的理解相一致。
本人最近在探究log4j開源項目,正是按照上面的步驟一步一步的走下來,接下來就把這段時間的學習過程及成果總結一下。
1 源碼獲取及編譯
源碼獲取可以到官網下載,也可以從github抽取;編譯方式可以使用javac,IDE工具,還有ant,maven。方式多種多樣,這裏略過。
2 源碼測試
根據源碼包提供的INSTALL說明,可以使用如下的程序作爲正常運行時的入口點。
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
public class Hello {
static Logger logger = Logger.getLogger(Hello.class);
public static void main(String argv[]) {
BasicConfigurator.configure();
logger.debug("Hello world.");
}
}
接下來貼出log4j的類圖及時序圖,先從整體上先了解一下log4j的結構。
3 架構圖之類圖
因爲圖片大小限制,再加上側重點的原因。上圖只包含了跟日誌記錄有關的邏輯完整的類圖。圖中實線表示關聯關係,虛線表示依賴關係,帶空心三角線表示繼承關係。
4 架構圖之時序圖
時序圖通過描述對象之間發送消息的時間順序顯示多個對象之間的動態協作。由於圖片大小限制,上圖顯示的是log4j組件裏主要對象之間的協作關係,某些細節的對象協作關係並沒有在圖中體現。比如:Logger對象是如何通過repository進行管理,Logger如何管理多個Appender,PatternLayout是如何創建PatternConverter的。這些內容將在後續的博文裏進行分析。
5 源碼分析
此處的代碼均來自log4j官網,由於側重點的不同,爲了分析的方便,本文刪除了一些無關的代碼。
5.1 BasicConfigurator類
/**
*使用這個類可以快速地配置log4j包,如果需要基於properties文件的配置,使用PropertyConfigurator類;如果需要xml文件的配置,使用DOMConfigurator類。
*/
public class BasicConfigurator {
/**
*添加一個使用PatternLayout佈局的ConsoleAppender,並且將其添加到root日誌記錄器。
*/
public static void configure() {
Logger root = Logger.getRootLogger();
root.addAppender(
new ConsoleAppender(
new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}
}
5.2 Logger類
/**
* 這是log4j的核心類。大部分的日誌操作,除了配置之外,都是通過這個類來實現的。
*/
public class Logger extends Category {
}
5.3 Category類
這是Logger的繼承類。
// Logger是Category的一個子類,它繼承自Category。
public class Category implements ULogger, AppenderAttachable {
// Category類的全名。
private static final String FQCN = Category.class.getName();
// category的名字。
protected String name;
// category的級別。
protected volatile Level level;
// category的父類。
protected volatile Category parent;
// 輸出目的地。該類存放多個Appender。
AppenderAttachableImpl aai;
/**
* 添加新的輸出目的地到Category的目的地列表。如果該輸出目的地已經存在於該列表,則不會再次新增。
*/
public void addAppender(Appender newAppender) {
try {
if (aai == null) {
aai = new AppenderAttachableImpl();
}
aai.addAppender(newAppender);
} finally {
}
}
/**
* 記錄調試級別的日誌。
*/
public void debug(Object message)
forcedLog(FQCN, Level.DEBUG, message, null);
}
/**
* 該方法創建一個新的日誌上下文,並且記錄相關日誌信息。
*/
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
callAppenders(new LoggingEvent(fqcn, (Logger) this, level, message, t));
}
/**
* 調用輸出目的地進行日誌記錄。
*/
public void callAppenders(LoggingEvent event) {
int writes = 0;
for (Category c = this; c != null; c = c.parent) {
try {
if (c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
} finally {
}
}
}
}
5.4 AppenderAttachableImpl類
/**
*AppenderAttachable類的實現類。
*/
public class AppenderAttachableImpl implements AppenderAttachable {
/** 輸出目的地列表 */
protected Vector appenderList;
/**
*調用所有輸出目的地的doAppend方法。
*/
public int appendLoopOnAppenders(LoggingEvent event) {
int size = 0;
Appender appender;
if (appenderList != null) {
size = appenderList.size();
for (int i = 0; i < size; i++) {
appender = (Appender) appenderList.elementAt(i);
appender.doAppend(event);
}
}
return size;
}
}
5.5 ConsoleAppender類
/**
* ConsoleAppender使用用戶指定的格式將日誌輸出到System.out或者System.err。
*/
public class ConsoleAppender extends WriterAppender {
/**
* 構造一個輸了目的地。
*/
public ConsoleAppender(final Layout layout) {
setLayout(layout);
}
}
5.6 WriterAppender類
這是ConsoleAppender的父類。
/**
* WriterAppender輸出日誌到java.io.Writer或者java.io.OutputStream,這依賴於用戶的選擇。
*/
public class WriterAppender extends AppenderSkeleton {
/**
* 這是一個我們將要記錄日誌的地方QuietWriter。
*/
protected QuietWriter qw;
/**
*該方法由AppenderSkeleton類的doAppend方法調用。
*/
public void append(LoggingEvent event) {
if (!checkEntryConditions()) {
return;
}
subAppend(event);
}
/**
* 這是實際進行日誌記錄的方法。
* 大部分WriterAppender的子類需要重寫這個方法。
*/
protected void subAppend(LoggingEvent event) {
// 調用layout的format方法對日誌信息進行格式化,再將其輸出到相應的appender。
this.qw.write(this.layout.format(event));
}
}
5.7 AppenderSkeleton類
這是WriterAppender的父類。
/**
* 這是log4j包裏其它輸出目的地的抽象父類。這個類提供了公用功能的代碼。
*/
public abstract class AppenderSkeleton extends ComponentBase implements Appender, OptionHandler {
/**
* 佈局變量。如果輸出目的地實現類有指定佈局,則不需要設置該變量。
*/
protected Layout layout;
/**
* 輸出目的地的名稱。
*/
protected String name;
/**
* 設置輸出目的地的佈局。注意:有些輸出目的地有它們自己的佈局而無需指定該變量。
*/
public void setLayout(Layout layout) {
this.layout = layout;
}
/**
* 這個方法執行一些日誌記錄前的檢查然後調用子類的日誌記錄功能。
*/
public synchronized void doAppend(LoggingEvent event) {
... ...
// 調用子類WriterAppender類的append方法。
this.append(event);
}
}
5.8 PatternLayout類
/**
* 一個靈活的使用模式字符串的佈局。這個類的目標是格式化一個日誌上下文並返回結果。
*
public class PatternLayout extends Layout {
public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n";
/**
* 模式轉換器。
*/
private PatternConverter head;
/**
* 轉換模式。
*/
private String conversionPattern;
/**
* 使用給定的模式字符串構造一個模式佈局類。
* @param pattern conversion pattern.
*/
public PatternLayout(final String pattern) {
this.conversionPattern = pattern;
head = createPatternParser(
(pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse();
}
/**
* 返回一個模式轉換器的解析器。
*/
protected org.apache.log4j.helpers.PatternParser createPatternParser(String pattern) {
return new org.apache.log4j.pattern.BridgePatternParser(pattern,repository, getLogger());
}
/**
* 格式化一個日誌上下文到writer。
*/
public String format(final LoggingEvent event) {
StringBuffer buf = new StringBuffer();
for(PatternConverter c = head;
c != null;
c = c.next) {
c.format(buf, event);
}
return buf.toString();
}
}
5.9 解析器BridgePatternParser類
/**
* 該類實現於log4j 1.3的org.apache.log4j.helpers.PatternConverter類。
*/
public final class BridgePatternParser extends org.apache.log4j.helpers.PatternParser {
/**
* 創建一個新的模式轉換器。
* @return pattern converter.
*/
public org.apache.log4j.helpers.PatternConverter parse() {
return new BridgePatternConverter(pattern, repository, logger);
}
}
5.10 轉換器BridgePatternConverter類
/**
* 該類實現了log4j 1.3的org.apache.log4j.helpers.PatternConverter類。
*/
public final class BridgePatternConverter extends org.apache.log4j.helpers.PatternConverter {
/**
* 模式轉換器數組。
*/
private LoggingEventPatternConverter[] patternConverters;
/**
* 日誌的長度和排列規則。
*/
private FormattingInfo[] patternFields;
/**
* 構造一個BridgePatternConverter。
*/
public BridgePatternConverter(final String pattern, final LoggerRepository repository,final ULogger logger) {
patternConverters = new LoggingEventPatternConverter[converters.size()];
patternFields = new FormattingInfo[converters.size()];
Iterator converterIter = converters.iterator();
Iterator fieldIter = fields.iterator();
while (converterIter.hasNext()) {
Object converter = converterIter.next();
if (converter instanceof LoggingEventPatternConverter) {
patternConverters[i] = (LoggingEventPatternConverter) converter;
} else {
patternConverters[i] = new org.apache.log4j.pattern.LiteralPatternConverter("");
}
if (fieldIter.hasNext()) {
patternFields[i] = (FormattingInfo) fieldIter.next();
} else {
patternFields[i] = FormattingInfo.getDefault();
}
}
}
/**
格式化日誌上下文到string buffer.
*/
public void format(final StringBuffer sbuf, final LoggingEvent e) {
for (int i = 0; i < patternConverters.length; i++) {
int startField = sbuf.length();
patternConverters[i].format(e, sbuf);
patternFields[i].format(startField, sbuf);
}
}
}
5.11 轉換器LoggingEventPatternConverter類
/**
* LoggingEventPatternConverter是一個模式轉換器的基礎類,可以從日誌上下文轉換信息。
*/
public abstract class LoggingEventPatternConverter extends PatternConverter {
/**
* 格式化一個日誌上下文到StringBuffer.
*/
public abstract void format(final LoggingEvent event, final StringBuffer toAppendTo);
/**
* {@inheritDoc}
*/
public void format(final Object obj, final StringBuffer output) {
if (obj instanceof LoggingEvent) {
// 調用子類LiteralPatternConverter的format方法。
format((LoggingEvent) obj, output);
}
}
5.12 LiteralPatternConverter類
這是LoggingEventPatternConver的子類。
/**
* 格式化一個字符串。
*/
public final class LiteralPatternConverter extends LoggingEventPatternConverter {
private final String literal;
/**
* 構造一個新實例。
*/
public LiteralPatternConverter(final String literal) {
super("Literal", "literal");
this.literal = literal;
}
/**
* {@inheritDoc}
*/
public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
toAppendTo.append(literal);
}
}
5.13 FormattingInfo類
/**
* 根據一個指定的最小和最大寬度和排列來修改模式轉換器的輸出。
*/
public final class FormattingInfo {
/**
* 根據指定的長度和排列方式來調整buffer的內容。
*/
public final void format(final int fieldStart, final StringBuffer buffer) {
final int rawLength = buffer.length() - fieldStart;
if (rawLength > maxLength) {
buffer.delete(fieldStart, buffer.length() - maxLength);
} else if (rawLength < minLength) {
if (leftAlign) {
final int fieldEnd = buffer.length();
buffer.setLength(fieldStart + minLength);
for (int i = fieldEnd; i < buffer.length(); i++) {
buffer.setCharAt(i, ' ');
}
} else {
int padLength = minLength - rawLength;
for (; padLength > 8; padLength -= 8) {
buffer.insert(fieldStart, SPACES);
}
buffer.insert(fieldStart, SPACES, 0, padLength);
}
}
}
}