一个更简单的字节码增强框架,谁看了案例都会使用!( 二 )


2. 字节码创建类和方法接下来的例子会通过一点点的增加代码梳理,不断的把一个方法完整的创建出来 。
2.1 定义输出字节码方法为了可以更加清晰的看到每一步对字节码编程后,所创建出来的方法样子(clazz),我们需要输出字节码生成 clazz 。在Byte buddy中默认提供了一个 dynamicType.saveIn() 方法,我们暂时先不使用,而是通过字节码进行保存 。
private static void outputClazz(byte[] bytes) {FileOutputStream out = null;try {String pathName = ApiTest.class.getResource("/").getPath() + "ByteBuddyHelloWorld.class";out = new FileOutputStream(new File(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();}}}

  • 这个方法我们在之前也用到过,主要就是一个 Java 基础的内容,输出字节码到文件中 。
2.2 创建类信息DynamicType.Unloaded<?> dynamicType = new ByteBuddy().subclass(Object.class).name("org.itstack.demo.bytebuddy.HelloWorld").make();// 输出类字节码outputClazz(dynamicType.getBytes());
  • 创建类和定义类名,如果不写类名会自动生成要给类名 。
此时class文件:
public class HelloWorld {public HelloWorld() {}}2.3 创建main方法DynamicType.Unloaded<?> dynamicType = new ByteBuddy().subclass(Object.class).name("org.itstack.demo.bytebuddy.HelloWorld").defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC).withParameter(String[].class, "args").intercept(FixedValue.value("Hello World!")).make();与上面相比新增的代码片段;
  • defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC),定义方法;名称、返回类型、属性public static
  • withParameter(String[].class, "args"),定义参数;参数类型、参数名称
  • intercept(FixedValue.value("Hello World!")),拦截设置返回值,但此时还能满足我们的要求 。
这里有一个知识点,Modifier.PUBLIC + Modifier.STATIC,这是一个是二进制相加,每一个类型都在二进制中占有一位 。例如 1 2 4 8 ... 对应的二进制占位 1111 。所以可以执行相加运算,并又能保留原有单元的属性 。
此时class文件:
public class HelloWorld {public static void main(String[] args) {String var10000 = "Hello World!";}public HelloWorld() {}}此时基本已经可以看到我们平常编写的 Hello World 影子了,但还能输出结果 。
2.4 委托函数使用为了能让我们使用字节码编程创建的方法去输出一段 Hello World ,那么这里需要使用到委托 。
DynamicType.Unloaded<?> dynamicType = new ByteBuddy().subclass(Object.class).name("org.itstack.demo.bytebuddy.HelloWorld").defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC).withParameter(String[].class, "args").intercept(MethodDelegation.to(Hi.class)).make();
  • 整体来看变化并不大,只有 intercept(MethodDelegation.to(Hi.class)),使用了一段委托函数,真正去执行输出的是另外的函数方法 。 MethodDelegation,需要是 public 类被委托的方法与需要与原方法有着一样的入参、出参、方法名,否则不能映射上
此时class文件:
public class HelloWorld {public static void main(String[] args) {Hi.main(var0);}public HelloWorld() {}}
  • 那么此时就可以输出我们需要的内容了,Hi.main 是定义出来的委托函数 。也就是一个 HelloWorld
五、测试结果为了可以让整个方法运行起来,我们需要添加字节码加载和反射调用的代码块,如下;
// 加载类Class<?> clazz = dynamicType.load(GenerateClazzMethod.class.getClassLoader()).getLoaded();// 反射调用clazz.getMethod("main", String[].class).invoke(clazz.newInstance(), (Object) new String[1]);运行结果
类输出路径:/User/xiaofuge/itstack/git/github.com/itstack-demo-bytecode/itstack-demo-bytecode-2-01/target/test-classes/ByteBuddyHelloWorld.classhelloWorldProcess finished with exit code 0效果图
一个更简单的字节码增强框架,谁看了案例都会使用!

文章插图


推荐阅读