Mybatis插件应用之Cat监控

一.关于cat

cat是吴其敏在大众点评工作期间设计开发的。是一个实时和接近全量的监控系统,它侧重于对Java应用的监控,基本接入了美团上海侧所有核心应用。 cat可以对应用的接口,mq消息,任务,mybatis,redis等组件进行监控。本文主要介绍cat如何对sql的运行情况进行监控。

二.插件源码解析

@Intercepts({@Signature( type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class} ), @Signature( type = StatementHandler.class, method = "update", args = {Statement.class} ), @Signature( type = StatementHandler.class, method = "batch", args = {Statement.class} )})

public class CatMybatisPlugin implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(CatMybatisPlugin.class);

ini 复制代码
public CatMybatisPlugin() {
    logger.info("CatMybatisPlugin cons...");
}

@Override
public Object intercept(Invocation invocation) throws Throwable {
   // logger.info("cat mybatis plugin invoked.....");
    //获取到方法的第一个参数
    Object firstArg = invocation.getArgs()[0];
    Statement statement;
    //判断是否为代理类
    if (Proxy.isProxyClass(firstArg.getClass())) {
        statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
    } else {
        statement = (Statement) firstArg;
    }

    MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);

    try {
        statement = (Statement) stmtMetaObj.getValue("stmt.statement");
    } catch (Exception var20) {
        ;
    }

    if (stmtMetaObj.hasGetter("delegate")) {
        try {
            statement = (Statement) stmtMetaObj.getValue("delegate");
        } catch (Exception var19) {
            ;
        }
    }
    Object target = PluginUtils.realTarget(invocation.getTarget());
    MetaObject metaObject = SystemMetaObject.forObject(target);
    //得到statement
    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
    //得到sql的方法名
    String methodName = this.getMethodName(mappedStatement);
    Transaction t = Cat.newTransaction("SQL", methodName);

    /***************sql****************/
    String originalSql = null;
    String stmtClassName = statement.getClass().getName();
    Class clazz;
    Object stmtSql;
  
    originalSql = statement.toString();

    //空白字符替换
    originalSql = originalSql.replaceAll("[\\s]+", " ");
    //获取sql开始的位置,原始sql带有statement类名前缀
    int index = this.indexOfSqlStart(originalSql);
    //截取得到完整sql
    if (index > 0) {
        originalSql = originalSql.substring(index);
    }
    /****************sql*****/
    //sql命令的类型,例如insert,update等
    SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
    //记录sql的时间
    Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, originalSql);
    String url = this.getSQLDatabaseUrlByStatement(mappedStatement);
    Cat.logEvent("SQL.Database", url);
    //执行sql,记录sql耗时,是否异常等
    return doFinish(invocation, t);
}

private int indexOfSqlStart(String sql) {
    String upperCaseSql = sql.toUpperCase();
    Set<Integer> set = new HashSet();
    set.add(upperCaseSql.indexOf("SELECT "));
    set.add(upperCaseSql.indexOf("UPDATE "));
    set.add(upperCaseSql.indexOf("INSERT "));
    set.add(upperCaseSql.indexOf("DELETE "));
    set.remove(-1);
    if (CollectionUtils.isEmpty(set)) {
        return -1;
    } else {
        List<Integer> list = new ArrayList(set);
        list.sort(Comparator.naturalOrder());
        return (Integer) list.get(0);
    }
}

public Method getMethodRegular(Class<?> clazz, String methodName) {
    if (Object.class.equals(clazz)) {
        return null;
    } else {
        Method[] var3 = clazz.getDeclaredMethods();
        int var4 = var3.length;

        for (int var5 = 0; var5 < var4; ++var5) {
            Method method = var3[var5];
            if (method.getName().equals(methodName)) {
                return method;
            }
        }

        return this.getMethodRegular(clazz.getSuperclass(), methodName);
    }
}

private String getMethodName(MappedStatement mappedStatement) {
    String[] strArr = mappedStatement.getId().split("\\.");
    String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];

    return methodName;
}

private Object doFinish(Invocation invocation, Transaction t) throws InvocationTargetException, IllegalAccessException {
    Object returnObj = null;
    try {
        //执行RoutingStatementHandler中的batch,query,update方法
        returnObj = invocation.proceed();
        t.setStatus(Transaction.SUCCESS);
    } catch (InvocationTargetException e) {
        Cat.logError(e.getTargetException());
        throw e;
    } catch (Exception e) {
        Cat.logError(e);
        throw e;
    } finally {
        t.complete();
    }

    return returnObj;
}


private String getSQLDatabaseUrlByStatement(MappedStatement mappedStatement) {
    String url = null;
    DataSource dataSource = null;
    try {
        Configuration configuration = mappedStatement.getConfiguration();
        Environment environment = configuration.getEnvironment();
        dataSource = environment.getDataSource();

        url = switchDataSource(dataSource);

        return url;
    } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {
        Cat.logError(e);
    }

    Cat.logError(new Exception("UnSupport type of DataSource : " + dataSource.getClass().toString()));
    return MYSQL_DEFAULT_URL;
}

private String switchDataSource(DataSource dataSource) throws NoSuchFieldException, IllegalAccessException {
    if (dataSource != null) {
        switch (dataSource.getClass().getName()) {

            case DruidDataSourceClassName:
                return getDataSourceSqlURL(dataSource, DruidDataSourceClassName, "getUrl");

            case DBCPBasicDataSourceClassName:
                return getDataSourceSqlURL(dataSource, DBCPBasicDataSourceClassName, "getUrl");

            case DBCP2BasicDataSourceClassName:
                return getDataSourceSqlURL(dataSource, DBCP2BasicDataSourceClassName, "getUrl");

            case C3P0ComboPooledDataSourceClassName:
                return getDataSourceSqlURL(dataSource, C3P0ComboPooledDataSourceClassName, "getJdbcUrl");

            case HikariCPDataSourceClassName:
                return getDataSourceSqlURL(dataSource, HikariCPDataSourceClassName, "getJdbcUrl");

            case BoneCPDataSourceClassName:
                return getDataSourceSqlURL(dataSource, BoneCPDataSourceClassName, "getJdbcUrl");
            default:
                return dataSource.getClass().getName();
        }
    }
    return null;
}

private String getDataSourceSqlURL(DataSource dataSource, String runtimeDataSourceClassName, String sqlURLMethodName) {
    Class<?> dataSourceClass = null;
    try {
        dataSourceClass = Class.forName(runtimeDataSourceClassName);
    } catch (ClassNotFoundException e) {
    }
    Method sqlURLMethod = ReflectionUtils.findMethod(dataSourceClass, sqlURLMethodName);
    return (String) ReflectionUtils.invokeMethod(sqlURLMethod, dataSource);
}


@Override
public Object plugin(Object target) {
    if (target instanceof StatementHandler) {
        return Plugin.wrap(target, this);
    }
    return target;
}

@Override
public void setProperties(Properties properties) {
}

}

相关推荐
wn53121 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
ggdpzhk43 分钟前
Mybatis 快速入门(maven)
oracle·maven·mybatis
希冀12344 分钟前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___12 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee