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

}

相关推荐
龚思凯4 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响7 分钟前
枚举在实际开发中的使用小Tips
后端
wuhunyu13 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi13 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
写bug写bug1 小时前
手把手教你使用JConsole
java·后端·程序员
苏三说技术1 小时前
给你1亿的Redis key,如何高效统计?
后端
JohnYan2 小时前
工作笔记- 记一次MySQL数据移植表空间错误排除
数据库·后端·mysql
程序员清风2 小时前
阿里二面:Kafka 消费者消费消息慢(10 多分钟),会对 Kafka 有什么影响?
java·后端·面试
CodeSheep2 小时前
宇树科技,改名了!
前端·后端·程序员
hstar95273 小时前
三十五、面向对象底层逻辑-Spring MVC中AbstractXlsxStreamingView的设计
java·后端·spring·设计模式·架构·mvc