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

前言

若是你有這樣的需求:java

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

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

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

其實這個也是我本身作的小框架,徹底開源,項目地址在github-mayoi7/timer,目前正在開發的分支是1.x,最新版本是1.2.0-RELEASEweb

使用樣例

引入依賴

首先建立一個簡單的web項目spring

而後在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,咱們裏面就添加一個方法:app

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);
    }

}

複製代碼

使用註解添加計時器

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

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,不能在普通類下使用

結束

到此,整個框架的使用教程已經徹底結束,若是有任何的問題能夠發送郵件到acerola.orion@foxmail.com

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


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

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

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

相關文章
相關標籤/搜索