「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第1张图片

相关历史文章(阅读本文之前,您可能需要先看下之前的系列

国内最全的Spring Boot系列之三

100G的文件如何读取续集 - 第307篇

Java语言的优雅停机 - 第308篇

SpringBoot 优雅停止服务的几种方法 - 第309篇

Docker优雅的关闭SpringBoot - 第310篇

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇

 

师傅:徒儿,听过字节码插桩嘛?

徒儿:师傅,徒儿诗如李白貌如潘安字如王羲之歌如刘德华智慧如诸葛亮数学如华罗庚,对于字节码插桩,我怎么能不懂呐。

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第2张图片

师傅:徒儿,你这什么时候,练就了吹牛逼吹的都不脸红的境界了,来来来,你倒是和为师说说什么是字节码插桩。

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第3张图片

 

徒儿:字节码插桩就是这么一个意思,字节码… 插… 桩… ,师傅,你品,你品,你细品。

师傅:我品啥?你貌似什么都没有说嘛。还诗如李白,智慧如诸葛亮呐,我看你是吹牛逼的本身涨了不少呐。

徒儿:我这牺牲小我,开心大家的吗,师傅,你看我容易嘛。

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第4张图片

 

师傅:哎,为师上辈子是造了什么孽。

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第5张图片

 

徒儿:师傅,不对,不对,你这上辈子得积多少德,才能收我这小可爱为徒呐。

师傅:是… 是… 你这可爱有淘气的小可爱!人见人见,花见花开。

师傅:好了,还是师傅给你普及下。请认真听讲。

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第6张图片

 

一、准备工作

       我们编写一个美眉类,有几个方法:

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!

「字节码插桩」统计方法耗时(第一篇:初出茅庐)- 第311篇_第7张图片

对于方法的耗时,师傅这节课讲了4种方法:

  1. 在方法块中记录开始和结束时间来进行统计耗时。
  2. 利用AutoClosealbe+try(){}资源释放调用close()方法来统计耗时。
  3. 利用Spring AOP面向切面编程技术来实现耗时。
  4. 利用Java Agent来实现耗时,此节并没有展开进行讲解。

你可能感兴趣的:(从零开始学Spring,Boot,spring,boot)