0
点赞
收藏
分享

微信扫一扫

ByteBuddy基本用法


ByteBuddy是一种字节码技术框架,其广泛用于中间件开发,用于字节码增强,变更字节码的形式来拦截,用途如:链路追踪,系统JVM状态监控,耗时分析等。目前市面上常见的链路追踪框架为:skywalking、美团cat等。本博客有对应skywalking解析

 

相关资料

官网:https://bytebuddy.net/

skywalking源码解析(不定期更新):https://gitee.com/lidishan/kywalking-source-code-analysis

 

ByteBuddy参考代码

public class Boot {

    public static void main(String[] args) throws Exception {
        // 1 基本用法    
        base();

        // 2 耗时、入参出参   
        use();

        // 3 使用委托实现抽象类方法并注入自定义注解信息  
        abstractAnnonation();
    }

    private static void abstractAnnonation() {
        // -- 生成含有注解的泛型实现字类
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                // 创建复杂类型的泛型注解
                .subclass(TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build())
                // 添加类信息包括地址
                .name(Repository.class.getPackage().getName().concat(".").concat("UserRepository"))
                // 匹配处理的方法
                .method(ElementMatchers.named("queryData"))
                // 交给委托函数
                .intercept(MethodDelegation.to(UserRepositoryInterceptor.class))
                // 拦截对应注解
                .annotateMethod(AnnotationDescription.Builder.ofType(RpcGatewayMethod.class)
                        .define("methodName", "queryData")
                        .define("methodDesc", "查询数据").build())
                .annotateType(AnnotationDescription.Builder.ofType(RpcGatewayClazz.class)
                        .define("alias", "dataApi")
                        .define("clazzDesc", "查询数据信息")
                        .define("timeOut", 350L).build())
                .make();
        // 输出类信息到目标文件夹下
        outputClazz(dynamicType.getBytes(), 4);
    }

    private static void use() throws Exception {
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                // 继承的类
                .subclass(BizMethod.class)
                // 被监控的方法
                .method(ElementMatchers.named("queryUserInfo"))
                // 具体监控实现类
                .intercept(MethodDelegation.to(MonitorDemo.class))
                .make();
        // 加载类
        Class<?> clazz = dynamicType.load(Boot.class.getClassLoader())
                .getLoaded();
        // 调用方法,测试监控效果
        BizMethod bizMethod = new BizMethod();
        System.out.println(bizMethod.queryUserInfo("10001", "Adhl9dkl"));
        // !!!用反射调用才有效果,
        //      那premain怎么结合bytebuddy使用?通过agentBuilder.with(listener).installOn(inst)绑定
        clazz.getMethod("queryUserInfo", String.class, String.class).invoke(clazz.newInstance(), "10001", "Adhl9dkl");
    }

    private static void base() throws Exception {
        // 1 第一次输出一个简单的结构体
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                // 定义继承的类
                .subclass(Object.class)
                // 定义命名空间
                .name("com.bd")
                .make();
        // 输出类字节码
        outputClazz(dynamicType.getBytes(), 1);

        // 2 增加一些参数、属性信息
        DynamicType.Unloaded<?> dynamicType2 = new ByteBuddy()
                // 定义继承的类
                .subclass(Object.class)
                // 定义命名空间
                .name("com.bd")
                // 定义一个main方法,public权限,并且是static
                .defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC)
                // 定义参数
                .withParameter(String[].class, "args")
                // 定义一个局部变量为"Hello World!"
                .intercept(FixedValue.value("Hello World!"))
                .make();
        outputClazz(dynamicType2.getBytes(), 2);

        // 3 委托函数使用
        DynamicType.Unloaded<?> dynamicType3 = new ByteBuddy()
                // 定义继承的类
                .subclass(Object.class)
                // 定义命名空间
                .name("com.bd")
                // 定义一个main方法,public权限,并且是static
                .defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC)
                // 定义参数
                .withParameter(String[].class, "args")
                // 定义委托的类,委托调用Hi.main()方法
                .intercept(MethodDelegation.to(Hi.class))
                .make();
        outputClazz(dynamicType3.getBytes(), 3);
        // 加载类
        Class<?> clazz = dynamicType3.load(Boot.class.getClassLoader()).getLoaded();
        // 反射调用
        clazz.getMethod("main", String[].class).invoke(clazz.newInstance(), (Object) new String[1]);
    }

    private static void outputClazz(byte[] bytes, Integer num) {
        FileOutputStream out = null;
        try {
            String pathName = Boot.class.getResource("/").getPath() + "ByteBuddyHelloWorld_" + num + ".class";
            out = new FileOutputStream(pathName);
            System.out.println("类输出路径:" + pathName);
            out.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


}

上面会调用对应拦截器进行处理,下面举例一个拦截器

上面调用了intercept(MethodDelegation.to(MonitorDemo.class)),会执行如下intercept()拦截方法

public class MonitorDemo {

    /**

     * @RuntimeType 定义运行时的目标方法
     *
     * @SuperCall     用于调用父类版本的方法
     * @AllArguments  绑定所有参数的数组
     * @Argument     绑定单个参数. 0表示第一个参数
     * @This          当前被拦截的、动态生成的那个对象
     * @Super         当前被拦截的、动态生成的那个对象的父类对象
     * @Origin        具备多种用法,如下:
     *                - Method:被调用的原始方法
     *                - Constructor:被调用的原始构造器
     *                - Class:当前动态创建的类
     *                - MethodHandle MethodType String 动态类的toString()的返回值 int 动态方法的修饰符
     * @DefaultCall   调用默认方法而非super的方法
     * @Super         注入父类型对象,可以是接口,从而调用它的任何方法
     * @Empty         注入参数的类型的默认值
     * @StubValue     注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0
     * @FieldValue    注入被拦截对象的一个字段的值
     * @Morph         类似于@SuperCall,但是允许指定调用参数
     */
    @RuntimeType
    public static Object intercept(@SuperCall Callable<?> callable,
                                   @AllArguments Object[] args,
                                   @Argument(0) Object uid,
                                   @This Object thisObj,
                                   @Super Object parentObj,
                                   @Origin Method method)
            throws Exception {
        long start = System.currentTimeMillis();
        Object resObj = null;
        try {
            // @SuperCall 调用原方法
            resObj = callable.call();
            return resObj;
        } finally {
            System.out.println("===============MonitorDemo======================");
            System.out.println("@AllArguments获取所有参数:" + JSON.toJSON(args));
            System.out.println("@Argument(0)获取第一个参数结果:" + uid);
            System.out.println("@This当前对象spanId结果:" + ((BizMethod) thisObj).getSpanId());
            System.out.println("@Super父类对象结果:" + parentObj.hashCode());
            System.out.println("@Origin方法名称:" + method.getName());
            System.out.println("@Origin入参个数:" + method.getParameterCount());
            System.out.println("@Origin入参类型:" + method.getParameterTypes()[0].getTypeName() + "、" + method.getParameterTypes()[1].getTypeName());
            System.out.println("@Origin出参类型:" + method.getReturnType().getName());
            System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("===============MonitorDemo======================");
        }
    }

 

 

 

 

举报

相关推荐

Git基本用法

Axios基本用法

Vue基本用法

Leaflet基本用法

VIM基本用法

promise基本用法

0 条评论