bytebuddy入门

简介

        Byte Buddy 是一个代码生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还允许创建任意类,并且不限于实现用于创建运行时代理的接口

核心api调用

创建类

@Testpublic void createTest() throws IOException {DynamicType.Unloaded<Object> upLoaded = new ByteBuddy().subclass(Object.class).make();upLoaded.saveIn(new File(path));}

以上即是一个bytebuddy的简单实例代码。创建了一个未被JVM加载的类的字节码,并且保存在了文件中。

除了将生成的字节码保存在文件中,还可以注入到jar包文件中

@Testpublic void createTest() throws IOException {DynamicType.Unloaded<Object> upLoaded = new ByteBuddy().subclass(Object.class).make();upLoaded.inject(new File("D:/test.jar"));}

也可以通过命名策略改变生成的类名称或者直接指定生成的类名称,以下是一段丰富的api调用示例:

@Testpublic void createTest() throws IOException {DynamicType.Unloaded<Object> unloaded = new ByteBuddy().with(new NamingStrategy.Suffixing("lyc")).subclass(Object.class)
//                .name("com.yc.learn.bytebuddy.learn.UserServiceImpl").make();unloaded.saveIn(new File(path));}

以上示例通过指定生成类的命名策略创建类,生成的类字节码反编译之后如下:

如果直接通过name方法指定类名,生成的类名如下:

加载新创建的类:

@Testpublic void loadCreateTest() throws InstantiationException, IllegalAccessException {DynamicType.Unloaded<Object> unloaded = new ByteBuddy().subclass(Object.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl").make();DynamicType.Loaded<Object> load = unloaded.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);Class<?> loaded = load.getLoaded();Object objectInstance = loaded.newInstance();}

通过调用load方法,并且指定类加载器和加载策略(ClassLoadingStrategy.Default.WRAPPER)加载新创建的类

常用的类加载策略如下:

  • WRAPPER 策略:创建一个新的 ClassLoader 来加载动态生成的类型。
  • CHILD_FIRST 策略:创建一个子类优先加载的 ClassLoader,即打破了双亲委派模型。
  • INJECTION 策略:使用反射, 将动态生成的类型直接注入到当前 ClassLoader 中。

创建类的方式

bytebuddy提供了三种创建类的方式,分别是:

subClass :生成对应类的子类

rebase:变基,效果是保留袁方法并重命名为xx$$original$xxxx

redefine:重定义类,如果有冲突的方法、字段,原方法和字段不在保留

以下示例说明:

用到的基础类代码如下:

bytebuddy的api调用如下:

subClass生成的类如下

rebase生成的类如下(字节码信息)

redefine生成的类如下

生成新方法

@Test
public void createMethod() throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {DynamicType.Unloaded<UserService> unloaded = new ByteBuddy().subclass(UserService.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl_bytebuddy").defineMethod("init", String.class, Modifier.PUBLIC|Modifier.STATIC).withParameters(String.class).intercept(FixedValue.value("test defineMethod value")).make();DynamicType.Loaded<UserService> load = unloaded.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER);Class<? extends UserService> loaded = load.getLoaded();Method method = loaded.getMethod("init", String.class);Object value = method.invoke(null, "张三");System.out.println(value);}

生成新的字段

@Test
public void createNewField() throws IOException {DynamicType.Unloaded<Object> unloaded = new ByteBuddy().subclass(Object.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl_bytebuddy").defineField("age", int.class, Modifier.PRIVATE).implement(UserAgeInterface.class).intercept(FieldAccessor.ofField("age")).make();unloaded.saveIn(new File(path));
}

自定义类的加载路径

    @Testpublic void jarLocatorTest() throws IOException {// 加载指定路径下的jar包ClassFileLocator jarLocator = ClassFileLocator.ForJarFile.of(new File("xxxx\\hutool-all-5.8.12.jar"));// 系统类加载,如果不加载会找不到jdk本身的类ClassFileLocator classFileLocator = ClassFileLocator.ForClassLoader.ofSystemLoader();ClassFileLocator.Compound compound = new ClassFileLocator.Compound(jarLocator, classFileLocator);TypePool pool = TypePool.Default.of(compound);// 此处调用不会触发类的加载TypeDescription typeDes = pool.describe("cn.hutool.core.util.StrUtil").resolve();DynamicType.Unloaded<Object> unloaded = new ByteBuddy().redefine(typeDes, compound).name("cn.hutool.core.util.YcUtil").method(named("isBlankIfStr")).intercept(FixedValue.value(false)).make();unloaded.saveIn(new File(path));}

方法拦截

MethodDelegation#to

在上面的代码中,已经有涉及到方法拦截的代码,其中使用了内置的方法拦截器FixedValue,在实际的使用中一般使用MethodDelegation(方法委托)完成方法拦截逻辑,实例代码如下:

    @Testpublic void methodDelegation() throws InstantiationException, IllegalAccessException {DynamicType.Unloaded<UserService> dynamicType = new ByteBuddy().subclass(UserService.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl")// 拦截构造方法
//                .constructor(any()).method(named("getUserName").and(returns(String.class).and(takesArguments(String.class)))).intercept(MethodDelegation.to(new CommonDelegation())).make();DynamicType.Loaded<UserService> load = dynamicType.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER);Class<? extends UserService> loaded = load.getLoaded();UserService userServiceLoaded = loaded.newInstance();String loadedValue = userServiceLoaded.getUserName("zhangsan");System.out.println(loadedValue);}

其中GetUserNameDelegation的代码如下:

在使用MethodDelegation.to作为方法委托时,可以传Clazz对象也可以传委托实例。

其实使用硬编码的方法委托也有诸多限制:

如果是传递Clazz对象做为MethodDelegation.to的参数,需要满足:

该类中有static方法的方法签名(返回值,参数列表)和被代理方法的方法签名(返回值、参数列表)相同

2 如果是传递实例对象作为MethodDelegation.to的参数,需要满足:

1 该类中有实例方法的方法签名(返回值,参数列表)和被代理方法的方法签名(返回值、参数列表)相同

所以其实在实际使用中,最常用的方式是使用注解,将相关的动作交由bytebuddy完成,示例代码如下:

@Testpublic void methodDelegation() throws InstantiationException, IllegalAccessException {DynamicType.Unloaded<UserService> dynamicType = new ByteBuddy().subclass(UserService.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl").method(named("getUserName").and(returns(String.class).and(takesArguments(String.class)))).intercept(MethodDelegation.to(new CommonDelegation())).make();DynamicType.Loaded<UserService> load = dynamicType.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER);Class<? extends UserService> loaded = load.getLoaded();UserService userServiceLoaded = loaded.newInstance();String loadedValue = userServiceLoaded.getUserName("zhangsan");System.out.println(loadedValue);}
package com.yc.learn.bytebuddy.learn;import cn.hutool.core.util.ArrayUtil;
import net.bytebuddy.implementation.bind.annotation.*;import java.lang.reflect.Method;
import java.util.concurrent.Callable;public class CommonDelegation {@RuntimeTypepublic Object intercept(@This Object target, @Origin Method targetMethod,@Super Object superObj, @AllArguments Object[] args,@SuperCall Callable<?> zuper) throws Exception {System.out.println("调用的对象类名称是:" + target.getClass().getName());System.out.println("调用的方法名称是:" + targetMethod.getName());System.out.println("调用对象的父类名称是:" + superObj.getClass().getName());System.out.println("调用的方法参数列表是:" + ArrayUtil.toString(args));return zuper.call();}
}

执行调用结果如下:

对于以上注解释义如下:

@RuntimeType

        该注解表示bytebuddy框架对委托代理方法不做类型校验

@This

        注解表示注入的是被委托方法的调用者,也就是调用被委托方法的实例对象(动态生成),注意:如果拦截的是静态方法,不能使用该注解,否则不能正常拦截

@Origin

        表示注入被拦截的源方法,如果拦截的是字段,该注解应该标注到 Field 类型参数

@Super

        注入当前被拦截的、动态生成的那个对象的父类对象,如果拦截的是静态方法,不能使用该注解,否则不能正常拦截


@AllArguments

        被委托方法的参数列表,参数必须是数组类型,并分配一个包含所有源方法参数的数组

@Argument

        绑定源方法的单个参数

@SuperCall

        这个注解比较特殊,我们要在 intercept() 方法中用于调用目标对象方法(源方法),需要通过这种方式注入。

        一般标注的对象是Callable对象,通过调用call方法返回源方法的返回值,也可以用于标注Runnable对象,此时,源方法的返回值将被丢弃

如果在创建类的时候使用了redefine创建新类,拦截不成功,因为redifine是丢弃了源方法

该种注入方式,不能通过修改被@AllArguments标注的参数数组的值修改源方法的入参

@Morph

        如果想要修改源方法的入参,使用@Morph注解,使用步骤如下:

1 自定义接口

2 在拦截器处通过@Morph标记自定义的接口

3 绑定自定义接口

示例如下:

1  自定义接口

package com.yc.learn.bytebuddy.learn;public interface MorphCall {Object call(Object[] args);
}

2 在拦截器处通过@Morph标记自定义的接口

package com.yc.learn.bytebuddy.learn;import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;import java.util.Objects;public class MorphDelegation {@RuntimeTypepublic Object MorphDelegate(@AllArguments Object[] args, @Morph MorphCall morphCall) {if (Objects.nonNull(args) && args.length > 0) {args[0] = "地球";}return morphCall.call(args);}
}

3 绑定自定义接口

    @Testpublic void methodDelegationParameterBinding() throws InstantiationException, IllegalAccessException {DynamicType.Unloaded<UserService> dynamicType = new ByteBuddy().subclass(UserService.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl").method(named("getUserName").and(returns(String.class).and(takesArguments(String.class)))).intercept(MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(MorphCall.class)).to(new MorphDelegation())).make();DynamicType.Loaded<UserService> load = dynamicType.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER);Class<? extends UserService> loaded = load.getLoaded();UserService userServiceLoaded = loaded.newInstance();String loadedValue = userServiceLoaded.getUserName("zhangsan");System.out.println(loadedValue);}

执行以上配置之后打印结果如下:

动态修改入参生效

Advice

方法拦截除了使用如上的MethodDelegation@to实现方法拦截,还可以使用Advice#to完成,示例代码如下:

@Test
public void adviceTest() throws InstantiationException, IllegalAccessException {DynamicType.Unloaded<UserService> dynamicType = new ByteBuddy().subclass(UserService.class).name("com.yc.learn.bytebuddy.learn.UserServiceImpl").method(named("getUserName").and(returns(String.class).and(takesArguments(String.class)))).intercept(Advice.to(AdviceCommonDelegation.class)).make();DynamicType.Loaded<UserService> load = dynamicType.load(getClass().getClassLoader(),ClassLoadingStrategy.Default.WRAPPER);Class<? extends UserService> loaded = load.getLoaded();UserService userServiceLoaded = loaded.newInstance();String loadedValue = userServiceLoaded.getUserName("zhangsan");System.out.println(loadedValue);}
package com.yc.learn.bytebuddy.learn;import net.bytebuddy.asm.Advice;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;public class AdviceCommonDelegation {@Advice.OnMethodEnterpublic static void onMethodEnter(@Advice.Argument(value = 0,typing = DYNAMIC,readOnly = false) Object param){System.out.println("进入方法之前,参数是:" + param.toString());}@Advice.OnMethodExitpublic static void onMethodExit(@Advice.Argument(value = 0,typing = DYNAMIC,readOnly = false) Object param,@Advice.Return Object result){System.out.println("执行方法之后,参数是:" + param.toString() + " 返回数据是:" + result.toString());}
}

其中使用@Advice.OnMethodEnter和使用@Advice.OnMethodExit标注的方法必须是static修饰的,可以结合其他的一些注解完成方法拦截操作

@Advice.OnMethodEnter

        标记被该注解标注的方法在源方法执行前执行,被该注解标记的方法必须是static修饰

@Advice.OnMethodExit

        标记被该注解标注的方法在源方法执行后执行,被该注解标记的方法必须是static修饰

@Advice.Argument

        用于绑定方法调用的某个参数(具体通过value绑定参数列表的参数下标位置),同时可以通过typeing属性和readonly属性设置改变方法入参的值

@Advice.AllArguments

        用于绑定方法调用的参数数组,同时可以通过typeing属性和readonly属性设置改变方法入参的值

@Advice.Return

        该注解只能用于被@Advice.onMethodExit标注的方法的参数中,表示方法调用的返回数据

更丰富的注解使用及释义,查看源码net.bytebuddy.asm.Advice和官方文档Byte Buddy - runtime code generation for the Java virtual machine

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/329900.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【信息安全】

信息安全 1.防火墙技术 防火墙是建立在内外网络边界上的过滤封锁机制。 补充内容 防火墙工作层次越低&#xff0c;工作效率越高&#xff0c;安全性越低DMZ的作用是保存一些公共服务器&#xff08;Web服务器、Eamil服务器&#xff09;防火墙的安全性从高到底&#xff1a;内网…

kafka Kerberos集群环境部署验证

背景 公司需要对kafka环境进行安全验证,目前考虑到的方案有Kerberos和SSL和SASL_SSL,最终考虑到安全和功能的丰富度,我们最终选择了SASL_SSL方案。处于知识积累的角度,记录一下kafka keberos安装部署的步骤。 机器规划 目前测试环境公搭建了三台kafka主机服务,现在将详细…

微软:最新ChatGPT-4o模型,可在 Azure OpenAI上使用

北京时间5月14日凌晨&#xff0c;OpenAI 一场不到 30 分钟的发布会&#xff0c;正式发布了 GPT-4o&#xff0c;视频语音交互丝滑到吓人&#xff0c;还即将免费可用&#xff01; GPT-4o&#xff0c;其中的「o」代表「omni」&#xff08;即全面、全能的意思&#xff09;&#xff…

FPGA搭积木之按键消抖(改进版)

目录 1.前言 2.回顾之前的设计 3.基于读者思路的设计 4.ModelSim仿真 1.前言 昨天分享的关于FPGA对机械按键消抖的设计&#xff0c;有读者指出了其中的不足&#xff0c;并给出了他的思路。今天就读者的设计思路&#xff0c;来再做一个按键消抖模块。这个程序大概是大学的时…

《Python编程从入门到实践》day34

# 昨日知识点回顾 json文件提取数据、绘制图表渐变色显示 # 今日知识点学习 第17章 17.1 使用Web API Web API作为网站的一部分&#xff0c;用于与使用具体URL请求特定信息的程序交互&#xff0c;这种请求称为API调用。 17.1.1 Git 和 GitHub Git&#xff1a;分布式版本控制系…

每日5题Day4 - LeetCode 16 - 20

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;16. 最接近的三数之和 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int threeSumClosest(int[] nums, int target) {//别用dp了&#xff0c;…

docker-compose Install homer

homer前言 一个非常简单的静态主页,为您的服务器保持您的服务在手,从一个简单的yaml配置文件。 前提要求 安装 docker docker-compose 参考创建一键安装homer 脚本 homer安装位置/homerhomer 脚本位置/homer/assetshomer logo 图标/home/assets/iconshomer 端口80homer 颜色…

MySQL5个查询

# 总查询 EXPLAIN SELECT * FROM city; # 范围查询 EXPLAIN SELECT * from city where ID>5 and ID<20; #主键查询 EXPLAIN SELECT * from city where ID5; # 索引查询 EXPLAIN SELECT * from city where CountryCodeNLD; # 普通索引 EXPLAIn SELECT * from cit…

工业交换机的好处有哪些?

工业交换机是现代工业网络中不可或缺的重要组成部分&#xff0c;它扮演着连接和管理各种网络设备的关键角色。工业交换机的优点不言而喻&#xff0c;首先是其稳定可靠的性能&#xff0c;能够支撑工业环境下的高负荷工作。无论是在恶劣的温度、湿度或电磁干扰的环境下&#xff0…

springboot2.x3.x的A项目(作为sdk)集成到启动B项目调用2

一 概述 1.1 说明 本博客记录的案例&#xff0c;逻辑是&#xff1a; 项目A读取配置文件&#xff0c;并在service类的方法进行打印输出。项目A作为sdk被项目B进行依赖&#xff0c; 在项目B启动后&#xff0c;进行调用&#xff0c;并且在B进行参数的配置&#xff0c;能够覆盖…

stm32常用编写C语言基础知识,条件编译,结构体等

位操作 宏定义#define 带参数的宏定义 条件编译 下面是头文件中常见的编译语句&#xff0c;其中_LED_H可以认为是一个编译段的名字。 下面代码表示满足某个条件&#xff0c;进行包含头文件的编译&#xff0c;SYSTEM_SUPPORT_OS可能是条件&#xff0c;当非0时&#xff0c;可以…

路由引入实验(华为)

思科设备参考&#xff1a;路由引入实验&#xff08;思科&#xff09; 技术简介 路由引入技术在网络通信中起着重要的作用&#xff0c;能够实现不同路由协议之间的路由传递&#xff0c;并在路由引入时部署路由控制&#xff0c;实现路径或策略的控制 实验目的 不同的路由协议之…

工大智信智能听诊器:开启个人健康管理的全新模式

工大智信智能听诊器&#xff1a;开启个人健康管理的全新模式 在快节奏的现代生活中&#xff0c;健康管理已成为人们关注的焦点。工大智信智能听诊器&#xff0c;作为一款创新的医疗设备&#xff0c;不仅提供高级数据管理功能&#xff0c;而且成为了个人健康管理的得力助手。 这…

【FreeRTOS移植到STM32F103C8T6超详细教程-->>>基于标准库】

移植教程 FreeRTOS简介FreeRTOS 介绍FreeRTOS优点 FreeRTOS移植FreeRTOS 下载FreeRTOS目录结构移植开始Keil5打开工程修改FreeRTOSConfig.h文件修改stm32f10x_it.c 测试FreeRTOS闪烁第一颗小灯 FreeRTOS简介 FreeRTOS 介绍 FreeRTOS 是市场领先的面向微控制器和小型微处理器的…

Flutter Text导致A RenderFlex overflowed by xxx pixels on the right.

使用Row用来展示两个Text的时候页面出现如下异常,提示"A RenderFlex overflowed by xxx pixels on the right." The following assertion was thrown during layout: A RenderFlex overflowed by 4.8 pixels on the right.The relevant error-causing widget was:…

如何排查hpet导致的CPU高负载——《OceanBase诊断系列》之十

1. 前言 我在OceanBase问答社区协助用户排查了一个CPU占用率过高的问题&#xff0c;帖子原文是&#xff1a; 《刚刚新安装的OceanBase集群&#xff0c;没有任何数据&#xff0c;CPU占用非常高&#xff0c;这正常吗&#xff1f;》。从这个场景出发&#xff0c;来分享相关的诊断…

【Python自动化测试】:Unittest单元测试与HTMLTestRunner自动生成测试用例的好帮手

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;unittest编写测试用例&#x1f680;unittest测…

智慧医疗时代:探索互联网医院开发的新篇章

在智慧医疗时代&#xff0c;互联网医院开发正引领着医疗服务的创新浪潮。通过将先进的技术与医疗服务相结合&#xff0c;互联网医院为患者和医生提供了全新的互动方式&#xff0c;极大地提升了医疗服务的便捷性和效率。本文将深入探讨互联网医院的开发&#xff0c;介绍其技术实…

巧秒用AI写作工具做影视解说文案,效率高!

在自媒体内容输出的快节奏当下&#xff0c;影视解说已经成为一种受欢迎的内容形式。然而&#xff0c;创作高质量的影视解说文案往往需要花费大量的时间和精力。随着人工智能技术的不断发展&#xff0c;AI写作工具为我们提供了一种全新的、高效的解决方案。 AI写作工具利用先进的…

电商API接口接入电商平台实现品牌价格、销量、评论数据监控

电商数据接口API在现代电商运营中扮演着至关重要的角色。它不仅能够实现品牌价格、销量、评论数据的监控&#xff0c;还能帮助商家优化销售策略&#xff0c;提升业务效率。下面将详细探讨如何通过接入电商平台的API接口来实现这些功能&#xff1a; 选择适合的电商平台 主流平台…