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

一、准备工作

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

  1. package com.kfit.test;
  2. public class MeiMei {
  3. public void shopping() {
  4. System.out.println("shopping:出发去和美眉一起逛街购物!");
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println("shopping:和美眉一起回家!");
  11. }
  12. //求和计算.
  13. public double sum(double x,double y) {
  14. double sum = x+y;
  15. return sum;
  16. }
  17. public static void main(String[] args) {
  18. MeiMei meimei = new MeiMei();
  19. //和美眉一起购物.
  20. meimei.shopping();
  21. //计算下花了多少钱.
  22. double money = meimei.sum(1500, 3500);
  23. System.out.println("花了多少钱:"+money);
  24. }
  25. }

Run下代码执行结果如下:

shopping:出发去和美眉一起逛街购物!

shopping:和美眉一起回家!

花了多少钱:5000.0

二、统计方法耗时

2.1 利用源码计算时间统计耗时

       我们在原先的代码包裹上一段时间统计的代码如下示例:

  1. long startTime = System.currentTimeMillis();
  2. //…… 执行具体的代码段
  3. long endTime = System.currentTimeMillis();
  4. System.out.println("耗时:"+(endTime-startTime));

师傅:徒儿,你看下上面的代码怎么修改呐?

徒儿,这个就很简单了,在每个方法的头部和结束加上上面的代码:

  1. package com.kfit.test;
  2. public class MeiMei1 {
  3. public void shopping() {
  4. long startTime = System.currentTimeMillis();
  5. System.out.println("shopping:出发去和美眉一起逛街购物!");
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println("shopping:和美眉一起回家!");
  12. long endTime = System.currentTimeMillis();
  13. System.out.println("shopping耗时:"+(endTime-startTime));
  14. }
  15. //求和计算.
  16. public double sum(double x,double y) {
  17. long startTime = System.currentTimeMillis();
  18. double sum = x+y;
  19. long endTime = System.currentTimeMillis();
  20. System.out.println("sum耗时:"+(endTime-startTime));
  21. return sum;
  22. }
  23. public static void main(String[] args) {
  24. MeiMei1 meimei = new MeiMei1();
  25. //和美眉一起购物.
  26. meimei.shopping();
  27. //计算下花了多少钱.
  28. double money = meimei.sum(1500, 3500);
  29. System.out.println("花了多少钱:"+money);
  30. }
  31. }

Run一下执行结果如下:

shopping:出发去和美眉一起逛街购物!

shopping:和美眉一起回家!

shopping耗时:1005

sum耗时:0

花了多少钱:5000.0

 

师傅:嗯,这个意思,那你说说你洗完的感觉。

徒儿:特别的不舒服,我这要是方法多,不得改废了。

师傅:这种方式,实现简单,但是确实实用性太差,有这么一些特点。

优点:实现简单,适用范围广泛;

缺点:侵入性强,大量的重复代码;

 

2.2 利用AutoCloseable

       在 JDK1.7 引入了一个新的接口AutoCloseable, 通常它的实现类配合try{}使用,里面需要重写一个close方法,用来释放资源的,我们会常用于IO流的关闭。

  1. public class TimeConsumingAutoCloseable implements AutoCloseable{
  2. private long startTime;
  3. public TimeConsumingAutoCloseable() {
  4. this.startTime = System.currentTimeMillis();
  5. }
  6. /**
  7. * close方法在try里,释放资源的时候会调用,可以认为是在finally里执行的一个方法。
  8. */
  9. @Override
  10. public void close(){
  11. long endTime = System.currentTimeMillis();
  12. System.out.println("耗时:"+(endTime-startTime));
  13. }
  14. }

       接下来之后,我们对于我们的美眉类稍微改造下:

  1. public class MeiMei2 {
  2. public void shopping() {
  3. System.out.println("shopping:出发去和美眉一起逛街购物!");
  4. try {
  5. Thread.sleep(1000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println("shopping:和美眉一起回家!");
  10. }
  11. //求和计算.
  12. public double sum(double x,double y) {
  13. double sum = x+y;
  14. return sum;
  15. }
  16. public static void main(String[] args) {
  17. MeiMei2 meimei = new MeiMei2();
  18. //和美眉一起购物.
  19. try(TimeConsumingAutoCloseable tc = new TimeConsumingAutoCloseable()){
  20. meimei.shopping();
  21. }
  22. //计算下花了多少钱.
  23. try(TimeConsumingAutoCloseable tc = new TimeConsumingAutoCloseable()){
  24. double money = meimei.sum(1500, 3500);
  25. System.out.println("花了多少钱:"+money);
  26. }
  27. }
  28. }

       这种方式的特点就是:

优点:简单,适用范围广泛,且适合统一管理;

缺点:有代码侵入、编码不够优雅、方法多处调用需多处编写不利于代码管理。

 

2.3 Spring AOP

       在 Spring 生态下,可以借助 AOP 来拦截目标方法,统计耗时:

  1. // 定义切点,拦截所有满足条件的方法
  2. @Pointcut("execution(public * com.kfit.*.*(*))")
  3. public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  4. long startTime = System.currentTimeMillis();
  5. try{
  6. return joinPoint.proceed();
  7. } finally {
  8. System.out.println("耗时: " + (System.currentTimeMillis() - startTime));
  9. }
  10. }

 

Spring AOP 的底层支持原理为代理模式,为目标对象提供增强功能;在 Spring 的生态体系下,使用 aop 的方式来统计方法耗时,可以说少侵入且实现简单,但是有以下几个问题:

(1)统计粒度为方法级别

(2)类内部方法调用无法生效

       总的来说,这种方式比前面两种方式已经好用了很多。

 

2.4 Java Agent

       除了上面介绍的两种方式,在代码上都是有侵入性的,如果你的项目已经打包了,不能修改代码,那么上面的方式就基本无法使用了,有没有一种方式可以是零侵入的呐。这个必须有,可以利用 Java Agent来实现。

       在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能

       这个具体牵涉的会比较多,我们在之后的章节进行展开讲解。

购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287