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.Driver
的connect
方法。
这里可能会有问题。如果我们只需要包装Statement的话,那我们直接拦截Connection
的createStatement
,prepareStatement
,prepareCall
方法,将这些函数的返回值包装起来不就行了?这里还有一个问题,就是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
- 在
WrapConnection
中调用createStatement
,prepareStatement
,prepareCall
时再次包装Statement。
...
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return new WrapStatement( this, realConnection.createStatement(resultSetType, resultSetConcurrency));
}
...
- 实际调用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);
}
});
}
- 真实调用前后加入操作:
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语句的详细信息可以不在流程页展示,可以做点击之后展示详情,此处暂不优化。