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

}

相关推荐
码界奇点28 分钟前
基于Spring Boot的前后端分离商城系统设计与实现
java·spring boot·后端·java-ee·毕业设计·源代码管理
fox_lht1 小时前
7.3.结构体-方法
开发语言·后端·rust
掘金者阿豪1 小时前
一个权限配置错误引发的“血案”:数据库访问控制手记
后端
消失的旧时光-19431 小时前
Spring Boot 接口设计进阶:POST / PUT / DELETE 的本质区别与工程实践
spring boot·后端
不恋水的雨1 小时前
mybatis-plus保存数据实现公共字段自动填充
mybatis
MegaDataFlowers1 小时前
基于EasyCode插件的SpringBoot和Mybatis框架快速整合以及PostMan的使用
spring boot·mybatis·postman
StackNoOverflow1 小时前
Spring Cloud的注册中心和配置中心(Nacos)
后端·spring cloud
SamDeepThinking2 小时前
秒杀系统需求PRD
java·后端·架构
掘金者阿豪2 小时前
被飞书和火山引擎账号体系整崩溃了?一个程序员彻底讲清楚背后的设计逻辑
后端
代码羊羊2 小时前
Rust基础类型与变量全解析
开发语言·后端·rust