spring,真是一个好东西;性能,真是个让人头疼又不得不面对的问题。如何排查出项目中性能瓶颈?如何迅速定位系统的慢查询?在这我就不说spring自带的性能监控器了,实在是有些简陋。下面就说说我自己写的这个性能监控器。先看看效果:

2013-07-07 19:19:50,440 WARN  [main] [aop.PerformanceInterceptor]
|-144 ms; [HelloService.hellpAop]
|+---10 ms; [AnotherService.childMehhod]
|+---21 ms; [AnotherService.childMehhod3]
|+---+---8 ms; [HelloService.parentMehtod]
|+---12 ms; [AnotherService.childMehhod2]

  

其实,利用spring AOP,任何人都可以写一个定制的监控,但大体思路是一样的,就是在被调用方法的开始之前,记录一下开始时间,在调用方法结束之后,记录结束时间,然后,在调用栈退出前,将日志打印出来。光说不练假把式,下面一步一步构建一个性能监控器。

step1.构造拦截器。

直接上代码,拦截器核心代码:

public Object invoke(MethodInvocation invocation) throws Throwable {  try {    String name = extractLogName(invocation);  //记录开始时间  start(name);    return invocation.proceed();    } finally {   //记录方法结束时间  stop();    }
}

  

因为最终要打印出来,因此,打印的名称必须在记录时间时把被调用方法的名称也记录下来。方法extractLogName就是干这个的。

step2.构造数据结构

首先,我们需要一个线程变量,存储AOP拦截的每个方法的开始时间及结束时间。就用threadLocal变量了,本人还没想到更好的方法。其次,调用过程其实是在一个方法栈(Stack)中旅行了一遍,被调用的方法总是后进先出。每进一个方法,都要记录一个开始时间,每当退出一个方法,都要记录这个方法运行的结束时间。最终,我们将得到一个类似树形的结构。我们需要定义这个结构,以便我们在退出方法栈时能够将每一个方法所耗费的时间都打印出来。

/** * 保存监控信息变量 * @author donlianli@126.com */  private static class StackData {  /** * 记录根根节点 */  public StackEntry root;  /** * 当前正在调用方法节点 */  public StackEntry currentEntry;  /** * 堆栈树高度 */  public int level;  }  /** * aop方法性能统计实体 * @author donlianli@126.com */  private static class StackEntry {  public String logName ;  public long beginTime;  public long endTime;  /** * 节点所处高度 */  public int level;  /** * 调用的子方法 */  public List<StackEntry> child;  /** * 上级节点 */  public StackEntry parent ;  public StackEntry(String logName, long currentTimeMillis) {  this.logName = logName;  this.beginTime = currentTimeMillis;  this.child = new ArrayList<StackEntry>(3);  }  }

  

StackData定义了根节点的结构,StackEntry存储每个方法的开始结束时间,另外在StackData和StackEntry加入level字段,方便后面打印日志。StackData和StackEntry都是作为一个内部类引入的,因为这两个类为了性能,都没有提供一些封装方法,不宜暴露出去(出去多丢人啊)。

好了,结构和拦截器都写好了。只需两步,大工基本就告成了,在拦截器中,在调用方法的前面及后面,记录一个StackEntry对象就可以了。start和stop的代码如下:

public static void start(String logName) {    StackData data = dataHolder.get();    StackEntry currentEntry = new StackEntry(logName, System.currentTimeMillis());    if (data == null) {    data = new StackData();    data.root = currentEntry;    data.level = 1;  dataHolder.set(data);    } else {    StackEntry parent = data.currentEntry;    currentEntry.parent=parent;    parent.child.add(currentEntry);    }    data.currentEntry = currentEntry;    currentEntry.level=data.level;  data.level++;    }  public static void stop() {  StackData data = dataHolder.get();    StackEntry self = data.currentEntry;  self.endTime = System.currentTimeMillis();  data.currentEntry = self.parent;  data.level--;  printStack(data);  }  /** * 此处还可以进行改进,可以将超时的数据放入一个有界队列 * 里,在另一个线程进行打印。 * @param data */  private static void printStack(StackData data) {  if(logger.isWarnEnabled()){  StringBuilder sb = new StringBuilder("\r\n");  StackEntry root = data.root;  appendNode(root,sb);  logger.warn(sb.toString());  }  }  private static void appendNode(StackEntry entry, StringBuilder sb) {  long totalTime = entry.endTime-entry.beginTime ;  if(entry.level ==1){  sb.append("|-");  }  sb.append(totalTime);  sb.append(" ms; [");  sb.append(entry.logName);  sb.append("]");  for(StackEntry cnode : entry.child){  sb.append("\r\n|");  for(int i=0,l=entry.level;i<l;i++){  sb.append("+---");  }  appendNode(cnode,sb);  }  }

  

等等,还有需求?

1、我们只想找出慢查询,而不想把所有的方法的运行时间都打印出来

2、希望有一个开关,平常不需要监控,在出现问题的时候,才把这个监控打开。

好吧,程序员都是被这些需求给搞死的。

在拦截器中增加一个开关switchOn和一个阈值threshold,当switchOn==true的时候,才进行监控,否则不监控。在监控时,如果整个方法的运行时间小于threshold,不打印日志,因为打印日志会IO,会给方法增加额外的开销。改进后代码如下:

  /** * 性能监控开关 * 可以在运行时动态设置开关 */
private volatile boolean switchOn = true;
/** * 方法执行阈值 */
private volatile int threshold = 100;
public Object invoke(MethodInvocation invocation) throws Throwable {  if(switchOn){  String name = extractLogName(invocation);  try {    start(name);    return invocation.proceed();    } finally {    stop(threshold);    }    }  else {  return invocation.proceed();    }
}

  

打印日志阈值:

    /** * @param threshold 打印日志的阈值 */
public static void stop(int threshold) {  StackData data = dataHolder.get();    StackEntry self = data.currentEntry;  self.endTime = System.currentTimeMillis();  data.currentEntry = self.parent;  data.level--;  if(data.root == self && (self.endTime -self.beginTime) > threshold){  printStack(data);  }
}

  

到此,这个性能监控器几乎算完美了。

但这个监控器是运行在spring AOP上面的,并且,监控的方法必须都是通过interface调用的。所以,如果你要使用这个方法,还要确保你是使用的面向接口的编程。不过,如果你的项目没有使用面向接口,可以利用eclipse自带的工具,将公用方法Extract成interface。

spring怎么配置?拦截器怎么配置?你不会连这个都不会吧,那你搜索一下吧。

PS:暂时未提供spring3.0的实现。

对这类话题感兴趣?欢迎发送邮件至donlianli@126.com
关于我:邯郸人,擅长Java,Javascript,Extjs,oracle sql。
更多我之前的文章,可以访问 我的空间

转载于:https://www.cnblogs.com/donlianli/p/3176828.html

Spring AOP 性能监控器相关推荐

  1. java aop性能检测_Spring AOP 性能监控器

    spring,真是一个好东西:性能,真是个让人头疼又不得不面对的问题.如何排查出项目中性能瓶颈?如何迅速定位系统的慢查询?在这我就不说spring自带的性能监控器了,实在是有些简陋.下面就说说我自己写 ...

  2. Spring AOP切点表达式详解

    1. 简介 面向对象编程,也称为OOP(即Object Oriented Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的.通过面向对象编程,不同的模板可以相互组装 ...

  3. 面试官:抛开Spring来说,如何自己实现Spring AOP?

    欢迎关注方志朋的博客,回复"666"获面试宝典 | 引言 翻开to-do,注解认证中答应大家要讲解代理模式. 正好遇到了一道这样的题:抛开Spring来说,如何自己实现Spring ...

  4. 面试官:说说Spring AOP、AspectJ、CGLIB ?它们有什么关系?

    欢迎关注方志朋的博客,回复"666"获面试宝典 AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务 ...

  5. Spring AOP 增强框架 Nepxion Matrix 详解

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 概述 在<深入聊一聊 Spring AOP 实现机制>一文中,介绍了 Spring A ...

  6. 比较Spring AOP与AspectJ

    本文翻译自博客Comparing Spring AOP and AspectJ 介绍 如今有多个可用的AOP库,这些组件需要回答一系列的问题: 是否与我现有的应用兼容? 我在哪实现AOP? 集成到我的 ...

  7. 面试官:Spring AOP、AspectJ、CGLIB 都是什么鬼?它们有什么关系?

    AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理.安全检查.缓存.对象池管理等. AOP 实现的关键就在 ...

  8. AspectJ和Spring AOP(java动态代理和CGLIB)简单介绍

    1.AOP介绍 什么是AOP:AOP就是面向切面编程.使用的背景: 1)我们的振隆维护着一千个方法,一天老板让振隆把这一千个方法都要加上事务代码(统一代码) 2)振隆咬咬牙,添加了一个新的方法,然后让 ...

  9. Spring学习总结(4)——Spring AOP教程

    2019独角兽企业重金招聘Python工程师标准>>> 一.概念 AOP(Aspect Oriented Programming):面向切面编程. 面向切面编程(也叫面向方面编程), ...

最新文章

  1. php学数据结构,PHP 程序员学数据结构与算法之《栈》
  2. oracle中minus
  3. 英文单词 hard
  4. 在Java中使用Google的协议缓冲区
  5. linux系统防火墙配置浅谈
  6. mysql修改表结构 删除字段_mysql更改表结构:添加、删除、修改字段、调整字段顺序...
  7. 56. mysqli 扩展库(3)
  8. 未能找到任何适合于指定的区域性或非特定区域性的资源。请确保在编译时已将“***Form.resources”正确嵌入或链接到程序集“***”。。。
  9. (转)Rust:Vec、String 内存布局
  10. 初学者怎样快速学会 SQL
  11. 加密的m3u8、ts文件合并
  12. Misra c规则简介
  13. vs2010中svn使用教程_VS2010中使用ankhSVN
  14. 关于 Uncaught ReferenceError: mOxie is not defined情况下的问题
  15. 微信网页版扫码登录是如何实现的?
  16. 微信小程序接入知晓云插件sdk入门
  17. 低成本血氧仪方案设计
  18. apache中的php模块安装
  19. java合法标识符_JAVA合法标识符
  20. 针孔相机(透视相机模型)

热门文章

  1. java取文本首位_java – 从文本文件中读取的第一个字符:[复制]
  2. php b64encode,[转]PHP base64_encode 在URL地址参数编码上使用
  3. 推荐 | 微软SAR近邻协同过滤算法相关问题(三)
  4. 不只是新车,2019上海车展还有这些彩蛋 | 一级供应商、科技公司篇
  5. Excel如何利用条件格式找出数据区域中最大的几项
  6. RestFul的初步理解
  7. 旧的非flash版Metalink的入口
  8. 将不确定变为确定~Linq to SQL不能随机排序吗?
  9. Exchange Server 2010高可用性配置
  10. 深度学习 --- 优化入门三(梯度消失和激活函数ReLU)