一步一步写APM(六)--Mysql插件

Jdbc拦截是一个比较特殊的情况

首先,我们需要拦截的依然是Jdbc的查询,更新等操作,具体涉及的函数就是:execute、executeQuery和executeUpdate。 但是对于Statement是有多种的,具体见:https://my.oschina.net/heweipo/blog/355729 在前面的情况中我们都是只需要拦截一个类的情况,这次是不是需要写拦截多个类呢?能不能通过前面的拦截函数的方式改造拦截类?

public class JdbcEnhancer extends PluginEnhancer {

    public static String ENHANCE_CLASSNAME = "com.mysql.jdbc.StatementImpl";
    public static String INTERCEPTOR_CLASSNAME = "younian.apmsample.agent.plugin.jdbc.JdbcInterceptor";

    @Override
    public String getEnhanceClassName() {
        return ENHANCE_CLASSNAME;
    }

    @Override
    public String getInterceptorClassName() {
        return INTERCEPTOR_CLASSNAME;
    }

    @Override
    public ElementMatcher getInstanceMethodInterceptorMatch() {
        return named("executeQuery").or(named("executeUpdate"));
    }
}

有问题的就是getEnhanceClassName 返回的是一个单一String--表示只能拦截一个类。假设我们可以拦截一批类: 比如使用 nameStartWith("younian.")拦截这一批类,那么我们在 public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader)函数中如何通过TypeDescription来获取响应的Enhancer呢?因为我们与Enhancer对应的不再是一个具体的类名,而是一个nameStartWith("younian.")。所以拦截一批类是不好实现的。

我们需要实现的目标是在execute等方法之前及之后加上span的创建以及停止工作,那么其实我们将这些方法包装一下,在内部我们再调用真正的statement进行操作不就可以了吗?

拦截Driver

拦截com.mysql.jdbc.Driverconnect方法。 这里可能会有问题。如果我们只需要包装Statement的话,那我们直接拦截ConnectioncreateStatementprepareStatementprepareCall方法,将这些函数的返回值包装起来不就行了?这里还有一个问题,就是Connection也是有多种的,所以对于Connection的具体拦截哪个类还是不清楚。所以从最开始拦截,从Driver开始拦截。

包装Connnection

public class JdbcInterceptor extends ClassInstanceMethodInterceptor {
    @Override
    protected void before(Method method, Object[] allArguments) {
    }

    @Override
    protected Object after(Object result, Object[] allArguments) {
        if(allArguments == null || allArguments.length < 1 || allArguments[0] == null){
            return result;
        }

        String url = (String) allArguments[0];
        if(result != null){
            Connection realConnection = (Connection) result;
            return new WrapConnection(url, realConnection);
        }
        return result;
    }
}

包装Statement

  1. WrapConnection中调用createStatementprepareStatementprepareCall时再次包装Statement。
...
@Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return new WrapStatement( this, realConnection.createStatement(resultSetType, resultSetConcurrency));
    }
...
  1. 实际调用exexute等操作:
@Override
	public int executeUpdate(String sql) throws SQLException {
		return ConnectionTracing.execute(realConnection, "executeUpdate", sql,
				new Executable<Integer>() {
					@Override
					public Integer exec(Connection realConnection, String sql) throws SQLException {
						return realStatement.executeUpdate(sql);
					}
				});
	}
  1. 真实调用前后加入操作:
public class ConnectionTracing {

    public static <R> R execute(Connection realConnection, String method, String sql,
                                Executable<R> exec) throws SQLException {

        ContextManager.createSpan("Mysql:/JDBC/Connection/" + method);
        try {
            return exec.exec(realConnection, sql);
        } catch (SQLException e) {
            throw e;
        } finally {
            Span span = ContextManager.stopSpan();
            System.out.println(span);
        }
    }

    public interface Executable<R> {
        R exec(Connection realConnection, String sql) throws SQLException;
    }
}

效果预览 阶段Tag:添加Mysql插件

mysql语句的详细信息可以不在流程页展示,可以做点击之后展示详情,此处暂不优化。 image.png

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×