利用spring AOP原理 实现 DAO层SQL的监控,辅助开发
2017-12-01 18:21
573 查看
公司项目大部分采用SpringMVC +Spring-JDBC框架来搭建web项目,每次需要分析代码的SQL的时候,都需要将代码打上断点,运行程序,定位到SQL,然后粘到对应的查询工具上进行查询,查询期间还要手动替换 上查询参数。这种做法做多了,令人生厌,所以利用AOP原理,写一个SQL调试管理小功能。
以下为项目dao层的简单接口定义:
public interface BaseDao {
}
可以发现传参比较简单,基本都是传入sql,以及一些sql参数,我们只需要拦截到这些要执行的方法,通过JAVA反射拿到对应的参数,进行控制台输出就好了。但是仅仅输出了sql还不够,我们还需要显示的知道这个方法的调用过程。这里通过Java线程来获取方法运行栈的信息。对比看了下具有sql监控的淘宝数据源druid,其实现逻辑大体上也是运用了AOP的原理进行SQL的监控。
废话不少说直接上代码:
1)自定义一个方法拦截器 DisplayExecuteSqlInterceptor :
2)在spring配置文件中,添加下一下的配置:
3)测试的执行结果如下:
4)第一次写,写的不清楚,不清楚的地方请留言,勿喷,谢谢。
以下为项目dao层的简单接口定义:
public interface BaseDao {
public List<List<String>> queryListData(String sql, Object[] o); public List<List<String>> queryListDataNoParams(String sql); public List queryList(String sql, Object[] o); public <T> List<T> queryObjList(String sql, Object[] args, Class<T> clazz); public int queryForInt(String sql, Object[] o); public int addOrUpdate(String sql, Object[] o);
}
可以发现传参比较简单,基本都是传入sql,以及一些sql参数,我们只需要拦截到这些要执行的方法,通过JAVA反射拿到对应的参数,进行控制台输出就好了。但是仅仅输出了sql还不够,我们还需要显示的知道这个方法的调用过程。这里通过Java线程来获取方法运行栈的信息。对比看了下具有sql监控的淘宝数据源druid,其实现逻辑大体上也是运用了AOP的原理进行SQL的监控。
废话不少说直接上代码:
1)自定义一个方法拦截器 DisplayExecuteSqlInterceptor :
package com.XXX.CCC.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; /** * * 方法拦截 粒度在方法上 * * @desc 调试管理 利用 AOP 原理, 在开发模式下于控制台展示 dao层 的实际执行的SQL * 粘出来即可 在pl/sql下执行,已经替换掉 ? 了 * * @author luotianyi * @create 2017-11-30 14:03 **/ public class DisplayExecuteSqlInterceptor implements MethodInterceptor { private static final Logger log = LoggerFactory.getLogger(DisplayExecuteSqlInterceptor.class); private static final String CONTROLLER ="CONTROLLER"; private static final String SERVICE ="SERVICE"; private static final String DAO ="DAO"; private static final String IMPL ="IMPL"; @Override public Object invoke(MethodInvocation mi) throws Throwable { //系统的开发模式 String codeModel =System.getProperty("codeModel"); String flag="true"; if(StringUtils.equals(flag,codeModel)){ //获取该方法的传参 Object[] ars = mi.getArguments(); //通过反射机制 获取到该方法 (Method 包含 作用域 返回类型 方法名 参数类型) Method method= mi.getMethod(); //获取代理的对象 (也就是这个方法所在内存中的对象) Object obj = mi.getThis(); Object [] params =new Object[]{} ; String sql =""; for(Object o :ars){ if(o instanceof Object[]){ params= (Object[]) o; }else if(o instanceof String){ sql=(String) o; } } Thread current = Thread.currentThread(); StackTraceElement[] elements =current.getStackTrace(); //倒序输出 栈帧 信息 ,过滤出 项目的代码 这里只过滤出(Controller / service impl / dao impl)层的代码,如需要其他的可自行遍历 if(elements !=null && elements.length>0){ //获得项目名 String packageName =DisplayExecuteSqlInterceptor.class.getPackage().getName(); packageName=StringUtils.substringBefore(packageName,"."); StringBuilder sb =new StringBuilder(); sb.append(" -------->本次执行SQL的代码在<--------- "); sb.append('\n'); for(int i=elements.length ;i>0 ;i--){ StackTraceElement e =elements[i-1]; if(StringUtils.contains(e.getClassName(),packageName)){ String cn=StringUtils.upperCase(e.getClassName()); if(StringUtils.contains(cn,CONTROLLER)){ sb.append( CONTROLLER+" 层 ->类名:"+e.getClassName()+",方法名:"+e.getMethodName()+",代码行数:"+e.getLineNumber()+""); sb.append('\n'); }else if(StringUtils.contains(cn,SERVICE) &&StringUtils.contains(cn,IMPL) && e.getLineNumber()>0) { sb.append( SERVICE+" 层 ->类名:"+e.getClassName()+",方法名:"+e.getMethodName()+",代码行数:"+e.getLineNumber()+""); sb.append('\n'); }else if(StringUtils.contains(cn,DAO) &&StringUtils.contains(cn,IMPL) && e.getLineNumber()>0 ){ sb.append(DAO +" 层->类名:"+e.getClassName()+",方法名:"+e.getMethodName()+",代码行数:"+e.getLineNumber()+""); sb.append('\n'); } } } log.info(sb.toString()); } getExecuteSql(sql,params); } //执行被拦截的方法,切记,如果此方法不调用,则被拦截的方法不会被执行。 return mi.proceed(); } private String getExecuteSql(String sql, Object[] params) { if (StringUtils.isNotBlank(sql)) { if (params != null && params.length > 0) { int a = getCount(sql, '?'); int b = params.length; if (a == b) { sql = StringUtils.replace(sql, "?", "XXXX"); for (int i = 0; i < params.length; i++) { Object obj = params[i]; if (StringUtils.isNotBlank(String.valueOf(obj)) && StringUtils.isNumeric(String.valueOf(obj))) { obj = Integer.valueOf(String.valueOf(obj)); } else { obj = "'" + obj + "'"; } sql = sql.replaceFirst("XXXX", String.valueOf(obj)); } } else { log.info("参数个数传的不正确, sql中 需要 :{} 个参数,实际传入参数为 :{} 个。", a, b); return null; } } } StringBuilder sb =new StringBuilder(); sb.append(" ----------->本次执行sql为:<----------- "); sb.append('\n'); sb.append(sql+'\n'); log.info(sb.toString()); return sql; } private int getCount(String sql ,char a ){ int count=0; if(StringUtils.isNotBlank(sql)){ for (int i = 0; i < sql.length(); i++) { if(sql.charAt(i)==a){ count++; } } } return count; } }
2)在spring配置文件中,添加下一下的配置:
<aop:config> <!--切入点,也就是你要监控哪些类下的方法,由于是监控SQL的执行情况,这里写的是DAO层的目录,包名要记得换成你自己项目的目录哟,大兄弟--> <aop:pointcut id="displayExecuteSql" expression="execution(public * com. com.XXX.CCC.base.dao.impl.*.*(..)) "/> <!--在该切入点使用自定义拦截器--> <aop:advisor pointcut-ref="displayExecuteSql" advice-ref="displayExecuteSqlInterceptor"/> </aop:config>
3)测试的执行结果如下:
4)第一次写,写的不清楚,不清楚的地方请留言,勿喷,谢谢。
相关文章推荐
- 17、Spring实战:利用AOP实现日志监控
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 【原创】SSH开发框架中,实现系统启动加载类,读取数据库常用数据进入内存,利用Spring托管,并完成reload功能
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现AOP 动态代理模式(Spring AOP 的实现原理)
- [转贴] 反射实现 AOP 动态代理模式(Spring AOP 的实现原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现原理)
- 反射实现 AOP 动态代理模式实例说明(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 在Spring中,是如何实现 Aop 的,原理:动态代理+cglib 分步图解
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 用Spring AOP实现开发中松散耦合
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)
- 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理) --转
- 用Spring AOP实现开发中松散耦合