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) {
}

}

相关推荐
程序员岳焱2 小时前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化
麦兜*3 小时前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
大只鹅3 小时前
解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
spring boot·后端·elasticsearch
ai小鬼头3 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
IT_10244 小时前
Spring Boot项目开发实战销售管理系统——数据库设计!
java·开发语言·数据库·spring boot·后端·oracle
bobz9654 小时前
动态规划
后端
stark张宇4 小时前
VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
linux·后端
耀耀_很无聊4 小时前
07_通过 Mybatis 自动填充记录的创建时间和更新时间
mybatis
亚力山大抵5 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍5 小时前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端