Alibaba OneJavaAgent 初探

一、使用

1、plugin

在这种模式下,one-java-agent在加载plugin时,会从以下几个方面进行,分别是

描述
plugin.properties这个文件是一个引导文件,里面的配置内容引导plugin加载
plugin-jarplugin的引导包,内有实现PluginActivator,用于引导和管理plugin的初始化和销毁
instrument-lib这个目录存放plugin的代理逻辑和Instrument类

在项目中有demo,以dubbo-test-plugin为例,在${oneagent.home}路径下新建目录,例如dubbo,该目录的内容如下:
在这里插入图片描述

plugin.properties文件内容如下:
在这里插入图片描述

在运行dubbo应用时添加相应的启动参数:

java -javaagent:${oneagent.home}/one-java-agent.jar=oneagent.home=${oneagent.home} org.apache.dubbo.springboot.demo.provider.ProviderApplication

如果是在idea则是在run/debug configurations 添加运行配置:
在这里插入图片描述

Dubbo consumer应用也是一样的,运行发起调用后,会输出instrument-jar中添加的增强逻辑。
在dubbo-test-plugin中是对org.apache.dubbo.rpc.Invoker 接口的实现类在invoke()方法中都添加日志,内容如下:
在这里插入图片描述

2、agent

one-java-agent 对于原生agent jar的支持也是非常简单直接的,在插件目录下面放一个plugin.properties,并且放上原生的agent jar文件。

plugin.properties配置内容例如:

type=traditional
name=demo-agent
version=1.0.0
agentJarPath=demo-agent.jar

则 one java agent会启动这个demo-agent。

二、设计

One java agent的代码量并不多,设计也非常巧妙,模块示意图如下:
在这里插入图片描述

one-java-agent的引导就是Bootstrap,会先初始化三个component,分别管理classloader, class share, 和class file transformer, 然后再从oneagent.home目录加载plugin的信息,找到PluginActivator,逐个激活plugin,在这个过程中会找到plugin中的instrument信息(增强类),创建InstrumentTransformer,然后就交由Java的Instrumentation完成后面的转换逻辑。
这里提一下,InstrumentTransformer中关于字节码的处理是通过alibaba-bytekit实现的。

三、alibaba-bytekit与bytebuddy

ByteKit和byte-buddy都是基于ASM提供更高层的字节码处理能力, 两者都提供丰富的注入点支持。

1、两者比较

ByteKit是阿里开源的字节码处理插件,在其开源的介绍内容中有对比byte-buddy。

功能函数Enter/Exit注入点绑定数据inline防止重复增强避免装箱/拆箱开销origin调用替换@ExceptionHandler
ByteKit@AtEnter
@AtExit
@AtExceptionExit
@AtFieldAccess
@AtInvoke
@AtInvokeException
@AtLine
@AtSyncEnter
@AtSyncExit
@AtThrow
this/args/return/throw
field
locals
子调用入参/返回值/子调用异常
line number
ByteBuddyOnMethodEnter
@OnMethodExit
@OnMethodExit#onThrowable()
this/args/return/throw
field
locals
传统AOPEnter
Exit
Exception
this/args/return/throw

2、实验-入参替换

对于ByteKit和bytebuddy进行了一次在agent场景下修改方法入参的实验,结合两者使用,就编码、调试方面进行对比总结。

2.1、 ByteKit

ByteKit的是直接在原方法中修改字节码,场景如下:
原方法:

    public String invokeList(List<String> list) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (String s : list) {
            if(sb.length()>1){
                sb.append(", ");
            }
            sb.append(s);
        }
        sb.append("]");
        return sb.toString();
    }

apm增强方法:

@Instrument(Interface = "org.apache.dubbo.springboot.demo.provider.Caller")
public abstract class Caller {

    public String invokeList(List<String> list) {
        if(list==null || list.size()<2){
            String result = InstrumentApi.invokeOrigin();
            System.err.println(result);
            return "changed: " + result;
        }
        List<String> ls = list;
        String results = "";
        for (String s : ls) {
            list = Arrays.asList(s);
            String result = InstrumentApi.invokeOrigin();
            results += result + ",";
        }

        System.err.println(results);
        return "changed: " + results;
    }
}

ByteKit处理后新的方法(反编译后的代码):

public String invokeList(List<String> list) {
        if (list == null || list.size() < 2) {
            List<String> list2 = list;
            InvokeOriginDemo invokeOriginDemo = this;
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("[");
            for (String string : list2) {
                if (stringBuilder.length() > 1) {
                    stringBuilder.append(", ");
                }
                stringBuilder.append(string);
            }
            stringBuilder.append("]");
            String result = stringBuilder.toString();
            System.err.println(result);
            return "changed: " + result;
        }
        List<String> ls = list;
        String results = "";
        for (String s : ls) {
            List<String> list3 = list = Arrays.asList(s);
            InvokeOriginDemo invokeOriginDemo = this;
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("[");
            for (String string : list3) {
                if (stringBuilder.length() > 1) {
                    stringBuilder.append(", ");
                }
                stringBuilder.append(string);
            }
            stringBuilder.append("]");
            String result = stringBuilder.toString();
            results = results + result + ",";
        }
        System.err.println(results);
        return "changed: " + results;
    }

idea调试:
在这里插入图片描述

2.2、bytebuddy

本次实验中采用bytebuddy的intercept Implementation 方式,实验代码如下:
进行插桩处理:

    private static void bytebuddyTransform(Instrumentation instrumentation) {
        new AgentBuilder.Default()
                .type(ElementMatchers.named("com.saleson.learn.java.agent.Caller"))
                .transform(
                        (builder, type, classLoader, module) -> {
                            DynamicType.Builder.MethodDefinition.ImplementationDefinition tImplementationDefinition = null;
                            tImplementationDefinition = builder.method(ElementMatchers.any());
                            return tImplementationDefinition.intercept(MethodDelegation.to(TimeInterceptor.class));
                        }
                ).installOn(instrumentation);
    }

增强代码:

    public static class TimeInterceptor {

        /**
         * 进行方法拦截, 注意这里可以对所有修饰符的修饰的方法(包含private的方法)进行拦截
         *
         * @param method   待处理方法
         * @param callable 原方法执行
         * @param args 方法入参
         * @return 执行结果
         */
        @RuntimeType
        public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception {
            List<String> list = (List<String>) args[0];
            Field argField1 = callable.getClass().getDeclaredFields()[1];
            argField1.setAccessible(true);

            boolean argsEquals = list.equals(argField1.get(callable));
            System.out.println("call arg equals args[0]: " + argsEquals);
            String results = "";
            for (int i = 0; i < list.size(); i++) {
                String s = list.get(i);
                List<String> l = Arrays.asList(s);
                argField1.set(callable, l);
                String res = (String) callable.call();
                results += res + ", ";
                System.out.println(String.format("$d/%d $s -> %s", i + 1, list.size(), s, res));
            }
            return "changed: " + results;
        }
    }

Idea 调试:
在这里插入图片描述

在这里插入图片描述

2.3、入参替换对比总结

本次实验主要测试ByteKit和bytebuddy关于方法入参替换的场景,仅针对这个场景而言,个人觉得bytebuddy设计的更优雅,能够将增强的代码和原代码做到有效隔离,而ByteKit是直接修改了原方法的字节码,对于增强代码无法调试,同时增强代码和原代码相当于是揉合在一块。
对于bytebuddy个人也觉得对于该场景仍有可优化的地方,比如添加修改方法入参的方法,对方法入参的处理更优雅。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值