簡體   English   中英

如何攔截繼承的方法

[英]How to intercept inherited method

我想用 Byte Buddy 構建一個 Java 代理,它攔截 JDBC 連接創建和關閉事件。 我想支持諸如 HikariCP、DBCP 等數據源,以及帶有 RDBMS 驅動程序的普通 JDBC。

我的代碼:

儀器設置

new AgentBuilder.Default()
                .type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(not(isAbstract())))
                .transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);

攔截代碼

package org.wxt.xtools.agents.jdbcmon;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.wxt.xtools.agents.utils.StringUtils;
import org.wxt.xtools.agents.utils.TopN;

import ch.qos.logback.classic.Logger;
import io.prometheus.client.Histogram;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

/**
 * 
 * @author ggfan
 * 
 */
public class JDBCAPIInterceptor {

    // ***ATTENTION*** fields below must be public, as we will access them from the
    // intercepting methods
    public static Map<Integer, ConnectionInfo> conns = new HashMap<Integer, ConnectionInfo>();
    
    public static Map<String, String> stacks = new HashMap<String, String>();

    public static Map<String, Integer> counstsByStack = new HashMap<String, Integer>();
    
    public static TopN<StatementExecutionInfo> slowStatements = new TopN<StatementExecutionInfo>(10, new Comparator<StatementExecutionInfo>() {
        @Override
        public int compare(StatementExecutionInfo o1, StatementExecutionInfo o2) {
            long d1 = o1.getExecutionEndTime() - o1.getExecutionStartTime();
            long d2 = o2.getExecutionEndTime() - o2.getExecutionStartTime();
            return Long.compare(d1, d2);
        }
    });

    public static Logger log = JDBCMonitor.log;

    public static JDBCStatsReport getInfo() {
        JDBCStatsReport report = new JDBCStatsReport();
        List<ConnectionInfo> data = new ArrayList<ConnectionInfo>();
        for (ConnectionInfo ci : conns.values()) {
            ConnectionInfo copy = new ConnectionInfo();
            copy.setHash(ci.getHash());
            copy.setStackHash(ci.getStackHash() + "(" + counstsByStack.get(ci.getStackHash()) + ")");
            copy.setCreationTime(ci.getCreationTime());
            copy.setLastActiveTime(ci.getLastActiveTime());
            copy.setLastMethod(ci.getLastMethod());
            copy.setCurrentStatement(ci.getCurrentStatement());
            data.add(copy);                                                                                                                                                                                                                                                                
        }
        report.setConnectionInfo(data);
        report.setSlowStatementTopN(slowStatements);
        return report;
    }

    @RuntimeType
    public static Object interceptor(@Origin Class<?> clazz, @Origin Method method, @SuperCall Callable<?> callable,
            @net.bytebuddy.implementation.bind.annotation.This Object inst, @AllArguments Object[] args)
            throws Exception {
        // this is equal to original method, will not cause inner calls to other
        // matching methods get intercepted.
        // Object o = callable.call();
        log.debug("intercepting {}#{}", clazz.getName(), method.getName());
        int hashCode = System.identityHashCode(inst);
        if (java.sql.Connection.class.isAssignableFrom(clazz)) {
            ConnectionInfo ci = conns.get(hashCode);
            // TODO fix bug here!!!
            if (ci == null) {
                log.debug("connection@{} is not in records anymore, maybe called #{} after close, ignored", hashCode,
                        method.getName());
            }
            return interceptConnectionMethods(method, hashCode, ci, callable, args);
        } else if (java.sql.Statement.class.isAssignableFrom(clazz)) {
            return interceptStatementMethods(method, hashCode, callable, args);
        } else if (java.sql.PreparedStatement.class.isAssignableFrom(clazz)) {
            return interceptStatementMethods(method, hashCode, callable, args);
        } else if (java.sql.CallableStatement.class.isAssignableFrom(clazz)) {
            return interceptStatementMethods(method, hashCode, callable, args);
        }
        return null;
    }

    private static Object interceptConnectionMethods(Method method, int hashCode, ConnectionInfo ci,
            Callable<?> callable, Object[] args) throws Exception {
        Object o = callable.call();
        log.debug("connection@{} used by {}", hashCode, method.getName());
        ci.setLastActiveTime(System.currentTimeMillis());
        ci.setLastMethod(method.getName());
        int resultHash = System.identityHashCode(o);
        if (method.getName().equals("close")) {
            log.info("connection@{} released", hashCode);
            String stackHash = ci.getStackHash();
            Integer scount = counstsByStack.get(stackHash);
            if (scount != null && scount > 0) {
                int newscount = scount - 1;
                log.info("set connection count to {} by stack hash {}", newscount, stackHash);
                if (newscount == 0) {
                    counstsByStack.remove(stackHash);
                    stacks.remove(stackHash);
                } else {
                    counstsByStack.put(stackHash, newscount);
                }
            } else {
                log.error("connection count by stack hash {} is not supposed to be null or less than zero", stackHash);
            }
            conns.remove(hashCode);
        } else if (method.getName().equals("createStatement")) {
            StatementExecutionInfo stmt = new StatementExecutionInfo();
            stmt.setType(StatementType.NORMAL);
            stmt.setHash(resultHash);
            conns.get(hashCode).setCurrentStatement(stmt);
            log.info("statement@{} created, type {}, attached to connection@{}", resultHash, StatementType.NORMAL, hashCode);
        } else if (method.getName().equals("prepareStatement")) {
            StatementExecutionInfo stmt = new StatementExecutionInfo();
            stmt.setType(StatementType.PREPARED);
            stmt.setSqlText((String) args[0]);
            stmt.setHash(resultHash);
            conns.get(hashCode).setCurrentStatement(stmt);
            log.info("statement@{} created, type {}, attached to connection@{}", resultHash, StatementType.PREPARED, hashCode);
        } else if (method.getName().equals("prepareCall")) {
            StatementExecutionInfo stmt = new StatementExecutionInfo();
            stmt.setType(StatementType.CALLABLE);
            stmt.setSqlText((String) args[0]);
            stmt.setHash(resultHash);
            conns.get(hashCode).setCurrentStatement(stmt);
            log.info("statement@{} created, type {}, attached to connection@{}", resultHash, StatementType.CALLABLE, hashCode);
        }
        return o;
    }

    private static Object interceptStatementMethods(Method method, int hashCode, Callable<?> callable, Object[] args)
            throws Exception {
        log.info("intercepting statement method {}", method.getName());
        Object o = null;
        ConnectionInfo containingCI = conns.values().stream().filter(c -> c.getCurrentStatement().getHash() == hashCode)
                .findFirst().orElse(null);
        if (containingCI == null) {
            log.warn("unexpected situation happened: statement can't found containing connection!");
        }
        if (method.getName().equals("close")) {
            o = callable.call();
            // TODO statement close method not intercepted ??
            if (containingCI != null) {
                containingCI.setCurrentStatement(null);
                log.info("statement@{} closed, detached from connection@{}", hashCode, containingCI.getHash());
            }
        }
        // all statement execution method trigger
        else if (method.getName().startsWith("execute")) {
            String stack = getCurrentThreadStackTrace();
            String stackHash = StringUtils.md5(stack);
            stacks.put(stackHash, stack);
            log.info("statement@{} {} started by {}", hashCode, method.getName(), stackHash);
            long est = System.currentTimeMillis();
            if (containingCI != null) {
                containingCI.getCurrentStatement().setExecutionStartTime(est);
                containingCI.getCurrentStatement().setStackHash(stackHash);
                containingCI.getCurrentStatement().setExecutionCallStack(stack);
                if (args.length > 0) {
                    containingCI.getCurrentStatement().setSqlText((String) args[0]);
                }
            }
            Histogram.Timer timer = JDBCMetrics.SQL_EXECUTION_TIME.startTimer();
            try {
                o = callable.call();
            } finally {
                timer.observeDuration();
            }
            long eet = System.currentTimeMillis();
            log.info("statement@{} {} finished, duration is {}", hashCode, method.getName(), (eet - est));
            if (containingCI != null) {
                containingCI.getCurrentStatement().setExecutionEndTime(eet);
                slowStatements.add(containingCI.getCurrentStatement());
            }
        }
        return o;
    }

    public static String getCurrentThreadStackTrace() throws IOException {
        StringWriter sw = new StringWriter();
        Throwable t = new Throwable("");
        t.printStackTrace(new PrintWriter(sw));
        String stackTrace = sw.toString();
        sw.close();
        return stackTrace;
    }

    @Advice.OnMethodExit
    public static void intercept(@net.bytebuddy.asm.Advice.Origin Constructor<?> m, @This Object inst)
            throws Exception {
        log.debug("----------------- constructor intercept -------------------------");
        if (java.sql.Connection.class.isAssignableFrom(m.getDeclaringClass())) {
            // some CP library override hashCode method
            int hashCode = System.identityHashCode(inst);
            ConnectionInfo ci = new ConnectionInfo();
            ci.setHash(hashCode);
            ci.setCreationTime(System.currentTimeMillis());
            String stackTrace = getCurrentThreadStackTrace();
            String shash = StringUtils.md5(stackTrace);
            stacks.put(shash, stackTrace);
            // ci.setStack(stackTrace);
            ci.setStackHash(shash);
            log.info("connection@{} acquired by stack@{}", hashCode, shash);
            log.debug(stackTrace);
            conns.put(hashCode, ci);
            Integer scount = counstsByStack.get(ci.getStackHash());
            if (scount == null) {
                counstsByStack.put(ci.getStackHash(), 1);
            } else {
                counstsByStack.put(ci.getStackHash(), scount + 1);
            }
        }
    }

}

但是,這在某些情況下不起作用。 以 HikariCP 為例:

HikariCP 的實現有一個抽象的 class ProxyConnection ,它實現了close方法,然后是一個 class HikariProxyConnection擴展了ProxyConnection ,它覆蓋了一些方法,除了close 如果我在HikariProxyConnection上設置檢測,則不會攔截close方法。 如果我將代碼更改為:

new AgentBuilder.Default()
                .type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(isAbstract()))
                .transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);

它適用於 HikariCP,但不適用於其他連接池實現。

對於我的用例,有沒有統一的方法? 無論使用什么連接池,即使是普通的 JDBC。

你的匹配器:

isSubTypeOf(java.sql.Connection.class).and(not(isAbstract()) 

明確排除抽象類。 如果您使用Advice ,您將需要檢測所有方法以實現您想要的。

此外,您似乎正在混合概念:

import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

后面的注釋屬於MethodDelegation ,而不是Advice 請參閱 javadoc 以了解如何應用建議。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM