背景

在业务开发中,我们经常需要对代码执行的耗时进行监控。Spring 有一个org.springframework.util.StopWatch工具类实现了该功能。

基本使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void testSpringStopWatch() {
StopWatch stopWatch = null;
try {
stopWatch = new StopWatch();
stopWatch.start();
// mock biz
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (stopWatch != null) {
stopWatch.stop();
}
}
log.info("执行耗时{}ms",stopWatch.getTotalTimeMillis());
}

除了我们需要执行的业务逻辑代码之外,我们还需要在finally代码块中手动调用stop方法,然后对执行耗时进行输出打印。

这样的代码多起来就显得很重复了。我们得想个办法少写点代码qwq~

如何对方法进行增强

增强一个Java类中的方法有几种方式?

  • 继承:只有在能够控制这个类的构造器的时候(即不能全部是private修饰的构造器),才可以使用继承。
  • 装饰模式:装饰对象和被装饰对象都要实现相同的接口,装饰对象中需要获取被装饰对象的引用。
  • 动态代理:
    • JDK动态代理:需要被增强类实现一个接口。
    • cglib动态代理:不需要被增强类实现一个接口,内部通过继承生成子类的方式对被代理类进行增强。所以被代理类最好不要声明成final

StopWatch进行增强

增强方式

由于监控代码执行耗时需要能监控任意一小段代码执行的耗时,所以我们不能使用AOP对整个方法进行增强。而装饰模式需要在装饰对象中获取被装饰对象的引用,适合有很多种增强的情况下使用,例如JDKIO流体系。这里我只需要单一的对StopWatch类进行增强,所以我选择使用继承的方式。

增强的具体实现

我们可以在继承StopWatch类后重写stop方法,在调用super.stop()后,进行耗时的日志打印。但这样还是每次都需要手动调用stop()方法。

有没有一种办法可以让我们不在业务代码中主动调用stop方法呢?

我们可以利用JDK7try-with-resources特性:在try后面可以创建一个实现了java.lang.AutoCloseable接口的对象,该对象作用于整个try语句块中,当try语句块中的逻辑执行完毕后会自动回调java.lang.AutoCloseable#close()方法。

来看下对StopWatch增强的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.sunchaser.sparrow.springboot.utils;

import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.StopWatch;

/**
* 对org.springframework.util.StopWatch类的增强
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2021/1/30
*/
@Slf4j
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class StopWatchWrapper extends StopWatch implements AutoCloseable {

/**
* 支持try-with-resources写法
* 1、监控停止:org.springframework.util.StopWatch#stop()
* 2、耗时打印
* @throws Exception org.springframework.util.StopWatch#stop() throws
*/
@Override
public void close() throws Exception {
if (this.isRunning()) {
this.stop();
}
}

@Override
public void stop() throws IllegalStateException {
super.stop();
log.info("{}-执行耗时{}ms", StringUtils.isEmpty(this.getId()) ? "default" : this.getId(), this.getTotalTimeMillis());
}
}

增强后,我们的使用方式如下:

1
2
3
4
5
6
7
8
9
10
private static void testStopWatchWrapper() {
try (StopWatchWrapper watchWrapper = new StopWatchWrapper("test-watch-wrapper")){
watchWrapper.start();
Thread.sleep(100);
// mock exception
// throw new RuntimeException("mock error");
} catch (Exception e) {
e.printStackTrace();
}
}

运行后输出日志如下:

1
03:41:58.165 [main] INFO com.sunchaser.sparrow.springboot.utils.StopWatchWrapper - test-watch-wrapper-执行耗时101ms

可看到已按照预期回调了org.springframework.util.StopWatch#stop()方法并打印了日志。