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 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien2 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手3 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生4 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei5 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
dessler5 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
Q_19284999066 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S6 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_7 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端