相关历史文章(阅读本文之前,您可能需要先看下之前的系列)
国内最全的Spring Boot系列之三
100G的文件如何读取续集 - 第307篇
Java语言的优雅停机 - 第308篇
SpringBoot 优雅停止服务的几种方法 - 第309篇
Docker优雅的关闭SpringBoot - 第310篇
「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇
师傅:徒儿,听过字节码插桩嘛?
徒儿:师傅,徒儿诗如李白,貌如潘安,字如王羲之,歌如刘德华,智慧如诸葛亮,数学如华罗庚,对于字节码插桩,我怎么能不懂呐。
师傅:徒儿,你这什么时候,练就了吹牛逼吹的都不脸红的境界了,来来来,你倒是和为师说说什么是字节码插桩。
徒儿:字节码插桩就是这么一个意思,字节码… 插… 桩… ,师傅,你品,你品,你细品。
师傅:我品啥?你貌似什么都没有说嘛。还诗如李白,智慧如诸葛亮呐,我看你是吹牛逼的本身涨了不少呐。
徒儿:我这牺牲小我,开心大家的吗,师傅,你看我容易嘛。
师傅:哎,为师上辈子是造了什么孽。
徒儿:师傅,不对,不对,你这上辈子得积多少德,才能收我这小可爱为徒呐。
师傅:是… 是… 你这可爱有淘气的小可爱!人见人见,花见花开。
师傅:好了,还是师傅给你普及下。请认真听讲。
一、准备工作
我们编写一个美眉类,有几个方法:
package com.kfit.test;
public class MeiMei {
public void shopping() {
System.out.println("shopping:出发去和美眉一起逛街购物!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("shopping:和美眉一起回家!");
}
//求和计算.
public double sum(double x,double y) {
double sum = x+y;
return sum;
}
public static void main(String[] args) {
MeiMei meimei = new MeiMei();
//和美眉一起购物.
meimei.shopping();
//计算下花了多少钱.
double money = meimei.sum(1500, 3500);
System.out.println("花了多少钱:"+money);
}
}
Run下代码执行结果如下:
shopping:出发去和美眉一起逛街购物!
shopping:和美眉一起回家!
花了多少钱:5000.0
二、统计方法耗时
2.1 利用源码计算时间统计耗时
我们在原先的代码包裹上一段时间统计的代码如下示例:
long startTime = System.currentTimeMillis();
//…… 执行具体的代码段
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
师傅:徒儿,你看下上面的代码怎么修改呐?
徒儿,这个就很简单了,在每个方法的头部和结束加上上面的代码:
package com.kfit.test;
public class MeiMei1 {
public void shopping() {
long startTime = System.currentTimeMillis();
System.out.println("shopping:出发去和美眉一起逛街购物!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("shopping:和美眉一起回家!");
long endTime = System.currentTimeMillis();
System.out.println("shopping耗时:"+(endTime-startTime));
}
//求和计算.
public double sum(double x,double y) {
long startTime = System.currentTimeMillis();
double sum = x+y;
long endTime = System.currentTimeMillis();
System.out.println("sum耗时:"+(endTime-startTime));
return sum;
}
public static void main(String[] args) {
MeiMei1 meimei = new MeiMei1();
//和美眉一起购物.
meimei.shopping();
//计算下花了多少钱.
double money = meimei.sum(1500, 3500);
System.out.println("花了多少钱:"+money);
}
}
Run一下执行结果如下:
shopping:出发去和美眉一起逛街购物!
shopping:和美眉一起回家!
shopping耗时:1005
sum耗时:0
花了多少钱:5000.0
师傅:嗯,这个意思,那你说说你洗完的感觉。
徒儿:特别的不舒服,我这要是方法多,不得改废了。
师傅:这种方式,实现简单,但是确实实用性太差,有这么一些特点。
优点:实现简单,适用范围广泛;
缺点:侵入性强,大量的重复代码;
2.2 利用AutoCloseable
在 JDK1.7 引入了一个新的接口AutoCloseable, 通常它的实现类配合try{}使用,里面需要重写一个close方法,用来释放资源的,我们会常用于IO流的关闭。
public class TimeConsumingAutoCloseable implements AutoCloseable{
private long startTime;
public TimeConsumingAutoCloseable() {
this.startTime = System.currentTimeMillis();
}
/**
* close方法在try里,释放资源的时候会调用,可以认为是在finally里执行的一个方法。
*/
@Override
public void close(){
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
}
}
接下来之后,我们对于我们的美眉类稍微改造下:
public class MeiMei2 {
public void shopping() {
System.out.println("shopping:出发去和美眉一起逛街购物!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("shopping:和美眉一起回家!");
}
//求和计算.
public double sum(double x,double y) {
double sum = x+y;
return sum;
}
public static void main(String[] args) {
MeiMei2 meimei = new MeiMei2();
//和美眉一起购物.
try(TimeConsumingAutoCloseable tc = new TimeConsumingAutoCloseable()){
meimei.shopping();
}
//计算下花了多少钱.
try(TimeConsumingAutoCloseable tc = new TimeConsumingAutoCloseable()){
double money = meimei.sum(1500, 3500);
System.out.println("花了多少钱:"+money);
}
}
}
这种方式的特点就是:
优点:简单,适用范围广泛,且适合统一管理;
缺点:有代码侵入、编码不够优雅、方法多处调用需多处编写不利于代码管理。
2.3 Spring AOP
在 Spring 生态下,可以借助 AOP 来拦截目标方法,统计耗时:
// 定义切点,拦截所有满足条件的方法
@Pointcut("execution(public * com.kfit.*.*(*))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try{
return joinPoint.proceed();
} finally {
System.out.println("耗时: " + (System.currentTimeMillis() - startTime));
}
}
Spring AOP 的底层支持原理为代理模式,为目标对象提供增强功能;在 Spring 的生态体系下,使用 aop 的方式来统计方法耗时,可以说少侵入且实现简单,但是有以下几个问题:
(1)统计粒度为方法级别
(2)类内部方法调用无法生效
总的来说,这种方式比前面两种方式已经好用了很多。
2.4 Java Agent
除了上面介绍的两种方式,在代码上都是有侵入性的,如果你的项目已经打包了,不能修改代码,那么上面的方式就基本无法使用了,有没有一种方式可以是零侵入的呐。这个必须有,可以利用 Java Agent来实现。
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。
这个具体牵涉的会比较多,我们在之后的章节进行展开讲解。
三、悟纤小结
师傅:好了咱们今天就学习这么多,来你和大家进行总结下。
徒儿:好的,no problem!
对于方法的耗时,师傅这节课讲了4种方法:
- 在方法块中记录开始和结束时间来进行统计耗时。
- 利用AutoClosealbe+try(){}资源释放调用close()方法来统计耗时。
- 利用Spring AOP面向切面编程技术来实现耗时。
- 利用Java Agent来实现耗时,此节并没有展开进行讲解。