不要再糾結如何打印方法執行用時了,這個東西能幫你

NO IMAGE

前言

如果你有這樣的需求:

想要計算自己的spring項目中某些方法的執行用時,且每次執行時自動輸出

那麼這個框架就很適合你了,目前,這個計時框架有以下優點:

  • 使用簡單,一行註解即可生效
  • 支持高度自定義的輸出格式
  • 對方法無侵入
  • 自由選擇輸出到控制檯或日誌

其實這個也是我自己做的小框架,完全開源,項目地址在github-mayoi7/timer,目前正在開發的分支是1.x,最新版本是1.2.0-RELEASE

使用樣例

引入依賴

首先創建一個簡單的web項目

不要再糾結如何打印方法執行用時了,這個東西能幫你

然後在pom.xml中引入我們項目需要的依賴:

    <properties>
<java.version>1.8</java.version>
<timer.version>1.2.0-RELEASE</timer.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 計時器的依賴 -->
<dependency>
<groupId>com.github.mayoi7</groupId>
<artifactId>timer</artifactId>
<version>${timer.version}</version>
</dependency>
</dependencies>

創建基本結構

然後接下來新建配置文件application.yml,不過我們這裡可以什麼都先不寫

接著新建一個Controller,我們裡面就添加一個方法:

import com.github.mayoi7.timer.anno.Timer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@RequestMapping("hi")
@Timer
public void hello() {
try {
Thread.sleep(826);
} catch (Exception ignore) {}
System.out.println("Hello World...");
}
}

這裡要注意一定要把我們com.github.mayoi7這個包下的類掃描進來,所以可以採用以下的配置方式:

@SpringBootApplication
// 下面兩種配置任選一(com.example.demo是當前項目的源碼包根目錄)
// @ComponentScan(basePackages = "com.*")
@ComponentScan(basePackages = {"com.example.demo.*", "com.github.mayoi7.*"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

使用註解添加計時器

好了,然後就到了重頭戲,如何給這個方法計時呢?既然我們引入了依賴,這裡直接一個註解就夠了:

import com.github.mayoi7.timer.anno.Timer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@RequestMapping("hi")
@Timer
public void hello() {
try {
Thread.sleep(826);
} catch (Exception ignore) {}
System.out.println("Hello World...");
}
}

測試

我們啟動服務器,訪問localhost:8080/hi,會發現控制檯會打印出結果:

Hello World...
[(date=2019-06-08 22:22:31, name=825608c3, duration=830 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]

我們來用JMeter測試下併發下的表現,這裡同時開了1000個線程循環1000次(電腦比較渣,之前開了太多被卡死機了),這裡截取了一小段輸出[1]

[(date=2019-06-08 22:36:08, name=0753f926, duration=826 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]
[(date=2019-06-08 22:36:08, name=a0a242cd, duration=826 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]
Hello World...
[(date=2019-06-08 22:36:08, name=96b67174, duration=827 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]
Hello World...
Hello World...
[(date=2019-06-08 22:36:08, name=26676684, duration=827 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]
[(date=2019-06-08 22:36:08, name=fc283e48, duration=827 ms, classInfo=com.example.demo.controller.DemoController, methodInfo=hello)]

可以發現輸出的結果還是準確的

使用教程

輸出屬性含義

目前可供輸出的有5個屬性,即剛才輸出的那些:

  • date:方法執行完畢的時間,也是結果輸出的時間
  • name:計時器名稱,可以自行設置,如果沒有設置則會使用一個隨機8位字符串
  • duration:方法執行用時
  • classInfo:方法所在類的信息
  • methodInfo:方法信息

這些屬性大部分都可以在有限基礎上自行修改,至於如何自定義我們接下來會進行講解

配置項

配置項可以分為配置文件中的配置,和註解上的配置,我們分開來講

配置文件

配置文件中以timer為前綴的是我們計時器的配置項,有兩大類,timer.formattimer.mode,我們單獨來說

timer.format
該類配置是用於配置輸出格式,自由度較高,所以統一放在一起,包含有:

  • timer.format.fileFormat:日誌文件名,包含了文件後綴(如果不輸出到日誌則無效)
  • timer.format.logPath:日誌輸出的絕對路徑,即“/”為當前磁盤根目錄(如果不輸出到日誌則無效)
  • timer.format.dateFormat:日期輸出格式,形如“yyyy-MM-dd HH:mm:ss”,和SimpleFormatter一致
  • timer.format.formatterPath:自定義格式化器類的全路徑(待會講到了會說)

timer.mode
該類配置是用於一些既定輸出模式的選擇,均為枚舉類型,所以統一放在一起,包含有:

  • timer.mode.timeMode:時間輸出方式,目前僅有simple一種格式
  • timer.mode.unit:時間單位,可選範圍為TimeUnit的所有枚舉類
  • timer.mode.methodMode:方法名輸出方式,有simpleparam兩種方式,分別是僅輸出方法名,以及輸出方法名和參數
  • timer.mode.classMode:類名輸出方式,有fullsimple兩種方式,分別是類全路徑輸出,以及僅輸出簡單類名

以下是一份樣例配置文件(暫時沒有配置自定義格式化器):

timer:
format:
file-format: timer-demo.log
log-path: /
date-format: yyyy-MM-dd HH:mm:ss
mode:
time-mode: simple
unit: milliseconds
method-mode: param
class-mode: full

註解配置

@Timer註解中,目前有效的配置有以下幾個:

  • name:計時器的名稱,如果不設置,則會默認使用隨機生成的8位字符串
  • unit:時間單位,默認為毫秒,在這裡配置的優先級會高於配置文件
  • formatter:自定義的格式化器類路徑,優先級高於配置文件
  • position:結果輸出的位置,可選項有ResultPosition.CONSOLEResultPosition.LOG,默認輸出到控制檯

樣例配置如下:

@Timer(name = "timer1", unit = TimeUnit.SECONDS, position = ResultPosition.LOG)

定製

如果剛才的這些配置不能夠滿足你的需要,這裡還提供了高自由度的自定義配置

自定義輸出格式

默認的輸出格式不好看?沒關係,現在教你如何自定義輸出的格式

首先,我們在創建一個timer包,然後在包下建一個類,就叫MyFormatter,然後繼承AbstractFormatter,注意千萬不要引錯包:

import com.github.mayoi7.timer.format.AbstractFormatter;
import com.github.mayoi7.timer.props.TimerOutputSource;
import com.github.mayoi7.timer.props.TimerProperties;
public class MyFormatter extends AbstractFormatter {
public MyFormatter(TimerProperties properties, TimerOutputSource source) {
super(properties, source);
}
}

這裡構造函數裡兩個對象我先說明一下,properties是我們所設置的各項配置,source是我們計時器的輸出結果,不過這些都不用我們手動設置

然後我們在這個類中定義一個MyRecevier的內部類[2],用於獲取到輸出屬性,需要覆寫其中的output()方法,並在我們自定義的MyFormatter格式化器類中重寫getInfoReceiver()方法,返回我們的MyReceiver對象:

import com.github.mayoi7.timer.format.AbstractFormatter;
import com.github.mayoi7.timer.props.TimerOutputSource;
import com.github.mayoi7.timer.props.TimerProperties;
public class MyFormatter extends AbstractFormatter {
public MyFormatter(TimerProperties properties, TimerOutputSource source) {
super(properties, source);
}
private static class MyReceiver extends InfoReceiver {
@Override
public String output() {
return "\n[myFormatter]" + date + "-" + duration;
}
}
@Override
public InfoReceiver getInfoReceiver() {
return new MyReceiver();
}
}

output()方法中,可供使用的屬性有以下5個(從InfoReceiver中得知):

    class InfoReceiver {
/** 日期 */
protected String date;
/** 名稱 */
protected String name;
/** 執行時間 */
protected String duration;
/** 類信息 */
protected String classInfo;
/** 方法信息 */
protected String methodInfo;
// ...
}

最後,最重要的一步來了,把MyFormatter類的路徑通知給計時器,有配置文件和註解配置兩種方式,我這裡就在註解中配置了:

@Timer(formatter = "com.example.demo.timer.MyFormatter")

然後運行測試,會發現輸出的結果改為我們配置的格式了:

Hello World...
[myFormatter]2019-06-08 23:34:20-828 ms

獲取更多的信息

當然,僅僅改個格式還不夠,如果你想獲取更多的關於被計時方法和其所在類的信息,我們也提供了自定義的手段

比如,你想在類信息輸出的FULL模式下獲取更多的內容,我們就需要回到MyFormatter類中,再次添加一個內部類[3]MyClassFormatter,並繼承ClassFormatter,選擇性覆寫其中的方法:

import com.github.mayoi7.timer.format.AbstractFormatter;
import com.github.mayoi7.timer.props.TimerOutputSource;
import com.github.mayoi7.timer.props.TimerProperties;
public class MyFormatter extends AbstractFormatter {
// ...
private static class MyClassFormatter extend ClassFormatter {
@Override
public String formatInFull(Class clazz) {
return clazz.getName() + "-" + clazz.getTypeName();
}
} 
}

然後最重要的一點是,將這個新聲明的類在構造方法中賦予對應的屬性:

public class MyFormatter extends AbstractFormatter {
public MyFormatter(TimerProperties properties, TimerOutputSource source) {
// ...
classFormatter = new MyClassFormatter();
}

同樣地,我們也為方法信息提供了對應的MethodFormatter類和methodFormatter屬性,使用方法基本一致,不再進行演示

到此,配置已經完成,如果我們開啟了類信息輸出的FULL模式,則剛才的MyReceiver中的classInfo屬性就為我們修改後的屬性了,具體內容不再測試了,感興趣的可以自行實驗

注意事項

  • 一定要開啟@ComponentScan註解,不管怎麼配置,一定要把com.github.mayoi7.*下的所有類掃描到
  • 註解中的配置優先級高於配置文件,但是如果在註解中配置時間單位為毫秒時,優先級會降至最低
  • 目前項目還處於不穩定的階段,可能會對方法執行時間有些許的影響
  • 該計時框架是基於spring-aop的,所以只能作用於spring的bean,不能在普通類下使用

結束

到此,整個框架的使用教程已經完全結束,如果有任何的問題可以發送郵件到[email protected]

最後,項目地址在github-mayoi7/timer,感興趣的可以點個星星啦


  1. 聚合報告的結果顯示加了註解之後響應時間慢了,但是錯誤率低了,可能是測試的方式有問題,我對於JMeter用的也不算熟練,這個報告沒什麼參考價值就暫且先不放 ↩︎

  2. 雖然這個類的聲明位置不限,不過推薦聲明為自定義格式化器的內部類 ↩︎

  3. 同樣地,這個類聲明位置不限 ↩︎

相關文章

隨便分享點不那麼常規的面試題(一)

淺談零拷貝機制

淺談ForkJoinPool

Java中LRU的實現