如果你有这样的需求:
想要计算自己的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次(电脑比较渣,之前开了太多被卡死机了),这里截取了一小段输出:
[(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个属性,即刚才输出的那些:
这些属性大部分都可以在有限基础上自行修改,至于如何自定义我们接下来会进行讲解
配置项可以分为配置文件中的配置,和注解上的配置,我们分开来讲
配置文件中以 timer 为前缀的是我们计时器的配置项,有两大类, timer.format 和 timer.mode ,我们单独来说
timer.format该类配置是用于配置输出格式,自由度较高,所以统一放在一起,包含有:
timer.format.fileFormat :日志文件名,包含了文件后缀(如果不输出到日志则无效) timer.format.logPath :日志输出的绝对路径,即“/”为当前磁盘根目录(如果不输出到日志则无效) timer.format.dateFormat :日期输出格式,形如“yyyy-MM-dd HH:mm:ss”,和 SimpleFormat 一致 timer.format.formatterPath :自定义格式化器类的全路径(待会讲到了会说) timer.mode该类配置是用于一些既定输出模式的选择,均为枚举类型,所以统一放在一起,包含有:
timer.mode.timeMode :时间输出方式,目前仅有 simple 一种格式 timer.mode.unit :时间单位,可选范围为 TimeUnit 的所有枚举类 timer.mode.methodMode :方法名输出方式,有 simple 和 param 两种方式,分别是仅输出方法名,以及输出方法名和参数 timer.mode.classMode :类名输出方式,有 full 和 simple 两种方式,分别是类全路径输出,以及仅输出简单类名 以下是一份样例配置文件(暂时没有配置自定义格式化器):
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 注解中,目前有效的配置有以下几个:
ResultPosition.CONSOLE 和 ResultPosition.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 的内部类,用于获取到输出属性,需要覆写其中的 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 类中,再次添加一个内部类 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 属性就为我们修改后的属性了,具体内容不再测试了,感兴趣的可以自行实验