当前位置: 首页 > news >正文

Skywalking流程分析_7(构造/实例方法的增强流程)

前言

this.enhance

protected DynamicType.Builder<?> enhance(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder,ClassLoader classLoader, EnhanceContext context) throws PluginException {//静态方法的增强newClassBuilder = this.enhanceClass(typeDescription, newClassBuilder, classLoader);//构造、实例方法的增强newClassBuilder = this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context);return newClassBuilder;
}

在上文中我们分析的静态方法的增强是如何做的,接下来我们来分析构造和实例方法

ClassEnhancePluginDefine.enhanceInstance(typeDescription, newClassBuilder, classLoader, context)

protected DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader,EnhanceContext context) throws PluginException {//查找构造方法的切点ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();//查找实例方法的切点InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();String enhanceOriginClassName = typeDescription.getTypeName();boolean existedConstructorInterceptPoint = false;if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) {existedConstructorInterceptPoint = true;}boolean existedMethodsInterceptPoints = false;if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {existedMethodsInterceptPoints = true;}/*** nothing need to be enhanced in class instance, maybe need enhance static methods.* 没有要增强的那么直接返回*/if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) {return newClassBuilder;}/*** Manipulate class source code.<br/>** new class need:<br/>* 1.Add field, name {@link #CONTEXT_ATTR_NAME}.* 2.Add a field accessor for this field.** And make sure the source codes manipulation only occurs once.* */// 如果被拦截的类没有实现EnhancedInstance接口if (!typeDescription.isAssignableTo(EnhancedInstance.class)) {if (!context.isObjectExtended()) {//给要增强的类添加_$EnhancedClassField_ws字段,//并实现了EnhancedInstance接口的set/get方法,来获取这个字段//新增这个字段的作用是可以放置自己的数据,然后通过这两个set/get方法来存放和拿取newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE).implement(EnhancedInstance.class).intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));// 将记录状态EnhanceContext标识符设置为已新增新的字段或者实现新的接口                                 context.extendObjectCompleted();}}/*** 2. enhance constructors* 构造器增强逻辑*/if (existedConstructorInterceptPoint) {//获取所有构造方法的切点for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) {//要增加的类是Bootstrap类加载器加载的if (isBootstrapInstrumentation()) {newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()).intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration().to(BootstrapInstrumentBoost.forInternalDelegateClass(constructorInterceptPoint.getConstructorInterceptor()))));} else {newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()).intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration().to(new ConstructorInter(constructorInterceptPoint.getConstructorInterceptor(), classLoader))));}}}/*** 3. enhance instance methods* 实例方法增强逻辑*/if (existedMethodsInterceptPoints) {//获取所有实例方法的切点for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) {String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor();if (StringUtil.isEmpty(interceptor)) {throw new EnhanceException("no InstanceMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName);}ElementMatcher.Junction<MethodDescription> junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher());if (instanceMethodsInterceptPoint instanceof DeclaredInstanceMethodsInterceptPoint) {junction = junction.and(ElementMatchers.<MethodDescription>isDeclaredBy(typeDescription));}if (instanceMethodsInterceptPoint.isOverrideArgs()) {//要增加的类是Bootstrap类加载器加载的if (isBootstrapInstrumentation()) {newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(OverrideCallable.class)).to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor)));} else {newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(OverrideCallable.class)).to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader)));}} else {//要增加的类是Bootstrap类加载器加载的if (isBootstrapInstrumentation()) {newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().to(BootstrapInstrumentBoost.forInternalDelegateClass(interceptor)));} else {newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().to(new InstMethodsInter(interceptor, classLoader)));}}}}return newClassBuilder;
}

总结

  • 给要增强的类添加_$EnhancedClassField_ws字段,并实现了EnhancedInstance接口的set/get方法,来获取这个字段。新增这个字段的作用是可以放置自己的数据,然后通过这两个set/get方法来存放和拿取
  • 执行构造器增强逻辑
  • 执行实例方法增强逻辑

下面我们来逐步分析构造器增强和实例方法增强

构造器增强

非JDK的类库是被ConstructorInter所增强的

newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()).intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration().to(new ConstructorInter(constructorInterceptPoint.getConstructorInterceptor(), classLoader))));
ConstructorInter
public class ConstructorInter {private static final ILog LOGGER = LogManager.getLogger(ConstructorInter.class);private InstanceConstructorInterceptor interceptor;/*** @param constructorInterceptorClassName class full name.*/public ConstructorInter(String constructorInterceptorClassName, ClassLoader classLoader) throws PluginException {try {// 实例化自定义的拦截器//对于同一份字节码,如果由不同的类加载器进行加载,那么加载出来的两个实例不相同interceptor = InterceptorInstanceLoader.load(constructorInterceptorClassName, classLoader);} catch (Throwable t) {throw new PluginException("Can't create InstanceConstructorInterceptorV2.", t);}}/*** Intercept the target constructor.** @param obj          target class instance.* @param allArguments all constructor arguments*/@RuntimeTypepublic void intercept(@This Object obj, @AllArguments Object[] allArguments) {try {//这里向上转型为EnhancedInstance的原因是为了使用之前新添加的_$EnhancedClassField_ws字段//使用setSkyWalkingDynamicField/getSkyWalkingDynamicField方法来存放和获取自己的数据EnhancedInstance targetObject = (EnhancedInstance) obj;//在原来的构造方法执行完后,再执行interceptor.onConstruct(targetObject, allArguments);} catch (Throwable t) {LOGGER.error("ConstructorInter failure.", t);}}
}

ConstructorInter逻辑总结

  • 构造方法中实例化自定义的拦截器
  • 在原先的构造方法执行完,再执行interceptor.onConstruct(targetObject, allArguments)

kafka为例

public class KafkaProducerInstrumentation extends AbstractKafkaInstrumentation {public static final String ENHANCE_CLASS = "org.apache.kafka.clients.producer.KafkaProducer";public static final String CONSTRUCTOR_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.kafka.ProducerConstructorInterceptor";public static final String CONSTRUCTOR_INTERCEPTOR_FLAG = "org.apache.kafka.clients.producer.ProducerConfig";@Overridepublic ConstructorInterceptPoint[] getConstructorsInterceptPoints() {return new ConstructorInterceptPoint[] {new ConstructorInterceptPoint() {@Overridepublic ElementMatcher<MethodDescription> getConstructorMatcher() {return takesArgumentWithType(0, CONSTRUCTOR_INTERCEPTOR_FLAG);}@Overridepublic String getConstructorInterceptor() {return CONSTRUCTOR_INTERCEPTOR_CLASS;}}};}@Overrideprotected ClassMatch enhanceClass() {return byName(ENHANCE_CLASS);}
}
  • 这里拦截的是org.apache.kafka.clients.producer.KafkaProducer类的构造方法
  • 拦截具体的构造方法是第一个参数类型为org.apache.kafka.clients.producer.ProducerConfig
  • 拦截的执行类是org.apache.skywalking.apm.plugin.kafka.ProducerConstructorInterceptor
public class ProducerConstructorInterceptor implements InstanceConstructorInterceptor {@Overridepublic void onConstruct(EnhancedInstance objInst, Object[] allArguments) {ProducerConfig config = (ProducerConfig) allArguments[0];objInst.setSkyWalkingDynamicField(StringUtil.join(';', config.getList("bootstrap.servers").toArray(new String[0])));}
}

注意

能看到这里使用_$EnhancedClassField_ws字段来保存一些数据方便后续使用,这也是为什么要执行这段逻辑,设置此字段的原因

if (!typeDescription.isAssignableTo(EnhancedInstance.class)) {if (!context.isObjectExtended()) {//给要增强的类添加_$EnhancedClassField_ws字段,//并实现了EnhancedInstance接口的set/get方法,来获取这个字段//新增这个字段的作用是可以放置自己的数据,然后通过这两个set/get方法来存放和拿取newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE).implement(EnhancedInstance.class).intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));context.extendObjectCompleted();}
}
实例方法增强

首先看这段逻辑

ElementMatcher.Junction<MethodDescription> junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher());
if (instanceMethodsInterceptPoint instanceof DeclaredInstanceMethodsInterceptPoint) {//拦截到的方法必须是当前类上的 注解匹配到的方法有可能不是当前类上的junction = junction.and(ElementMatchers.<MethodDescription>isDeclaredBy(typeDescription));
}
  • 比如说,要对Student.add()方法增强,那么直接指定类名即可,但如果想对所有Controller的SpringMVC控制层的方法进行增强,那么就要使用注解。
  • 但有可能通过注解匹配到的方法不是当前类上的,所以判断如果拦截点为DeclaredInstanceMethodsInterceptPoint会新增匹配条件:获取到的方法必须是当前类上的
    非JDK的类库是被ConstructorInter所增强的

我们依旧先分析不是JDK类库的类

不修改原方法的参数
newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().to(new InstMethodsInter(interceptor, classLoader)));

具体逻辑在InstMethodsInter

InstMethodsInter

public class InstMethodsInter {private static final ILog LOGGER = LogManager.getLogger(InstMethodsInter.class);/*** An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class}* type will trigger classloader failure. If you want to know more, please check on books about Classloader or* Classloader appointment mechanism.*/private InstanceMethodsAroundInterceptor interceptor;/*** @param instanceMethodsAroundInterceptorClassName class full name.*/public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) {try {//使用自定义加载器加载拦截器interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);} catch (Throwable t) {throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t);}}/*** Intercept the target instance method.** @param obj          target class instance.* @param allArguments all method arguments* @param method       method description.* @param zuper        the origin call ref.* @return the return value of target instance method.* @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a*                   bug, if anything triggers this condition ).*/@RuntimeTypepublic Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable<?> zuper,@Origin Method method) throws Throwable {EnhancedInstance targetObject = (EnhancedInstance) obj;MethodInterceptResult result = new MethodInterceptResult();try {//前置增强interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result);} catch (Throwable t) {LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName());}Object ret = null;try {if (!result.isContinue()) {ret = result._ret();} else {//执行原有方法ret = zuper.call();}} catch (Throwable t) {try {//异常增强interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t);} catch (Throwable t2) {LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName());}throw t;} finally {try {//后置增强ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret);} catch (Throwable t) {LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName());}}return ret;}
}
修改原方法的参数
newClassBuilder = newClassBuilder.method(junction).intercept(MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(OverrideCallable.class)).to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader)));

具体逻辑在InstMethodsInterWithOverrideArgs

InstMethodsInterWithOverrideArgs

public class InstMethodsInterWithOverrideArgs {private static final ILog LOGGER = LogManager.getLogger(InstMethodsInterWithOverrideArgs.class);/*** An {@link InstanceMethodsAroundInterceptor} This name should only stay in {@link String}, the real {@link Class}* type will trigger classloader failure. If you want to know more, please check on books about Classloader or* Classloader appointment mechanism.*/private InstanceMethodsAroundInterceptor interceptor;/*** @param instanceMethodsAroundInterceptorClassName class full name.*/public InstMethodsInterWithOverrideArgs(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) {try {interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);} catch (Throwable t) {throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t);}}/*** Intercept the target instance method.** @param obj          target class instance.* @param allArguments all method arguments* @param method       method description.* @param zuper        the origin call ref.* @return the return value of target instance method.* @throws Exception only throw exception because of zuper.call() or unexpected exception in sky-walking ( This is a*                   bug, if anything triggers this condition ).*/@RuntimeTypepublic Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method,@Morph OverrideCallable zuper) throws Throwable {EnhancedInstance targetObject = (EnhancedInstance) obj;MethodInterceptResult result = new MethodInterceptResult();try {interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result);} catch (Throwable t) {LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName());}Object ret = null;try {if (!result.isContinue()) {ret = result._ret();} else {ret = zuper.call(allArguments);}} catch (Throwable t) {try {interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t);} catch (Throwable t2) {LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName());}throw t;} finally {try {ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret);} catch (Throwable t) {LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName());}}return ret;}
}

可以看到实例方法的增强逻辑和静态方法的大致相同

这里面有个问题,为什么用zuper.call()执行原方法的调用,不能替换成method.invoke(clazz.newInstance())吗?

这里需要借助一个Java实时反编译工具friday,看看项目执行SkyWalking Agent之后反编译的结构

原始类:

@RestController
@RequestMapping("/api/hello")
public class UserController {@GetMappingpublic String sayHello() {return "hello";}}

增强后:

@RestController
@RequestMapping(value={"/test"})
public class TestController
implements EnhancedInstance {private volatile Object _$EnhancedClassField_ws;public static volatile /* synthetic */ InstMethodsInter delegate$mvblfc0;public static volatile /* synthetic */ InstMethodsInter delegate$hfbkh30;public static volatile /* synthetic */ ConstructorInter delegate$gr07501;private static final /* synthetic */ Method cachedValue$kkbY4FHP$ldstch2;public static volatile /* synthetic */ InstMethodsInter delegate$lvp69q1;public static volatile /* synthetic */ InstMethodsInter delegate$mpv7fs0;public static volatile /* synthetic */ ConstructorInter delegate$v0q1e31;private static final /* synthetic */ Method cachedValue$Hx3zGNqH$ldstch2;public TestController() {this(null);delegate$v0q1e31.intercept((Object)this, new Object[0]);}private /* synthetic */ TestController(auxiliary.YsFzTfDy ysFzTfDy) {}@RequestMappingpublic String test() {return (String)delegate$lvp69q1.intercept((Object)this, new Object[0], (Callable)new auxiliary.pEJy33Ip(this), cachedValue$Hx3zGNqH$ldstch2);}private /* synthetic */ String test$original$70VVkKcL() {return "sucess";}static {ClassLoader.getSystemClassLoader().loadClass("org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, TestController.class, 544534948);cachedValue$Hx3zGNqH$ldstch2 = TestController.class.getMethod("test", new Class[0]);}final /* synthetic */ String test$original$70VVkKcL$accessor$Hx3zGNqH() {return this.test$original$70VVkKcL();}
}

然后还要再看一下SkyWalkingAgent.premain()

public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {final PluginFinder pluginFinder;//省略...//指定byteBuddy要拦截的类agentBuilder.type(pluginFinder.buildMatch())//指定字节码增强的工具.transform(new Transformer(pluginFinder))//redefine和retransformation的区别在于是否保留修改前的内容.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).with(new RedefinitionListener()).with(new Listener())//将agent安装到instrumentation.installOn(instrumentation);//省略...            
}
  • redefineretransform的区别在于是否保留修改前的内容,从反编译的内容可以看到test()方法的内容已经被修改掉了,原方法被重命名为test o r i g i n a l original original+随机字符串
  • 新的test()方法中调用了InstMethodsInter的intercept()方法,最后传入的intercept()的method参数是被修改后的test()方法,不再指向原生的test()方法,如果在InstMethodsInter使用method.invoke(clazz.newInstance())就相当于自己调自己,就会死递归下去

总结

  • 构造方法
    • 是JDK类库的类
    • 不是JDK类库的类
      • 在原有的构造方法执行完后,在执行onContruct进行增强
  • 实例方法
    • 和静态方法相同

以上就是构造方法和实例方法增强逻辑。

在我们执行增强逻辑前,我们先看下插件拦截类的加载过程

  • 静态方法增强前的加载
//clazz 要修改的字节码的原生类
StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz.getClassLoader());
  • 构造/实例方法前的加载
//当前拦截到的类的类加载器
interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);

这个加载的方法很重要,它是将插件拦截类和业务执行类的类加载器进行融合的关键,也是skywalking的精髓所在,下篇文章我们来详细的分析下。

相关文章:

  • JS-项目实战-点击水果名修改特定水果库存记录
  • flutter ios Exception : No Impeller Context is Available
  • 【JUC】三、集合的线程安全
  • 【机器学习基础】机器学习的模型评估(评估方法及性能度量原理及主要公式)
  • 用Postman发送xml数据
  • 基于SSM的教学管理系统设计与实现
  • vue项目中 commonJS转es6
  • Azure 机器学习:在 Azure 机器学习中使用 Azure OpenAI 模型
  • 传递函数的推导和理解
  • iApp祁天社区UI成品源码 功能齐全的社区应用
  • 51单片机应用从零开始(五)·加减乘除运算
  • Spark DataFrame join后移除重复的列
  • 【Linux网络】搭建内外网的网关服务器,实现DNS分离解析与DHCP自动分配
  • 决策树,sql考题,30个经典sql题目
  • 扩散模型实战(九):使用CLIP模型引导和控制扩散模型
  • 分享的文章《人生如棋》
  • .pyc 想到的一些问题
  • 2017年终总结、随想
  • HomeBrew常规使用教程
  • js ES6 求数组的交集,并集,还有差集
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • MySQL数据库运维之数据恢复
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • 多线程 start 和 run 方法到底有什么区别?
  • 基于axios的vue插件,让http请求更简单
  • 老板让我十分钟上手nx-admin
  • 类orAPI - 收藏集 - 掘金
  • 深入 Nginx 之配置篇
  • 我与Jetbrains的这些年
  • ###STL(标准模板库)
  • #define,static,const,三种常量的区别
  • (C语言)fgets与fputs函数详解
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (学习日记)2024.01.09
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .gitignore文件—git忽略文件
  • .NET Standard 的管理策略
  • .NET 服务 ServiceController
  • .NET 命令行参数包含应用程序路径吗?
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET6实现破解Modbus poll点表配置文件
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .net反编译的九款神器
  • [ 云计算 | AWS ] 对比分析:Amazon SNS 与 SQS 消息服务的异同与选择
  • [20190416]完善shared latch测试脚本2.txt
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt
  • [boost]使用boost::function和boost::bind产生的down机一例
  • [BZOJ3757] 苹果树
  • [BZOJ4016][FJOI2014]最短路径树问题
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [C#]无法获取源 https://api.nuge t.org/v3-index存储签名信息解决方法