之前我们介绍了插件的加载,接下来就是真正开始进行插件的执行了,首先要看下插件的结构是怎么样的,以阿里的druid
数据源为例
skywalking-plugin.def:
druid-1.x=org.apache.skywalking.apm.plugin.druid.v1.define.DruidPooledConnectionInstrumentation
druid-1.x=org.apache.skywalking.apm.plugin.druid.v1.define.DruidDataSourceInstrumentation
druid-1.x=org.apache.skywalking.apm.plugin.druid.v1.define.DruidDataSourceStatManagerInstrumentation
以第一个获取数据源连接的插件DruidPooledConnectionInstrumentation
为例
/**
* 插件的定义,继承xxxPluginDefine,通常命名为xxxInstrumentation
*/
public class DruidDataSourceInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "com.alibaba.druid.pool.DruidDataSource";
private static final String ENHANCE_METHOD = "getConnection";
private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.druid.v1.PoolingGetConnectInterceptor";
/**
* 在哪个类进行字节码增强
* */
@Override
protected ClassMatch enhanceClass() {
return byName(ENHANCE_CLASS);
}
/**
* 进行构造方法的拦截
* */
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
}
/**
* 进行实例方法的拦截
* */
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[]{
new InstanceMethodsInterceptPoint() {
//对getConnection无参方法记性增强
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_METHOD).and(takesNoArguments());
}
//增强逻辑在哪个具体的插件类中执行
@Override
public String getMethodsInterceptor() {
return INTERCEPTOR_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
},
new InstanceMethodsInterceptPoint() {
//对getConnection有参方法记性增强
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_METHOD).and(takesArguments(String.class, String.class));
}
//增强逻辑在哪个具体的插件类中执行
@Override
public String getMethodsInterceptor() {
return INTERCEPTOR_CLASS;
}
//在增强时是否要对原方法的入参进行改变
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return new StaticMethodsInterceptPoint[0];
}
}
ClassInstanceMethodsEnhancePluginDefine
如果对构造方法/实例方法增强,则需继承此类
public abstract class ClassInstanceMethodsEnhancePluginDefine extends ClassEnhancePluginDefine {
/**
* @return null, means enhance no static methods.
*/
/**
* 静态方法拦截点
* */
@Override
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return null;
}
}
ClassStaticMethodsEnhancePluginDefine
如果对构造方法/实例方法增强,则需继承此类
public abstract class ClassStaticMethodsEnhancePluginDefine extends ClassEnhancePluginDefine {
/**
* @return null, means enhance no constructors.
*/
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return null;
}
/**
* @return null, means enhance no instance methods.
*/
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return null;
}
}
结构
ClassInstanceMethodsEnhancePluginDefine
和ClassStaticMethodsEnhancePluginDefine
都继承了ClassEnhancePluginDefine
ClassEnhancePluginDefine
继承了AbstractClassEnhancePluginDefine
在返回要增强的类方法中ClassMatch
就是要进行匹配的策略,有名字匹配、前缀匹配等,这里就不做详细分析了
@Override
protected ClassMatch enhanceClass() {
return byName(ENHANCE_CLASS);
}
这里我们知道了如何指定在哪个类,对哪个方法进行增强,下面我们就来看看增强逻辑的类是怎么做的,仍然以阿里的druid
数据源为例。指定了是org.apache.skywalking.apm.plugin.druid.v1.PoolingGetConnectInterceptor
public class PoolingGetConnectInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
AbstractSpan span = ContextManager.createLocalSpan("Druid/Connection/" + method.getName());
span.setComponent(ComponentsDefine.ALIBABA_DRUID);
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
ContextManager.stopSpan();
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
ContextManager.activeSpan().errorOccurred().log(t);
}
}
可以看到和APO非常的像,前置/后置/异常增强
总结
ClassInstanceMethodsEnhancePluginDefine
ClassStaticMethodsEnhancePluginDefine
ClassInstanceMethodsEnhancePluginDefine
和ClassStaticMethodsEnhancePluginDefine
都继承ClassEnhancePluginDefine
,ClassEnhancePluginDefine
继承AbstractClassEnhancePluginDefine
enhanceClass()
getConstructorsInterceptPoints
getInstanceMethodsInterceptPoints
getStaticMethodsInterceptPoints
NameMatch
IndirectMatch
PrefixMatch
,前缀匹配MethodAnnotationMatch
,注解匹配skywalking是把不同版本的框架,来分别设置插件来对应着不用的版本,就拿常见的Srping来说,插件结构为
mvc-annotation-3.x-plugin
mvc-annotation-4.x-plugin
mvc-annotation-5.x-plugin
但有个关键的问题,skywalking是怎么识别出不同的Spring版本来执行对应版本的插件?
skywalking对这个问题的处理很巧妙,就是判断当前的类加载器中在相应的版本是否有对应类和方法
当判断版本之间的类都相同是,类识别就没有办法了,这时就需要方法识别
test
方法,返回类型为Integer
,入参类型为Integer
、String
。Spring5.x的A类也存在test
方法,但返回类型为String
,入参类型为String
Intger test(Integer,String)
,那么Spring-4.x-plugin生效String test(String)
,那么Spring-5.x-plugin生效witnessClasses
就是类识别witnessMethods
就是方法识别AbstractClassEnhancePluginDefine
定义public abstract class AbstractClassEnhancePluginDefine {
protected String[] witnessClasses() {
return new String[] {};
}
protected List<WitnessMethod> witnessMethods() {
return null;
}
那么skywalking到底是怎么判断类是否存在呢?
AgentClassLoader
加载的AgentClassLoader
的父类加载器是AppClassLoader
AgentClassLoader
中找不到要识别的类就会向上委派给AppClassLoader
AppClassLoader
找不到就向上委派,知道顶级的BootStrapClassLoader
类识别
Spring3.x
public abstract class AbstractSpring3Instrumentation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String WITHNESS_CLASSES = "org.springframework.web.servlet.view.xslt.AbstractXsltView";
@Override
protected final String[] witnessClasses() {
return new String[] {WITHNESS_CLASSES};
}
}
Srping4.x
public abstract class AbstractSpring4Instrumentation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String WITHNESS_CLASSES = "org.springframework.cache.interceptor.SimpleKey";
@Override
protected String[] witnessClasses() {
return new String[] {
WITHNESS_CLASSES,
"org.springframework.cache.interceptor.DefaultKeyGenerator"
};
}
}
Spring5.x
public abstract class AbstractSpring5Instrumentation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String WITNESS_CLASSES = "org.springframework.web.servlet.resource.HttpResource";
@Override
protected final String[] witnessClasses() {
return new String[] {WITNESS_CLASSES};
}
}
可以看到Spring3.x、Spring4.x、Spring5.x分别都有不同要识别的类
方法识别
dubbo2.7.x
public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter";
private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor";
private static final String CONTEXT_TYPE_NAME = "org.apache.dubbo.rpc.RpcContext";
private static final String GET_SERVER_CONTEXT_METHOD_NAME = "getServerContext";
@Override
protected List<WitnessMethod> witnessMethods() {
return Collections.singletonList(new WitnessMethod(
CONTEXT_TYPE_NAME,
named(GET_SERVER_CONTEXT_METHOD_NAME).and(
returns(named(CONTEXT_TYPE_NAME)))
));
}
}
dubbo3.x
public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String CONTEXT_TYPE_NAME = "org.apache.dubbo.rpc.RpcContext";
public static final String GET_SERVER_CONTEXT_METHOD_NAME = "getServerContext";
public static final String CONTEXT_ATTACHMENT_TYPE_NAME = "org.apache.dubbo.rpc.RpcContextAttachment";
@Override
protected List<WitnessMethod> witnessMethods() {
return Collections.singletonList(
new WitnessMethod(
CONTEXT_TYPE_NAME,
named(GET_SERVER_CONTEXT_METHOD_NAME).and(
returns(named(CONTEXT_ATTACHMENT_TYPE_NAME)))
));
}
}
可以看到是通过dubbo2.7.x和dubbo3.x的getServerContext
方法返回的不同类型来判断
总结
接下来开始分析字节码的增强过程