分析堆棧信息封裝一個SLF4J的靜態類

前言

當我們在使用日誌框架的時候,每個類都要通過工廠方法獲取一個日誌對象來打印日誌,感覺太麻煩了。所以想着去封裝一個日誌靜態類。但是問題是封裝的那個靜態日誌類打印出的類信息都是日誌類自己,這肯定不是我們想要的啊,我們需要的是當前調用打印日誌的這個類的信息啊。

開始思考

我們發現平時使用的Logback等日誌框架能準確捕獲源代碼的所在的類、方法、行。但java並沒有提供相應的方法,這似乎很神奇。其實Logback是通過java錯誤堆棧來實現的,也就是說通過new一個異常Throwable,然後再捕獲,從而得到堆棧信息,在進行分析就可以得到行號等信息了。

我們可以借鑑這種思想,在靜態日誌類中,獲取當前線程的堆棧信息,然後找到實際調用者。JDK的Thread類提供了一個getStackTrace()可以獲取線程的堆棧跟蹤鏈。下面來看一下這個方法。

/**
* 返回一個表示該線程堆棧轉儲的堆棧跟蹤元素數組。如果該線程尚未啓動或已經終止,則該方法將返回一個零長度數組。
* 如果返回的數組不是零長度的,則其第一個元素代表堆棧頂,它是該序列中最新的方法調用。最後一個元素代表堆棧底,是該序列中最舊的方法調用。
*/
public StackTraceElement[] getStackTrace() {
    // 如果該線程不是當前線程
    if (this != Thread.currentThread()) {
        // 獲取安全管理器
        SecurityManager security = System.getSecurityManager();
        // 如果有安全管理器
        if (security != null) {
            // 檢查是否有權限獲取堆棧跟蹤
            security.checkPermission(SecurityConstants.GET_STACK_TRACE_PERMISSION);
        }
        // 如果線程未啓動或已終止,則不會獲取堆棧跟蹤信息,直接返回一個空堆棧
        if (!isAlive()) {
            return EMPTY_STACK_TRACE;
        }
        StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
        StackTraceElement[] stackTrace = stackTraceArray[0];
        // 可能線程剛剛終止,沒有獲取到堆棧跟蹤,也返回一個空堆棧
        if (stackTrace == null) {
            stackTrace = EMPTY_STACK_TRACE;
        }
        return stackTrace;
    } else {
        // new一個Exception,然後捕獲當前線程的堆棧信息並返回
        return (new Exception()).getStackTrace();
    }
}

那麼堆棧信息時啥樣的呢,下面打印一個看看。

java.lang.Thread.getStackTrace(Thread.java:1559)
util.Logger.getCaller(Logger.java:20)
util.Logger.log(Logger.java:60)
util.Logger.info(Logger.java:99)
byteBuf.test1.main(test1.java:36)

這是調用靜態日誌的方法得到的堆棧信息。可以看到日誌類的堆棧信息的後面緊跟着的就是調用日誌方法的類。要拿到的就是這個調用類,然後通過這個類名構造一個日誌對象,真正的日誌打印是通過這個對象來執行的。

靜態日誌類

public class Logger {

	// 當前日誌類名
	private final static String logClassName = Logger.class.getName();

	/**
	 * 獲取最原始被調用的堆棧信息
	 */
	private static StackTraceElement getCaller() {

		// 獲取堆棧信息
		StackTraceElement[] traceElements = Thread.currentThread()
				.getStackTrace();
		if (null == traceElements) {
			return null;
		}

		// 最原始被調用的堆棧信息
		StackTraceElement caller = null;

		// 循環遍歷到日誌類標識
		boolean isEachLogFlag = false;

		// 遍歷堆棧信息,獲取出最原始被調用的方法信息
		// 當前日誌類的堆棧信息完了就是調用該日誌類對象信息
		for (StackTraceElement element : traceElements) {
			// 遍歷到日誌類
			if (element.getClassName().equals(logClassName)) {
				isEachLogFlag = true;
			}

			// 下一個非日誌類的堆棧,就是最原始被調用的方法
			if (isEachLogFlag) {
				if (!element.getClassName().equals(logClassName)) {
					caller = element;
					break;
				}
			}
		}

		return caller;
	}

	/**
	 * 自動匹配請求類名,生成logger對象
	 */
	private static org.slf4j.Logger log() {
		// 最原始被調用的堆棧對象
		StackTraceElement caller = getCaller();
		// 空堆棧處理
		if (caller == null) {
			return LoggerFactory.getLogger(Logger.class);
		}

		// 取出被調用對象的類名,並構造一個Logger對象返回
		return LoggerFactory.getLogger(caller.getClassName());
	}

	// 下列是封裝後的方法
    // 省略其它方法的封裝
	public static void info(String message) {
		log().info(message);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章