├── cfg └── gems │ └── .gitkeep ├── docs ├── assets │ ├── images │ │ ├── alipay.png │ │ ├── wxpay.jpg │ │ ├── wxpay.png │ │ ├── wx-jikerizhi.png │ │ └── java-instrumentation.jpeg │ ├── styles │ │ └── custom.css │ └── tags │ │ └── analytics.htm ├── questions.adoc ├── index.adoc ├── _attributes.adoc ├── preface.adoc ├── annotations.adoc ├── custom-instrumentation.adoc ├── preliminary.adoc ├── fields-and-methods.adoc └── creating-a-class.adoc ├── src └── main │ └── java │ ├── com │ └── diguage │ │ └── cafe │ │ ├── divecode │ │ ├── MyNewMain.java │ │ ├── JavassistMain.java │ │ ├── AttachTest.java │ │ ├── AgentTest.java │ │ ├── MyMain.java │ │ ├── AttachMain.java │ │ ├── JavassistTest.java │ │ ├── AgentMain.java │ │ └── AsmTest.java │ │ └── jiadao │ │ ├── Bar.java │ │ ├── Foo.java │ │ ├── Test6.java │ │ └── ByteBuddyTest.java │ └── Example.java ├── README.adoc ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── mvnw.cmd ├── mvnw ├── LICENSE └── pom.xml /cfg/gems/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/images/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diguage/byte-buddy-tutorial/HEAD/docs/assets/images/alipay.png -------------------------------------------------------------------------------- /docs/assets/images/wxpay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diguage/byte-buddy-tutorial/HEAD/docs/assets/images/wxpay.jpg -------------------------------------------------------------------------------- /docs/assets/images/wxpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diguage/byte-buddy-tutorial/HEAD/docs/assets/images/wxpay.png -------------------------------------------------------------------------------- /docs/assets/images/wx-jikerizhi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diguage/byte-buddy-tutorial/HEAD/docs/assets/images/wx-jikerizhi.png -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/MyNewMain.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | public class MyNewMain { 4 | } 5 | -------------------------------------------------------------------------------- /docs/assets/images/java-instrumentation.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diguage/byte-buddy-tutorial/HEAD/docs/assets/images/java-instrumentation.jpeg -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/jiadao/Bar.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.jiadao; 2 | 3 | public class Bar { 4 | String m() { 5 | return "bar"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/jiadao/Foo.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.jiadao; 2 | 3 | public class Foo { 4 | String m() { 5 | return "foo"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/JavassistMain.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | public class JavassistMain { 4 | 5 | public int bar(int i1, int i2) { 6 | return 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/assets/styles/custom.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-decoration: none; 3 | } 4 | 5 | p > code, strong > code { 6 | color: #d14 !important; 7 | background-color: #f5f5f5 !important; 8 | border: 1px solid #e1e1e8; 9 | white-space: nowrap; 10 | border-radius: 3px; 11 | } -------------------------------------------------------------------------------- /docs/questions.adoc: -------------------------------------------------------------------------------- 1 | = 问题 2 | 3 | image::assets/images/java-instrumentation.jpeg[{image_attr}] 4 | 5 | . `ClassLoader` 的工作原理 6 | . Byte Buddy 的使用 7 | . 字节码热替换 8 | . 改造 OpenTelemetry 的代码,支持获取 SGM 的 TraceId 和 SpanId 9 | . 可以 OpenTelemetry 和阿尔萨斯的代码,直接抄他们的代码 10 | . 研究 OpenTelemetry 11 | . 怎么判断两段代码是否重复?可选方案: 12 | .. 抽象语法书 13 | .. 字节码 -------------------------------------------------------------------------------- /docs/assets/tags/analytics.htm: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/AttachTest.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class AttachTest { 6 | public static void main(String[] args) throws InterruptedException { 7 | while (true) { 8 | System.out.println(foo()); 9 | TimeUnit.SECONDS.sleep(5); 10 | } 11 | } 12 | 13 | private static int foo() { 14 | return 100; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/AgentTest.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | public class AgentTest { 4 | public static void main(String[] args) { 5 | System.out.println("start to execute my app."); 6 | new AgentTest().foo(); 7 | } 8 | 9 | public void foo() { 10 | bar1(); 11 | bar2(); 12 | } 13 | 14 | private void bar2() { 15 | } 16 | 17 | private void bar1() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Byte Buddy 教程 ^Alpha^ 2 | D瓜哥 3 | v0.1.0, 2017-07-24 4 | include::_attributes.adoc[] 5 | 6 | ++++ 7 | include::assets/tags/analytics.htm[] 8 | ++++ 9 | 10 | :sectnums!: 11 | 12 | // // 感谢 13 | // include::dedication.adoc[] 14 | 15 | // 前言 16 | include::preface.adoc[leveloffset=+1] 17 | 18 | include::preliminary.adoc[leveloffset=+1] 19 | 20 | include::creating-a-class.adoc[leveloffset=+1] 21 | 22 | include::fields-and-methods.adoc[leveloffset=+1] 23 | 24 | include::annotations.adoc[leveloffset=+1] 25 | 26 | include::custom-instrumentation.adoc[leveloffset=+1] 27 | 28 | 29 | 30 | [#license] 31 | [appendix] 32 | == 版权声明 33 | 34 | ++++ 35 |
36 | include::../LICENSE[]
37 | 
38 | ++++ 39 | -------------------------------------------------------------------------------- /docs/_attributes.adoc: -------------------------------------------------------------------------------- 1 | :doctype: book 2 | :icons: font 3 | :iconfont-cdn: https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css 4 | :source-highlighter: rouge 5 | :source-language: java 6 | :rouge-style: github 7 | :linkcss: 8 | :docinfo: 9 | :toc: left 10 | :toc-title: 目录 11 | :toclevels: 4 12 | :sectnumlevels: 4 13 | :preface-title: 前言 14 | :chapter-label: 章 15 | :appendix-caption: 附录 16 | :listing-caption: 代码 17 | :figure-caption: 图 18 | :version-label: V 19 | :pdf-page-size: A4 20 | :keywords: Byte Buddy Tutorial, Byte Buddy 教程, 字节码, Java, JVM, Java Virtual Machine, Java 虚拟机 21 | :description: Byte Buddy 教程 -- Byte Buddy 是一个字节码生成与维护的库,主要用于在 Java 应用运行时生成和修改 Java 类,并且不需要编译器来辅助。 22 | :last-update-label: 最后更新时间 23 | :homepage: http://www.diguage.com/ 24 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/MyMain.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | public class MyMain { 4 | public int a = 0; 5 | public int b = 1; 6 | public int abc = 2; 7 | public String s; 8 | 9 | public void test1() { 10 | 11 | } 12 | 13 | public void test2() { 14 | 15 | } 16 | 17 | public void xyz() { 18 | 19 | } 20 | 21 | public int foo(int a) { 22 | System.out.println("hello foo"); 23 | return a; // 修改为 return a + 100; 24 | } 25 | 26 | public void divzero() { 27 | System.out.println("step 1"); 28 | int a = 1 / 0; 29 | System.out.println("step 2"); 30 | } 31 | 32 | public static void main(String[] args) { 33 | MyMain object = new MyMain(); 34 | System.out.println(object.foo(1)); 35 | object.divzero(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = “Byte Buddy Tutorial” 中文翻译:Byte Buddy 教程 2 | 3 | 最近看到好多人关注这个 Repo。有问题,欢迎关注公众号(**公众号的微信号是: jikerizhi**。),给我留言私信。 4 | 5 | _本文档是 D瓜哥 在学习 http://bytebuddy.net/#/tutorial[Byte Buddy Tutorial] 时随手翻译的。水平有限,难免失误,欢迎随时发生 PR。谢谢_ 6 | 7 | == 友情支持 8 | 9 | 如果您觉得这个笔记对您有所帮助,看在 D瓜哥 码字的辛苦上,请友情支持一下。屌丝逆袭,赢取白富美,走向人生巅峰就靠这个了,😜 😜 10 | 11 | [cols="2*^",frame=none] 12 | |=== 13 | | image:docs/assets/images/alipay.png[title="支付宝", alt="支付宝", width="90%"] 14 | | image:docs/assets/images/wxpay.jpg[title="微信", alt="微信", width="90%"] 15 | |=== 16 | 17 | 有些打赏的朋友希望可以加个好友,欢迎关注D瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。 18 | 19 | image::docs/assets/images/wx-jikerizhi.png[align="center",width=98%] 20 | 21 | TIP: **公众号的微信号是: `jikerizhi`**。__因为众所周知的原因,有时图片加载不出来。如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。__ 22 | 23 | == 官网及版本库 24 | 25 | 本文档的版本库使用 Git 管理。另外,单独发布阅读版。 26 | 27 | “地瓜哥”博客网:: https://www.diguage.com/ 。D瓜哥的个人博客。欢迎光临,不过,内容很杂乱,请见谅。不见谅,你来打我啊,😂😂 28 | 本文档官网:: https://notes.diguage.com/byte-buddy-tutorial/ 。为了方便阅读,这里展示了处理好的文档。阅读请点击这个网址。 29 | 本文档版本库:: https://github.com/diguage/byte-buddy-tutorial 。反正也没啥保密可言,版本库分分钟给出。😜 30 | -------------------------------------------------------------------------------- /docs/preface.adoc: -------------------------------------------------------------------------------- 1 | [#preface] 2 | [preface] 3 | = 前言 4 | 5 | *本文档是 D瓜哥 在学习 http://bytebuddy.net/#/tutorial[Byte Buddy Tutorial] 时随手翻译的。水平有限,难免失误,欢迎随时发生 PR。谢谢!* 6 | 7 | == 友情支持 8 | 9 | 如果您觉得这个笔记对您有所帮助,看在 D瓜哥 码字的辛苦上,请友情支持一下。屌丝逆袭,赢取白富美,走向人生巅峰就靠这个了,😜 😜 10 | 11 | [cols="2*^",frame=none] 12 | |=== 13 | | image:assets/images/alipay.png[title="支付宝", alt="支付宝", width="85%"] 14 | | image:assets/images/wxpay.jpg[title="微信", alt="微信", width="85%"] 15 | |=== 16 | 17 | 有些打赏的朋友希望可以加个好友,欢迎关注D瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。 18 | 19 | image::assets/images/wx-jikerizhi.png[align="center",width=98%] 20 | 21 | TIP: **公众号的微信号是: `jikerizhi`**。__因为众所周知的原因,有时图片加载不出来。如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。__ 22 | 23 | 24 | == 官网及版本库 25 | 26 | 本文档的版本库使用 Git 管理。另外,单独发布阅读版。 27 | 28 | “地瓜哥”博客网:: https://www.diguage.com/ 。D瓜哥的个人博客。欢迎光临,不过,内容很杂乱,请见谅。不见谅,你来打我啊,😂😂 29 | 本文档官网:: https://notes.diguage.com/byte-buddy-tutorial/ 。为了方便阅读,这里展示了处理好的文档。阅读请点击这个网址。 30 | 本文档版本库:: https://github.com/diguage/byte-buddy-tutorial 。反正也没啥保密可言,版本库分分钟给出。😜 31 | 32 | NOTE: 文本中斜体字表示翻译很不满意,还需要再三斟酌来探究更好的翻译。希望感兴趣的小伙伴在阅读的时候多多留意。更希望发送 PR 过来。谢谢!! 33 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/AttachMain.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | import com.sun.tools.attach.AgentInitializationException; 4 | import com.sun.tools.attach.AgentLoadException; 5 | import com.sun.tools.attach.AttachNotSupportedException; 6 | import com.sun.tools.attach.VirtualMachine; 7 | 8 | import java.io.IOException; 9 | import java.net.URISyntaxException; 10 | import java.net.URL; 11 | 12 | public class AttachMain { 13 | public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, URISyntaxException { 14 | // URL url = Thread.currentThread().getContextClassLoader().getResource("com/diguage/cafe/divecode/AgentMain.class"); 15 | // String basePath = url.toURI().getPath().replace("classes/" + "com/diguage/cafe/divecode/AgentMain.class", ""); 16 | String basePath = "/Users/lijun695/Documents/byte-buddy-tutorial/target/"; 17 | VirtualMachine vm = VirtualMachine.attach(args[0]); 18 | try { 19 | vm.loadAgent(basePath + "jiadao.jar"); 20 | } finally { 21 | vm.detach(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/annotations.adoc: -------------------------------------------------------------------------------- 1 | [#annotations] 2 | = 注解 3 | 4 | 5 | [{java_source_attr}] 6 | ---- 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @interface RuntimeDefinition { } 9 | 10 | class RuntimeDefinitionImpl implements RuntimeDefinition { 11 | @Override 12 | public Class annotationType() { 13 | return RuntimeDefinition.class; 14 | } 15 | } 16 | 17 | new ByteBuddy() 18 | .subclass(Object.class) 19 | .annotateType(new RuntimeDefinitionImpl()) 20 | .make(); 21 | ---- 22 | 23 | [{java_source_attr}] 24 | ---- 25 | new ByteBuddy() 26 | .subclass(Object.class) 27 | .annotateType(new RuntimeDefinitionImpl()) 28 | .method(named("toString")) 29 | .intercept(SuperMethodCall.INSTANCE) 30 | .annotateMethod(new RuntimeDefinitionImpl()) 31 | .defineField("foo", Object.class) 32 | .annotateField(new RuntimeDefinitionImpl()) 33 | ---- 34 | 35 | [#type-annotations] 36 | == 类型注解 37 | 38 | Byte Buddy 暴露并编写了类型注解,它们被引入到 Java 8,并成为其中的一部分。 39 | 40 | [#attribute-appenders] 41 | == 属性附加器 42 | 43 | 44 | [{java_source_attr}] 45 | ---- 46 | class AnnotatedMethod { 47 | @SomeAnnotation 48 | void bar() { } 49 | } 50 | new ByteBuddy() 51 | .subclass(AnnotatedMethod.class) 52 | .method(named("bar")) 53 | .intercept(StubMethod.INSTANCE) 54 | .attribute(MethodAttributeAppender.ForInstrumentedMethod.INSTANCE) 55 | ---- 56 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/jiadao/Test6.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.jiadao; 2 | 3 | import net.bytebuddy.ByteBuddy; 4 | import net.bytebuddy.dynamic.ClassFileLocator; 5 | import net.bytebuddy.dynamic.DynamicType; 6 | import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; 7 | import net.bytebuddy.pool.TypePool; 8 | 9 | import java.lang.reflect.Field; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class Test6 { 14 | public static void main(String[] args) throws NoSuchFieldException { 15 | TypePool typePool = TypePool.Default.ofSystemLoader(); 16 | String className = "com.diguage.cafe.jiadao.Test6$UnloadedBar"; 17 | DynamicType.Loaded qux = new ByteBuddy() 18 | .redefine(typePool.describe(className).resolve(), // do not use 'Bar.class' 19 | ClassFileLocator.ForClassLoader.ofSystemLoader()) 20 | .defineField("qux", String.class) // we learn more about defining fields later 21 | .make() 22 | .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER); 23 | Class quxClass = qux.getLoaded(); 24 | Field qux1 = quxClass.getDeclaredField("qux"); 25 | assertThat(qux1).isNotNull(); 26 | } 27 | 28 | public static class UnloadedBar { 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/JavassistTest.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | import javassist.*; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.net.URL; 8 | 9 | import static javassist.CtClass.intType; 10 | import static javassist.Modifier.PUBLIC; 11 | 12 | public class JavassistTest { 13 | @Test 14 | public void test1() throws CannotCompileException, IOException { 15 | ClassPool cp = ClassPool.getDefault(); 16 | CtClass ct = cp.makeClass("com.diguage.cafe.divecode.JavassistMain"); 17 | ct.writeFile(getPath()); 18 | } 19 | 20 | @Test 21 | public void test2() throws NotFoundException, CannotCompileException, IOException { 22 | ClassPool cp = ClassPool.getDefault(); 23 | cp.insertClassPath(getMainPath()); 24 | CtClass ct = cp.get(JavassistMain.class.getName()); 25 | CtMethod method = new CtMethod(intType, "foo", new CtClass[]{intType, intType}, ct); 26 | method.setModifiers(PUBLIC); 27 | ct.addMethod(method); 28 | 29 | 30 | ct.writeFile(getPath()); 31 | } 32 | 33 | @Test 34 | public void test3() throws NotFoundException, CannotCompileException, IOException { 35 | ClassPool cp = ClassPool.getDefault(); 36 | cp.insertClassPath(getMainPath()); 37 | CtClass ct = cp.get(JavassistMain.class.getName()); 38 | CtMethod method = ct.getMethod("bar", "(II)I"); 39 | method.setBody("return $1 * $2;"); 40 | ct.writeFile(getPath()); 41 | } 42 | 43 | private static String getMainPath() { 44 | String testClassPath = "com/diguage/cafe/divecode/JavassistMain.class"; 45 | URL url = Thread.currentThread().getContextClassLoader().getResource(testClassPath); 46 | String path = url.getPath(); 47 | return path; 48 | } 49 | 50 | 51 | private static String getPath() { 52 | String testClassPath = "com/diguage/cafe/divecode/JavassistTest.class"; 53 | URL url = Thread.currentThread().getContextClassLoader().getResource(testClassPath); 54 | String path = url.getPath(); 55 | String classPath = path.replace(testClassPath, ""); 56 | return classPath; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/Example.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 字节码示例代码 3 | * 4 | * @author D瓜哥 · https://www.diguage.com 5 | */ 6 | public class Example { 7 | 8 | // { 9 | // long l = 1L + 4L; 10 | // } 11 | 12 | private static Object object = new Object(); 13 | 14 | // public Example() { 15 | // float f = 0.0F + 2.0F; 16 | // } 17 | // 18 | // public Example(int i) { 19 | // double d = 0.0 + 1.0; 20 | // } 21 | 22 | // { 23 | // int i = 1 << 17; 24 | // } 25 | 26 | static { 27 | double d = 0.0 + 1.0; 28 | } 29 | 30 | static { 31 | double d = 3.0 + 5.0; 32 | } 33 | // 34 | 35 | // 36 | // public static void main(String[] args) { 37 | // System.out.println("start to new Example"); 38 | // Example example = new Example(); 39 | // System.out.println("finish creating Example"); 40 | // } 41 | 42 | // static { 43 | // float f = 0.0F + 5.0F; 44 | // } 45 | // 46 | 47 | // 48 | // { 49 | // long l = 2 + 5; 50 | // } 51 | // 52 | 53 | // 54 | 55 | // /** 56 | // * 测试 boolean 型示例 57 | // */ 58 | // public boolean testBoolean() { 59 | // return true; 60 | // } 61 | // 62 | // /** 63 | // * 测试 byte 型示例 64 | // */ 65 | // public byte testByte() { 66 | // // '0' == 0x30 == 48 67 | // return '0'; 68 | // } 69 | // 70 | // /** 71 | // * 测试 char 型示例 72 | // */ 73 | // public char testChar() { 74 | // // '0' == 0x30 == 48 75 | // return '0'; 76 | // } 77 | // 78 | // /** 79 | // * 测试 short 型示例 80 | // */ 81 | // public short testShort() { 82 | // // '0' == 0x30 == 48 83 | // return 32767; 84 | // } 85 | 86 | // /** 87 | // * 操作码 ldc2_w 示例 88 | // */ 89 | // public long testLdc2_w() { 90 | // // 替换为long 和 double 类型除上述内容提到的之外的值, 91 | // // 编译查看结果 92 | // return 2L; 93 | // } 94 | 95 | // /** 96 | // * 操作码 ldc 示例 97 | // */ 98 | // public float testLdc() { 99 | // // 替换为除上述内容提到的 int 和 float 之外的值,或者字符串 100 | // // 编译查看结果 101 | // return 1.1F; 102 | // } 103 | 104 | // /** 105 | // * 操作码 sipush 示例 106 | // */ 107 | // public int testSipush() { 108 | // // 替换为 -32768 ~ -129 和 128 ~ 32767 之间的整数, 109 | // // 编译查看结果 110 | // return -32768; 111 | // } 112 | 113 | // /** 114 | // * 操作码 bipush 示例 115 | // */ 116 | // public int testBipush() { 117 | // // 替换为 -128 ~ -2 和 6 ~ 127 之间的整数, 118 | // // 编译查看结果 119 | // return 6; 120 | // } 121 | 122 | // /** 123 | // * 操作码 dconst_ 示例 124 | // */ 125 | // public double testDconst() { 126 | // // 替换为 1.0,编译查看结果 127 | // return 0.0; 128 | // } 129 | 130 | // /** 131 | // * 操作码 fconst_ 示例 132 | // */ 133 | // public float testFconst() { 134 | // // 依次替换为 1.0F 和 2.0F,编译查看结果 135 | // return 0.0F; 136 | // } 137 | 138 | // /** 139 | // * 操作码 lconst_ 示例 140 | // */ 141 | // public void testLconst() { 142 | // long l0 = 0L; 143 | // long l1 = 1L; 144 | // } 145 | 146 | // /** 147 | // * 操作码 iconst_ 示例 148 | // */ 149 | // public void testIconst() { 150 | // int im1 = -1; 151 | // int i0 = 0; 152 | // int i1 = 1; 153 | // int i2 = 2; 154 | // int i3 = 3; 155 | // int i4 = 4; 156 | // int i5 = 5; 157 | // } 158 | 159 | // /** 160 | // * 操作码 aconst_null 示例 161 | // */ 162 | // public Object testAconst() { 163 | // return null; 164 | // } 165 | } 166 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 12 | .mvn/wrapper/maven-wrapper.jar 13 | 14 | # Compiled class file 15 | *.class 16 | 17 | # Log file 18 | *.log 19 | 20 | # BlueJ files 21 | *.ctxt 22 | 23 | # Mobile Tools for Java (J2ME) 24 | .mtj.tmp/ 25 | 26 | # Package Files # 27 | *.jar 28 | *.war 29 | *.nar 30 | *.ear 31 | *.zip 32 | *.tar.gz 33 | *.rar 34 | 35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 36 | hs_err_pid* 37 | 38 | .metadata 39 | #bin/ 40 | tmp/ 41 | *.tmp 42 | *.bak 43 | *.swp 44 | *~.nib 45 | local.properties 46 | .settings/ 47 | .loadpath 48 | .recommenders 49 | 50 | # External tool builders 51 | .externalToolBuilders/ 52 | 53 | # Locally stored "Eclipse launch configurations" 54 | *.launch 55 | 56 | # PyDev specific (Python IDE for Eclipse) 57 | *.pydevproject 58 | 59 | # CDT-specific (C/C++ Development Tooling) 60 | .cproject 61 | 62 | # CDT- autotools 63 | .autotools 64 | 65 | # Java annotation processor (APT) 66 | .factorypath 67 | 68 | # PDT-specific (PHP Development Tools) 69 | .buildpath 70 | 71 | # sbteclipse plugin 72 | .target 73 | 74 | # Tern plugin 75 | .tern-project 76 | 77 | # TeXlipse plugin 78 | .texlipse 79 | 80 | # STS (Spring Tool Suite) 81 | .springBeans 82 | 83 | # Code Recommenders 84 | .recommenders/ 85 | 86 | # Annotation Processing 87 | .apt_generated/ 88 | .apt_generated_test/ 89 | 90 | # Scala IDE specific (Scala & Java development for Eclipse) 91 | .cache-main 92 | .scala_dependencies 93 | .worksheet 94 | 95 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 96 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 97 | 98 | # User-specific stuff 99 | .idea/**/workspace.xml 100 | .idea/**/tasks.xml 101 | .idea/**/usage.statistics.xml 102 | .idea/**/dictionaries 103 | .idea/**/shelf 104 | 105 | # Generated files 106 | .idea/**/contentModel.xml 107 | 108 | # Sensitive or high-churn files 109 | .idea/**/dataSources/ 110 | .idea/**/dataSources.ids 111 | .idea/**/dataSources.local.xml 112 | .idea/**/sqlDataSources.xml 113 | .idea/**/dynamic.xml 114 | .idea/**/uiDesigner.xml 115 | .idea/**/dbnavigator.xml 116 | 117 | # Gradle 118 | .idea/**/gradle.xml 119 | .idea/**/libraries 120 | 121 | # Gradle and Maven with auto-import 122 | # When using Gradle or Maven with auto-import, you should exclude module files, 123 | # since they will be recreated, and may cause churn. Uncomment if using 124 | # auto-import. 125 | # .idea/artifacts 126 | # .idea/compiler.xml 127 | # .idea/modules.xml 128 | # .idea/*.iml 129 | # .idea/modules 130 | # *.iml 131 | # *.ipr 132 | 133 | # CMake 134 | cmake-build-*/ 135 | 136 | # Mongo Explorer plugin 137 | .idea/**/mongoSettings.xml 138 | 139 | # File-based project format 140 | *.iws 141 | 142 | # IntelliJ 143 | out/ 144 | 145 | # mpeltonen/sbt-idea plugin 146 | .idea_modules/ 147 | 148 | # JIRA plugin 149 | atlassian-ide-plugin.xml 150 | 151 | # Cursive Clojure plugin 152 | .idea/replstate.xml 153 | 154 | # Crashlytics plugin (for Android Studio and IntelliJ) 155 | com_crashlytics_export_strings.xml 156 | crashlytics.properties 157 | crashlytics-build.properties 158 | fabric.properties 159 | 160 | # Editor-based Rest Client 161 | .idea/httpRequests 162 | 163 | # Android studio 3.1+ serialized cache file 164 | .idea/caches/build_file_checksums.ser 165 | 166 | .idea/compiler.xml 167 | .idea/inspectionProfiles/ 168 | .idea/misc.xml 169 | .idea/modules.xml 170 | .idea/qaplug_profiles.xml 171 | .idea/vcs.xml 172 | .idea 173 | *.iml 174 | 175 | /docs/*.html 176 | /docs/.asciidoctor 177 | /docs/*.svg 178 | /docs/*.png 179 | 180 | cfg/gems 181 | 182 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | 2 | name: GitHub Pages 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@v3 13 | with: 14 | submodules: recursive 15 | fetch-depth: 0 16 | 17 | - name: Set up JDK 17 ☕️ 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '17' 21 | distribution: 'corretto' 22 | 23 | - name: Install Graphviz 🐰 24 | run: | 25 | sudo apt update -y -m 26 | sudo apt install -y graphviz 27 | 28 | - name: Build 🔧 29 | run: mvn clean package 30 | 31 | - name: Add Reward Qrcode 💰 32 | run: | 33 | cd target/docs/multipage/ 34 | find . -name "*.html" | grep -v "preface.html" | xargs -I {} sed -i "s|
|

友情支持

如果您觉得这个笔记对您有所帮助,看在D瓜哥码这么多字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

\"支付宝\"

\"微信\"

有些打赏的朋友希望可以加个好友,欢迎关注D 瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。

\"wx

公众号的微信号是: jikerizhi因为众所周知的原因,有时图片加载不出来。 如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。
|" {} 35 | find . -name "*.html" | grep -v "index.html" | xargs -I {} sed -i 's|||' {} 36 | 37 | - name: Setup Node.js 🕸 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: '16' 41 | 42 | - name: Compress Style 🍭 43 | run: | 44 | npm install cssnano-cli --location=global 45 | cd target/docs/multipage/assets/styles 46 | echo -e '\na{text-decoration:none;}p>code,strong>code{color: #d14 !important;background-color: #f5f5f5 !important;border: 1px solid #e1e1e8;white-space: nowrap;border-radius: 3px;}' >> asciidoctor.css 47 | for f in `ls *.css`; 48 | do 49 | fn="${f%.*}.min.css"; 50 | cssnano $f $fn; 51 | rm -rf $f; 52 | mv $fn $f 53 | done 54 | 55 | - name: Rsync Deploy 🏹 56 | uses: burnett01/rsync-deployments@5.2 57 | with: 58 | switches: -avzr --delete 59 | path: target/docs/multipage/ 60 | remote_path: ${{ secrets.DEPLOY_PATH }} 61 | remote_host: ${{ secrets.DEPLOY_HOST }} 62 | remote_port: ${{ secrets.DEPLOY_PORT }} 63 | remote_user: ${{ secrets.DEPLOY_USER }} 64 | remote_key: ${{ secrets.DEPLOY_KEY }} 65 | 66 | - name: Change Files Mod 🔐 67 | uses: appleboy/ssh-action@master 68 | with: 69 | host: ${{ secrets.DEPLOY_HOST }} 70 | port: ${{ secrets.DEPLOY_PORT }} 71 | username: ${{ secrets.DEPLOY_USER }} 72 | key: ${{ secrets.DEPLOY_KEY }} 73 | script: | 74 | cd ${{ secrets.DEPLOY_PATH }} 75 | sudo chmod -R 777 * 76 | 77 | - name: Deploy 🚀 78 | uses: JamesIves/github-pages-deploy-action@v4 79 | with: 80 | # The branch the action should deploy to. 81 | branch: gh-pages 82 | # The folder the action should deploy. 83 | folder: target/docs/multipage/ 84 | single-commit: true -------------------------------------------------------------------------------- /docs/custom-instrumentation.adoc: -------------------------------------------------------------------------------- 1 | [#custom-instrumentation] 2 | = 定制化仪表 3 | 4 | [{java_source_attr}] 5 | ---- 6 | LDC 10 // stack contains 10 7 | LDC 50 // stack contains 10, 50 8 | IADD // stack contains 60 9 | IRETURN // stack is empty 10 | ---- 11 | 12 | 13 | [{java_source_attr}] 14 | ---- 15 | 12 00 01 16 | 12 00 02 17 | 60 18 | AC 19 | ---- 20 | 21 | [{java_source_attr}] 22 | ---- 23 | enum IntegerSum implements StackManipulation { 24 | 25 | INSTANCE; // singleton 26 | 27 | @Override 28 | public boolean isValid() { 29 | return true; 30 | } 31 | 32 | @Override 33 | public Size apply(MethodVisitor methodVisitor, 34 | Implementation.Context implementationContext) { 35 | methodVisitor.visitInsn(Opcodes.IADD); 36 | return new Size(-1, 0); 37 | } 38 | } 39 | ---- 40 | 41 | 42 | [{java_source_attr}] 43 | ---- 44 | enum SumMethod implements ByteCodeAppender { 45 | 46 | INSTANCE; // singleton 47 | 48 | @Override 49 | public Size apply(MethodVisitor methodVisitor, 50 | Implementation.Context implementationContext, 51 | MethodDescription instrumentedMethod) { 52 | if (!instrumentedMethod.getReturnType().asErasure().represents(int.class)) { 53 | throw new IllegalArgumentException(instrumentedMethod + " must return int"); 54 | } 55 | StackManipulation.Size operandStackSize = new StackManipulation.Compound( 56 | IntegerConstant.forValue(10), 57 | IntegerConstant.forValue(50), 58 | IntegerSum.INSTANCE, 59 | MethodReturn.INTEGER 60 | ).apply(methodVisitor, implementationContext); 61 | return new Size(operandStackSize.getMaximalSize(), 62 | instrumentedMethod.getStackSize()); 63 | } 64 | } 65 | ---- 66 | 67 | [{java_source_attr}] 68 | ---- 69 | enum SumImplementation implements Implementation { 70 | 71 | INSTANCE; // singleton 72 | 73 | @Override 74 | public InstrumentedType prepare(InstrumentedType instrumentedType) { 75 | return instrumentedType; 76 | } 77 | 78 | @Override 79 | public ByteCodeAppender appender(Target implementationTarget) { 80 | return SumMethod.INSTANCE; 81 | } 82 | } 83 | ---- 84 | 85 | 86 | [{java_source_attr}] 87 | ---- 88 | abstract class SumExample { 89 | public abstract int calculate(); 90 | } 91 | 92 | new ByteBuddy() 93 | .subclass(SumExample.class) 94 | .method(named("calculate")) 95 | .intercept(SumImplementation.INSTANCE) 96 | .make() 97 | ---- 98 | 99 | [#creating-a-custom-assigner] 100 | == 创建自定义分配器 101 | 102 | [{java_source_attr}] 103 | ---- 104 | enum ToStringAssigner implements Assigner { 105 | 106 | INSTANCE; // singleton 107 | 108 | @Override 109 | public StackManipulation assign(TypeDescription.Generic source, 110 | TypeDescription.Generic target, 111 | Assigner.Typing typing) { 112 | if (!source.isPrimitive() && target.represents(String.class)) { 113 | MethodDescription toStringMethod = new TypeDescription.ForLoadedType(Object.class) 114 | .getDeclaredMethods() 115 | .filter(named("toString")) 116 | .getOnly(); 117 | return MethodInvocation.invoke(toStringMethod).virtual(sourceType); 118 | } else { 119 | return StackManipulation.Illegal.INSTANCE; 120 | } 121 | } 122 | } 123 | ---- 124 | 125 | 126 | [{java_source_attr}] 127 | ---- 128 | new ByteBuddy() 129 | .subclass(Object.class) 130 | .method(named("toString")) 131 | .intercept(FixedValue.value(42) 132 | .withAssigner(new PrimitiveTypeAwareAssigner(ToStringAssigner.INSTANCE), 133 | Assigner.Typing.STATIC)) 134 | .make() 135 | ---- 136 | 137 | [#creating-a-custom-parameter-binder] 138 | == 创建自定义参数绑定器 139 | 140 | [{java_source_attr}] 141 | ---- 142 | @Retention(RetentionPolicy.RUNTIME) 143 | @interface StringValue { 144 | String value(); 145 | } 146 | ---- 147 | 148 | [{java_source_attr}] 149 | ---- 150 | enum StringValueBinder 151 | implements TargetMethodAnnotationDrivenBinder.ParameterBinder { 152 | 153 | INSTANCE; // singleton 154 | 155 | @Override 156 | public Class getHandledType() { 157 | return StringValue.class; 158 | } 159 | 160 | @Override 161 | public MethodDelegationBinder.ParameterBinding bind(AnnotationDescription.Loaded annotation, 162 | MethodDescription source, 163 | ParameterDescription target, 164 | Implementation.Target implementationTarget, 165 | Assigner assigner, 166 | Assigner.Typing typing) { 167 | if (!target.getType().asErasure().represents(String.class)) { 168 | throw new IllegalStateException(target + " makes illegal use of @StringValue"); 169 | } 170 | StackManipulation constant = new TextConstant(annotation.loadSilent().value()); 171 | return new MethodDelegationBinder.ParameterBinding.Anonymous(constant); 172 | } 173 | } 174 | ---- 175 | 176 | [{java_source_attr}] 177 | ---- 178 | class ToStringInterceptor { 179 | public static String makeString(@StringValue("Hello!") String value) { 180 | return value; 181 | } 182 | } 183 | 184 | new ByteBuddy() 185 | .subclass(Object.class) 186 | .method(named("toString")) 187 | .intercept(MethodDelegation.withDefaultConfiguration() 188 | .withBinders(StringValueBinder.INSTANCE) 189 | .to(ToStringInterceptor.class)) 190 | .make() 191 | ---- 192 | -------------------------------------------------------------------------------- /docs/preliminary.adoc: -------------------------------------------------------------------------------- 1 | [#preliminary] 2 | = 为什么需要在运行时生成代码? 3 | 4 | Java 语言带有一套比较严格的类型系统。Java 要求所有变量和对象都有一个确定的类型,并且任何赋值不兼容类型的尝试都会抛出错误。这些错误通常都会被编译器检查出来,或者极少情况下,在非法转换类型的时候由Java运行时抛出。如此严格的类型限制在大多数情况下是可取的,比如在编写业务应用时。在业务领域,通常可以以明确的方式去描述其中任何元素,各个元素都有自己明确的类型。通过这种方式,我们可以用 Java 构建具有非常强可读性和稳定性的应用,应用中的错误也非常贴近源码。除此之外,Java 严格的类型系统造就 Java 在企业编程中的普及。 5 | 6 | _然而,通过强制实施其严格的类型系统,Java 限制了自己在其他领域的应用范围。_ 比如,当编写一个供其他 Java 应用使用的通用库时,我们通常不能引用用户应用中定义的任何类型,因为当这个通用库被编译时,我们还不知道这些类型。为了调用用户未知代码的方法或者访问其属性,Java 类库提供了一套反射 API。使用这套反射 API,我们就可以反省未知类型,进而调用方法或者访问属性。不幸的是,这套反射 API 的用法有两个明显的缺点: 7 | 8 | * 相比硬编码的方法调用,使用 http://docs.oracle.com/javase/tutorial/reflect/index.html[反射 API 非常慢]:首先,需要执行一个相当昂贵的方法查找来获取描述特定方法的对象。同时,当一个方法被调用时,这要求 Java 虚拟机去运行本地代码,相比直接调用,这需要一个很长的运行时间。然而,现代 Java 虚拟机知道一个被称为“类型膨胀”的概念:基于 JNI 的方法调用会被动态生成的字节码给替换掉,而这些方法调用的字节码被注入到一个动态生成的类中。(即使 Java 虚拟机自身也使用代码生成!)毕竟,Java 的类型膨胀系统仍存在生成非常一般的代码的缺点,例如,仅能使用基本类型的装箱类型以至于性能缺陷不能完全解决。 9 | * 反射 API 能绕过类型安全检查:即使 Java 虚拟机支持通过反射进行代码调用,但反射 API 自身并不是类型安全的。当编写一个类库时,只要我们不需要把反射 API 暴露给库的用户,就不会有什么大问题。毕竟,当我们编译类库时,我们不知道用户代码,而且也不能校验我们的库与用户类型是否匹配。__有时,需要通过让一个库为我们自己调用我们自己的方法之一来向用户显示反射 API 示例。__这是使用反射 API 变得有问题的地方,因为 Java 编译器将具有所有信息来验证我们的程序的类型安全性。例如,当实现方法级安全库时,这个库的用户将希望这个库做到强制执行安全限制才能调用方法。为此,在用户传递过来方法所需的参数后,这个库将反射性地调用方法。这样,就没有编译时类型检查这些方法参数是否与方法的反射调用相匹配。方法调用依然会校验,只是被推迟到了运行时。这样做,我们就错失了 Java 编程语言的一大特性。 10 | 11 | 这正是运行时代码生成能帮助我们的地方。它允许我们模拟一些只有使用动态编程语言编程才有的特性,而且不丢失 Java 的静态类型检查。这样,我们就可以两全其美并且还可以提高运行时性能。为了更好地理解这个问题,让我们实现一个方法级安全库。 12 | 13 | [#writing-a-security-library] 14 | == 编写一个安全的库 15 | 16 | 业务应用程序可能会增长,有时很难在我们的应用程序中概述调用堆栈。当我们在应用程序中使用至关重要的方法时,而这些方法只能在特定条件下调用,这可能会变得有问题。 设想一下,实现重置功能的业务应用程序可以从应用程序的数据库中删除所有内容。 17 | 18 | [{java_source_attr}] 19 | ---- 20 | class Service { 21 | void deleteEverything() { 22 | // delete everything ... 23 | } 24 | } 25 | ---- 26 | 27 | 这样的复位操作当然只能由管理员执行,而不是由应用程序的普通用户执行。通过分析源代码,我们当然可以确保这将永远不会发生。但是,我们期望我们的应用能够在未来发展壮大。因此,我们希望实现更紧密的安全模型,其中通过对应用程序的当前用户的显式检查来保护方法调用。我们通常会使用一个安全框架来确保该方法从不被除管理员外的任何人调用。 28 | 29 | 为此,假设我们使用具有公共 API 如下的安全框架: 30 | 31 | [{java_source_attr}] 32 | ---- 33 | @Retention(RetentionPolicy.RUNTIME) 34 | @interface Secured { 35 | String user(); 36 | } 37 | 38 | class UserHolder { 39 | static String user; 40 | } 41 | 42 | interface Framework { 43 | T secure(Class type); 44 | } 45 | ---- 46 | 47 | 在此框架中,`Secured` 注解应用于标记只能由给定用户访问的方法。`UserHolder` 用于在全局范围内定义当前登录到应用程序的用户。`Framework` 接口允许通过调用给定类型的默认构造函数来创建安全实例。当然,这个框架过于简单,但是,从本质上来说,即使流行的安全框架,例如 http://projects.spring.io/spring-security/[Spring Security],也是这样实现的。这个安全框架的一个特点是我们过滤用户的类型。通过调用我们框架的接口,我们承诺返回给用户任何类型 `T` 的实例。幸亏这样,用户能够透明地他自己的类型进行交互,就像安全框架根本不存在一样。在测试环境中,用户甚至可以创建其类型的不安全实例,使用这些实例来代替安全实例。你会同意这真的很方便!已知这种框架使用 POJO,普通的旧 Java 对象进行交互,这是一种用于描述不侵入框架的术语,这些框架不会将自己的类型强加给用户。 48 | 49 | 现在,想象一下,假如我们知道传递给 `Framework` 的类型只能是 `T = Service`,而且 `deleteEverything` 方法用 `@Secured("ADMIN")` 注解。这样,我们可以通过简单的子类化来轻松实现这种特定类型的安全版本: 50 | 51 | [{java_source_attr}] 52 | ---- 53 | class SecuredService extends Service { 54 | @Override 55 | void deleteEverything() { 56 | if(UserHolder.user.equals("ADMIN")) { 57 | super.deleteEverything(); 58 | } else { 59 | throw new IllegalStateException("Not authorized"); 60 | } 61 | } 62 | } 63 | ---- 64 | 65 | 通过这个额外的类,我们可以实现框架如下: 66 | 67 | [{java_source_attr}] 68 | ---- 69 | class HardcodedFrameworkImpl implements Framework { 70 | @Override 71 | public T secure(Class type) { 72 | if(type == Service.class) { 73 | return (T) new SecuredService(); 74 | } else { 75 | throw new IllegalArgumentException("Unknown: " + type); 76 | } 77 | } 78 | } 79 | ---- 80 | 81 | 当然这个实现并没有太多的用处。通过标注 `secure` 方法签名,我们建议该方法可以为任何类型提供安全性,但实际上,一旦遇到其他事情,我们将抛出一个异常,然后是已知的 `Service`。此外,当编译库时,这将需要我们的安全库知道有关此特定 `Service` 类型的信息。显然,这不是实现框架的可行解决方案。那么我们如何解决这个问题呢?好吧,由于这是一个关于代码生成库的教程,你可能已经猜到答案:当通过调用 `secure` 方法, `Service` 类第一次被我们安全框架知道时,我们会在运行时后台地创建一个子类。通过使用代码生成,我们可以使用任何给定的类型,在运行时将其子类化,并覆盖我们要保护的方法。在我们的例子中,我们覆盖所有被 `@Secured` 注解标注的方法,并从注解的 `user` 属性中读取所需的用户。许多流行的 Java 框架都使用类似的方法实现。 82 | 83 | [#general-information] 84 | == 基本信息 85 | 在学习代码生成和 Byte Buddy 之前,请注意,应该谨慎使用代码生成。Java 类型对于 Java 虚拟机来说,是相当特别的东西,通常不能当做垃圾被回收。因此,不应该过度使用代码生成,而应该只在生成代码是解决问题的唯一出路时使用。但是,如果需要像上面的示例那样增强未知类型时,则代码生成很可能是你唯一的选择。用于安全性,事务管理,对象关系映射或类型模拟(mock)等框架是代码生成库的典型用户。 86 | 87 | 当然,Byte Buddy 不是 Java 虚拟机上第一个代码生成库。不过,我们认为 Byte Buddy 拥有其他框架没有的技巧。Byte Buddy 的总体目标是通过专注于其领域特定语言和注解的使用来声明式地进行工作。据我们所知,没有其他针对 Java 虚拟机的代码生成库以这种方式工作。不过,你可能希望看一下其他代码生成框架,以找出最适合你的套件。以下库在 Java 中很流行: 88 | 89 | http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html[*Java Proxy*]:: 90 | Java 类库自带了一个代理工具,它允许为实现了一系列接口的类创建代理。这个内置的代理供应商非常方便,但局限性也特别明显。 上面提到的安全框架就不能用这样的方式来实现的,因为我们想扩展是类而不是扩展接口。 91 | 92 | https://github.com/cglib/cglib[*cglib*]:: 93 | 代码生成库(注:这里指 `cglib`)诞生于 Java 初期,但不幸的是没有跟上 Java 平台的发展。然而,cglib 仍然是一个相当强大的库,但其积极的开发却变得相当模糊。鉴于此,其许多用户已经离开了 cglib。 94 | 95 | https://github.com/jboss-javassist/javassist[*Javassist*]:: 96 | 该库附带一个编译器,它使用包含 Java 源代码的字符串,这些字符串在应用程序的运行时被转换为 Java 字节码。这是非常有前途的,本质上是一个好主意,因为 Java 源代码显然是描述 Java 类的好方法。但是,Javassist 编译器在功能上比不了 javac 编译器,并且在动态组合字符串以实现比较复杂的逻辑时容易出错。此外,Javassist 还提供了一个类似于 Java 类库中的代理工具,但允许扩展类,并不限于接口。然而,Javassist 的代理工具的范围在其 API 和功能上仍然受到限制。 97 | 98 | 即使评估完这些框架,但我们相信 Byte Buddy 提供了功能和便利,可以减少徒劳地搜索。Byte Buddy 提供了一种具有表现力的领域特定语言,允许通过编写简单的 Java 代码和使用强大的类型为你自己的代码创建非常自定义的运行时类。与此同时,Byte Buddy 还具有非常开放的定制性,并不限制开箱即用的功能。如果需要,你甚至可以为任何实现的方法定义自定义字节码。但即使不知道什么字节代码是或它如何工作,你可以做很多,而不深入到框架。你有没有看看 http://bytebuddy.net/#/[`Hello World!` example?],使用 Byte Buddy 是如此简单。 99 | 100 | 当然,在选择代码生成库时,一个愉快的 API 不是唯一需要考虑的特性。对于许多应用程序,生成代码的运行时特性更有可能确定最佳选择。__而在生成的代码本身的运行时间之外,用于创建动态类的运行时也是一个问题。__声称“我们是最快的!”很容易,但是为库的速度提供有效的评比指标却很难。不过,我们希望提供这样的指标作为基本方向。但是,请注意,这些结果并不一定会转化为更具体的用例,此时你应该采用单独的指标。 101 | 102 | 在讨论我们的指标之前,让我们来看一下原始数据。下表显示了一个操作的平均运行时间,以纳秒为单位,标准偏差在括号内附加: 103 | 104 | [cols="h,>,>,>,>,>",options="header"] 105 | |=== 106 | | | 基线 | Byte Buddy | cglib | Javassist | Java proxy 107 | 108 | | 简单的类创建 | 0.003 (0.001) | 142.772 (1.390) | 515.174 (26.753) | 193.733 (4.430) | 70.712 (0.645) 109 | | 接口实现 | 0.004 (0.001) | 1'126.364 (10.328) | 960.527 (11.788) | 1'070.766 (59.865) | 1'060.766 (12.231) 110 | | 方法调用 | 0.002 (0.001) | 0.002 (0.001) | 0.003 (0.001) | 0.011 (0.001) | 0.008 (0.001) 111 | | 类型扩展 | 0.004 (0.001) | 885.983 (7.901) | 1'632.730 (52.737) | 683.478 (6.735) | 5'408.329 (52.437) 112 | | 父类方法调用 | 0.004 (0.001) | 0.004 (0.001) | 0.021 (0.001) | 0.025 (0.001) | 0.004 (0.001) 113 | |=== 114 | 115 | 与静态编译器类似,代码生成库在生成快速代码和快速生成代码之间面临着折衷。当在这些冲突的目标之间进行选择时,Byte Buddy 的主要侧重点在于以最少的运行时生成代码。通常,类型创建或操作不是任何程序中的常见步骤,并不会对任何长期运行的应用程序产生重大影响;特别是因为类加载或类构建(class instrumentation)是运行此类代码时最耗时且不可避免的步骤。 116 | 117 | WARNING: 按照这个逻辑,D瓜哥觉得应该选择“生成快速代码”,毕竟很少生成而且只生成一次,但是生成的代码却可能运行多次。不过,考虑到 Java 虚拟机的优化,选择“生成快速代码”是否是更好的选择呢? 118 | 119 | 上表中的第一个基准测试测量一个库在运行时子类化类,并且不实现或覆盖任何方法。这给我们一个库在代码生成时的一般开销的印象。在这个基准测试中,Java 代理执行得比其他库更好,这是因为存在着一种优化,假设总是扩展接口。Byte Buddy 还会检查类的泛型和注解类别,从而导致额外的运行时间。这个性能开销在创建类的其他基准中也是可见的。基准(2a)展示了运行时创建类,这个类实现了一个有 18 个方法的接口;(2b)显示为此类生成的方法的执行时间。类似地,(3a)显示了扩展类的基准,这个拥有相同的 18 种被实现的方法。 Byte Buddy 提供了两个基准测试,因为对于总是执行超类方法的拦截器来说,可能的优化是可能的。除了在类创建期间花费一段时间,Byte Buddy 创建类的执行时间通常达到基线,这意味着构建根本不会产生开销。应该注意的是,如果元数据处理被禁用,则在类创建期间,Byte Buddy 也会胜过任何其他代码生成库。由于代码生成的运行时间与程序的总运行时间相比微乎其微,所以这种性能优化是不可取的,因为它虽然获得了极少的性能,但却使库代码复杂很多。 120 | 121 | 最后,请注意,我们这些衡量 Java 代码性能的测试,都由 Java 虚拟机即时编译器优化过。如果你的代码只能偶尔执行,那么性能将会比上述表格指标略差。在这种情况下,你的代码并不是性能攸关的开始。这些性能测试代码与 Byte Buddy 一起发布,你可以在自己的计算机上运行这些指标,其中可能会根据你的机器的处理能力对上述数字进行涨跌。因此,不要绝对地解释上述数字,而是将它们视为不同库的对比方式。当进一步开发 Byte Buddy 时,我们希望监控这些指标,以避免在添加新功能时造成性能损失。 122 | 123 | 在下面的教程中,我们将会逐步说明 Byte Buddy 的功能。我们将从其更广泛的功能开始,这些功能最有可能被大多数用户使用。然后,我们将考虑越来越多的高级主题,并简要介绍 Java 字节码和类文件格式。即使你快速跳过这以后的材料,也不要灰心!你可以通过使用 Byte Buddy 的标准 API 来完成任何操作,而无需了解任何 JVM 规范。要了解标准 API,只需继续阅读。 124 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/jiadao/ByteBuddyTest.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.jiadao; 2 | 3 | import net.bytebuddy.ByteBuddy; 4 | import net.bytebuddy.dynamic.ClassFileLocator; 5 | import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; 6 | import net.bytebuddy.implementation.FixedValue; 7 | import net.bytebuddy.pool.TypePool; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | import java.util.Arrays; 13 | 14 | import static net.bytebuddy.matcher.ElementMatchers.*; 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | public class ByteBuddyTest { 18 | // 19 | // @Test 20 | // public void test1() { 21 | // DynamicType.Unloaded dynamicType = new ByteBuddy() 22 | // .subclass(Object.class) 23 | // .make(); 24 | // } 25 | // 26 | // @Test 27 | // public void test2() { 28 | // DynamicType.Unloaded dynamicType = new ByteBuddy() 29 | // .subclass(Object.class) 30 | // .name("com.diguage.cafe.jiadao.demo.Type") 31 | // .make(); 32 | // } 33 | // 34 | // @Test 35 | // public void test3() { 36 | // DynamicType.Unloaded dynamicType = new ByteBuddy() 37 | // .with(new NamingStrategy.AbstractBase() { 38 | // @Override 39 | // protected String name(TypeDescription superClass) { 40 | // return "com.diguage.cafe.buddy." + superClass.getTypeName(); 41 | // } 42 | // 43 | //// @Override 44 | //// public String subclass(TypeDescription.Generic superClass) { 45 | //// return "com.diguage.cafe.buddy." + superClass.getTypeName(); 46 | //// } 47 | // }) 48 | // .subclass(Object.class) 49 | // .make(); 50 | // } 51 | // 52 | // @Test 53 | // public void test4() { 54 | // Class type = new ByteBuddy() 55 | // .subclass(Object.class) 56 | // .make() 57 | // .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) 58 | // .getLoaded(); 59 | // } 60 | // 61 | // @Test 62 | // public void test5() { 63 | // ByteBuddyAgent.install(); 64 | // Foo foo = new Foo(); 65 | // new ByteBuddy() 66 | // .redefine(Bar.class) 67 | // .name(Foo.class.getName()) 68 | // .make() 69 | // .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 70 | // assertThat(foo.m()).isEqualTo("bar"); 71 | // } 72 | // 73 | public static class UnloadedBar { 74 | } 75 | 76 | @Test 77 | public void test6() throws NoSuchFieldException { 78 | 79 | TypePool typePool = TypePool.Default.ofSystemLoader(); 80 | 81 | String quxFieldName = "qux"; 82 | // TODO 示例里面显示是 Class,但是程序返回的不是 Class 83 | // TODO 加一句 .getLoaded() 就会返回 Class,但是还是不行 84 | Class type = new ByteBuddy() 85 | .redefine(typePool.describe("com.diguage.cafe.jiadao.ByteBuddyTest$UnloadedBar").resolve(), 86 | ClassFileLocator.ForClassLoader.ofSystemLoader()) 87 | .defineField(quxFieldName, String.class) 88 | .make() 89 | .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER) 90 | .getLoaded(); 91 | 92 | assertThat(type.getDeclaredField(quxFieldName)).isNotNull(); 93 | } 94 | // 95 | // /** 96 | // * TODO 还没有实验 97 | // */ 98 | // public static class ToStringAgent { 99 | // public static void premain(String argments, Instrumentation instrumentation) { 100 | // new AgentBuilder.Default() 101 | // .type(isAnnotatedWith(ToString.class)) 102 | // .transform(new AgentBuilder.Transformer() { 103 | // @Override 104 | // public DynamicType.Builder transform(DynamicType.Builder builder, 105 | // TypeDescription typeDescription, 106 | // ClassLoader classLoader, 107 | // JavaModule module) { 108 | // 109 | // return builder.method(named("toString")) 110 | // .intercept(FixedValue.value("transformed")); 111 | // } 112 | // }).installOn(instrumentation); 113 | // } 114 | // } 115 | // 116 | // 117 | // public static @interface ToString { 118 | // } 119 | // 120 | // @Test 121 | // public void test7() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 122 | // String className = "com.diguage.cafe.jiadao.demo.Type"; 123 | // String toString = new ByteBuddy() 124 | // .subclass(Object.class) 125 | // .name(className) 126 | // .make() 127 | // .load(getClass().getClassLoader()) 128 | // .getLoaded() 129 | // .getDeclaredConstructor() 130 | // .newInstance() 131 | // .toString(); 132 | // assertThat(toString).contains(className); 133 | // } 134 | // 135 | // @Test 136 | // public void test8() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 137 | // String toString = new ByteBuddy() 138 | // .subclass(Object.class) 139 | // .name("com.diguage.cafe.jiadao.demo.Type") 140 | // .method(named("toString")) 141 | // .intercept(FixedValue.value("Hello, https://www.diguage.com")) 142 | // .make() 143 | // .load(getClass().getClassLoader()) 144 | // .getLoaded() 145 | // .getDeclaredConstructor() 146 | // .newInstance() 147 | // .toString(); 148 | // assertThat(toString).contains("https://www.diguage.com"); 149 | // } 150 | 151 | /** 152 | * TODO 理解错了 takesArguments(0) 应该是长度为 0,不是第一个参数。 153 | */ 154 | @Test 155 | public void test9() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { 156 | String className = "com.diguage.cafe.jiadao.demo.Type"; 157 | String website = "https://www.diguage.com"; 158 | Object dynamicObject = new ByteBuddy() 159 | .subclass(Object.class) 160 | .name(className) 161 | .method(named("toString").and(returns(String.class)).and(takesArguments(0))) 162 | .intercept(FixedValue.argument(0)) 163 | .make() 164 | .load(getClass().getClassLoader()) 165 | .getLoaded() 166 | .getDeclaredConstructor() 167 | .newInstance(); 168 | Method[] methods = dynamicObject.getClass().getDeclaredMethods(); 169 | Method method = Arrays.stream(methods) 170 | .filter(m -> "toString".equals(m.getName()) && m.getParameterCount() > 0) 171 | .findAny().orElse(null); 172 | 173 | assertThat(method).isNotNull(); 174 | Object toString = method.invoke(dynamicObject, website); 175 | assertThat(toString).isInstanceOf(String.class); 176 | assertThat(toString).isEqualTo(website); 177 | } 178 | 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/AgentMain.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | 4 | import com.sun.tools.attach.AgentInitializationException; 5 | import com.sun.tools.attach.AgentLoadException; 6 | import com.sun.tools.attach.AttachNotSupportedException; 7 | import com.sun.tools.attach.VirtualMachine; 8 | import org.objectweb.asm.ClassReader; 9 | import org.objectweb.asm.ClassVisitor; 10 | import org.objectweb.asm.ClassWriter; 11 | import org.objectweb.asm.MethodVisitor; 12 | import org.objectweb.asm.commons.AdviceAdapter; 13 | 14 | import java.io.IOException; 15 | import java.lang.instrument.ClassFileTransformer; 16 | import java.lang.instrument.IllegalClassFormatException; 17 | import java.lang.instrument.Instrumentation; 18 | import java.lang.instrument.UnmodifiableClassException; 19 | import java.net.URISyntaxException; 20 | import java.net.URL; 21 | import java.security.ProtectionDomain; 22 | 23 | import static org.objectweb.asm.ClassReader.SKIP_DEBUG; 24 | import static org.objectweb.asm.ClassReader.SKIP_FRAMES; 25 | import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; 26 | import static org.objectweb.asm.Opcodes.ASM9; 27 | 28 | public class AgentMain { 29 | public static void premain(String agentArgs, Instrumentation instrumentation) { 30 | System.out.println("execute premain"); 31 | instrumentation.addTransformer(new AgentFileTransformer(), true); 32 | } 33 | 34 | public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException { 35 | System.out.println("execute agentmain"); 36 | instrumentation.addTransformer(new AttachClassFileTransformer(), true); 37 | Class[] classes = instrumentation.getAllLoadedClasses(); 38 | for (Class clazz : classes) { 39 | if (clazz.getName().contains("AttachTest")) { 40 | System.out.println("\nReloading class: " + clazz.getName()); 41 | instrumentation.retransformClasses(clazz); 42 | break; 43 | } 44 | } 45 | } 46 | 47 | public static class AgentMethodVisitor extends AdviceAdapter { 48 | // // TODO 如何给方法调用加前缀? 49 | // private ThreadLocal PREFIX = new ThreadLocal<>(); 50 | 51 | protected AgentMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) { 52 | super(api, methodVisitor, access, name, descriptor); 53 | } 54 | 55 | @Override 56 | protected void onMethodEnter() { 57 | // String prefix = PREFIX.get(); 58 | // prefix = Objects.isNull(prefix) ? "" : prefix; 59 | // PREFIX.set(" " + prefix); 60 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 61 | // mv.visitLdcInsn(prefix + ">>>> enter:" + getName()); 62 | mv.visitLdcInsn(">>>> enter:" + getName()); 63 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 64 | super.onMethodEnter(); 65 | } 66 | 67 | @Override 68 | protected void onMethodExit(int opcode) { 69 | // super.onMethodExit(opcode); 70 | // String prefix = PREFIX.get(); 71 | // if (Objects.isNull(prefix) || prefix.length() < 2) { 72 | // prefix = ""; 73 | // } else { 74 | // PREFIX.set(prefix.substring(0, prefix.length() - 2)); 75 | // } 76 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 77 | // mv.visitLdcInsn(prefix + "<<<< exit:" + getName()); 78 | mv.visitLdcInsn("<<<< exit:" + getName()); 79 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 80 | } 81 | } 82 | 83 | public static class AgentClassVisitor extends ClassVisitor { 84 | 85 | protected AgentClassVisitor(int api) { 86 | super(api); 87 | } 88 | 89 | protected AgentClassVisitor(int api, ClassVisitor classVisitor) { 90 | super(api, classVisitor); 91 | } 92 | 93 | @Override 94 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 95 | MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); 96 | if ("".equals(name)) { 97 | return mv; 98 | } 99 | return new AgentMethodVisitor(ASM9, mv, access, name, descriptor); 100 | } 101 | } 102 | 103 | public static class AgentFileTransformer implements ClassFileTransformer { 104 | @Override 105 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 106 | if (className.contains("diguage")) { 107 | System.out.println("start to load class: " + className); 108 | } 109 | if (!className.contains("AgentTest")) { 110 | return classfileBuffer; 111 | } 112 | System.out.println("start to transform class:" + className); 113 | ClassReader cr = new ClassReader(classfileBuffer); 114 | ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES); 115 | AgentClassVisitor cv = new AgentClassVisitor(ASM9, cw); 116 | cr.accept(cv, SKIP_FRAMES | SKIP_DEBUG); 117 | return cw.toByteArray(); 118 | } 119 | } 120 | 121 | public static class AttachMethodVisitor extends AdviceAdapter { 122 | protected AttachMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) { 123 | super(api, methodVisitor, access, name, descriptor); 124 | } 125 | 126 | @Override 127 | protected void onMethodEnter() { 128 | mv.visitIntInsn(BIPUSH, 50); 129 | mv.visitInsn(IRETURN); 130 | } 131 | } 132 | 133 | public static class AttachClassVisitor extends ClassVisitor { 134 | 135 | protected AttachClassVisitor(int api) { 136 | super(api); 137 | } 138 | 139 | protected AttachClassVisitor(int api, ClassVisitor classVisitor) { 140 | super(api, classVisitor); 141 | } 142 | 143 | @Override 144 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 145 | MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); 146 | if ("foo".equals(name)) { 147 | return new AttachMethodVisitor(ASM9, mv, access, name, descriptor); 148 | } 149 | return mv; 150 | } 151 | } 152 | 153 | public static class AttachClassFileTransformer implements ClassFileTransformer { 154 | @Override 155 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 156 | if (!className.contains("AttachTest")) { 157 | return classfileBuffer; 158 | } 159 | ClassReader cr = new ClassReader(classfileBuffer); 160 | ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES); 161 | AttachClassVisitor cv = new AttachClassVisitor(ASM9, cw); 162 | cr.accept(cv, SKIP_FRAMES | SKIP_DEBUG); 163 | return cw.toByteArray(); 164 | } 165 | } 166 | 167 | public static class AttachMain { 168 | public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, URISyntaxException { 169 | URL url = Thread.currentThread().getContextClassLoader().getResource("com/diguage/cafe/divecode/AgentMain.class"); 170 | String basePath = url.toURI().getPath().replace("classes/" + "com/diguage/cafe/divecode/AgentMain.class", ""); 171 | VirtualMachine vm = VirtualMachine.attach(args[0]); 172 | try { 173 | vm.loadAgent(basePath + "jiadao.jar"); 174 | } finally { 175 | vm.detach(); 176 | } 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /docs/fields-and-methods.adoc: -------------------------------------------------------------------------------- 1 | [#fields-and-methods] 2 | = 属性和方法 3 | 4 | [{java_source_attr}] 5 | ---- 6 | String toString = new ByteBuddy() 7 | .subclass(Object.class) 8 | .name("example.Type") 9 | .make() 10 | .load(getClass().getClassLoader()) 11 | .getLoaded() 12 | .newInstance() // Java reflection API 13 | .toString(); 14 | ---- 15 | 16 | [{java_source_attr}] 17 | ---- 18 | String toString = new ByteBuddy() 19 | .subclass(Object.class) 20 | .name("example.Type") 21 | .method(named("toString")).intercept(FixedValue.value("Hello World!")) 22 | .make() 23 | .load(getClass().getClassLoader()) 24 | .getLoaded() 25 | .newInstance() 26 | .toString(); 27 | ---- 28 | 29 | [{java_source_attr}] 30 | ---- 31 | named("toString").and(returns(String.class)).and(takesArguments(0)) 32 | ---- 33 | 34 | [{java_source_attr}] 35 | ---- 36 | class Foo { 37 | public String bar() { return null; } 38 | public String foo() { return null; } 39 | public String foo(Object o) { return null; } 40 | } 41 | 42 | Foo dynamicFoo = new ByteBuddy() 43 | .subclass(Foo.class) 44 | .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!")) 45 | .method(named("foo")).intercept(FixedValue.value("Two!")) 46 | .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!")) 47 | .make() 48 | .load(getClass().getClassLoader()) 49 | .getLoaded() 50 | .newInstance(); 51 | ---- 52 | 53 | [#a-closer-look-at-fixed-values] 54 | == 深入细看一个固定值 55 | 56 | [{java_source_attr}] 57 | ---- 58 | new ByteBuddy() 59 | .subclass(Foo.class) 60 | .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value(0)) 61 | .make(); 62 | ---- 63 | 64 | [#delegating-a-method-call] 65 | == 委托方法调用 66 | 67 | [{java_source_attr}] 68 | ---- 69 | class Source { 70 | public String hello(String name) { return null; } 71 | } 72 | 73 | class Target { 74 | public static String hello(String name) { 75 | return "Hello " + name + "!"; 76 | } 77 | } 78 | 79 | String helloWorld = new ByteBuddy() 80 | .subclass(Source.class) 81 | .method(named("hello")).intercept(MethodDelegation.to(Target.class)) 82 | .make() 83 | .load(getClass().getClassLoader()) 84 | .getLoaded() 85 | .newInstance() 86 | .hello("World"); 87 | ---- 88 | 89 | [{java_source_attr}] 90 | ---- 91 | class Target { 92 | public static String intercept(String name) { return "Hello " + name + "!"; } 93 | public static String intercept(int i) { return Integer.toString(i); } 94 | public static String intercept(Object o) { return o.toString(); } 95 | } 96 | ---- 97 | 98 | [{java_source_attr}] 99 | ---- 100 | void foo(Object o1, Object o2) 101 | ---- 102 | 103 | [{java_source_attr}] 104 | ---- 105 | void foo(@Argument(0) Object o1, @Argument(1) Object o2) 106 | ---- 107 | 108 | [{java_source_attr}] 109 | ---- 110 | class MemoryDatabase { 111 | public List load(String info) { 112 | return Arrays.asList(info + ": foo", info + ": bar"); 113 | } 114 | } 115 | 116 | class LoggerInterceptor { 117 | public static List log(@SuperCall Callable> zuper) 118 | throws Exception { 119 | System.out.println("Calling database"); 120 | try { 121 | return zuper.call(); 122 | } finally { 123 | System.out.println("Returned from database"); 124 | } 125 | } 126 | } 127 | 128 | MemoryDatabase loggingDatabase = new ByteBuddy() 129 | .subclass(MemoryDatabase.class) 130 | .method(named("load")).intercept(MethodDelegation.to(LoggerInterceptor.class)) 131 | .make() 132 | .load(getClass().getClassLoader()) 133 | .getLoaded() 134 | .newInstance(); 135 | ---- 136 | 137 | [{java_source_attr}] 138 | ---- 139 | class LoggingMemoryDatabase extends MemoryDatabase { 140 | 141 | private class LoadMethodSuperCall implements Callable { 142 | 143 | private final String info; 144 | private LoadMethodSuperCall(String info) { 145 | this.info = info; 146 | } 147 | 148 | @Override 149 | public Object call() throws Exception { 150 | return LoggingMemoryDatabase.super.load(info); 151 | } 152 | } 153 | 154 | @Override 155 | public List load(String info) { 156 | return LoggerInterceptor.log(new LoadMethodSuperCall(info)); 157 | } 158 | } 159 | ---- 160 | 161 | [{java_source_attr}] 162 | ---- 163 | class ChangingLoggerInterceptor { 164 | public static List log(String info, @Super MemoryDatabase zuper) { 165 | System.out.println("Calling database"); 166 | try { 167 | return zuper.load(info + " (logged access)"); 168 | } finally { 169 | System.out.println("Returned from database"); 170 | } 171 | } 172 | } 173 | ---- 174 | 175 | [{java_source_attr}] 176 | ---- 177 | class Loop { 178 | public String loop(String value) { return value; } 179 | public int loop(int value) { return value; } 180 | } 181 | ---- 182 | 183 | [{java_source_attr}] 184 | ---- 185 | class Interceptor { 186 | @RuntimeType 187 | public static Object intercept(@RuntimeType Object value) { 188 | System.out.println("Invoked method with: " + value); 189 | return value; 190 | } 191 | } 192 | ---- 193 | 194 | [{java_source_attr}] 195 | ---- 196 | interface Forwarder { 197 | T to(S target); 198 | } 199 | ---- 200 | 201 | [{java_source_attr}] 202 | ---- 203 | class ForwardingLoggerInterceptor { 204 | 205 | private final MemoryDatabase memoryDatabase; // constructor omitted 206 | 207 | public List log(@Pipe Forwarder, MemoryDatabase> pipe) { 208 | System.out.println("Calling database"); 209 | try { 210 | return pipe.to(memoryDatabase); 211 | } finally { 212 | System.out.println("Returned from database"); 213 | } 214 | } 215 | } 216 | 217 | MemoryDatabase loggingDatabase = new ByteBuddy() 218 | .subclass(MemoryDatabase.class) 219 | .method(named("load")).intercept(MethodDelegation.withDefaultConfiguration() 220 | .withBinders(Pipe.Binder.install(Forwarder.class))) 221 | .to(new ForwardingLoggerInterceptor(new MemoryDatabase())) 222 | .make() 223 | .load(getClass().getClassLoader()) 224 | .getLoaded() 225 | .newInstance(); 226 | ---- 227 | 228 | [#calling-a-super-method] 229 | == 调用超类方法 230 | 231 | [{java_source_attr}] 232 | ---- 233 | new ByteBuddy() 234 | .subclass(Object.class) 235 | .make() 236 | ---- 237 | 238 | [{java_source_attr}] 239 | ---- 240 | new ByteBuddy() 241 | .subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_TYPE) 242 | .make() 243 | ---- 244 | 245 | [#calling-a-default-method] 246 | == 调用默认方法 247 | 248 | [{java_source_attr}] 249 | ---- 250 | interface First { 251 | default String qux() { return "FOO"; } 252 | } 253 | 254 | interface Second { 255 | default String qux() { return "BAR"; } 256 | } 257 | ---- 258 | 259 | [{java_source_attr}] 260 | ---- 261 | new ByteBuddy(ClassFileVersion.JAVA_V8) 262 | .subclass(Object.class) 263 | .implement(First.class) 264 | .implement(Second.class) 265 | .method(named("qux")).intercept(DefaultMethodCall.prioritize(First.class)) 266 | .make() 267 | ---- 268 | 269 | [#calling-a-specific-method] 270 | == 调用特定方法 271 | 272 | [{java_source_attr}] 273 | ---- 274 | public class SampleClass { 275 | public SampleClass(int unusedValue) { 276 | super(); 277 | } 278 | } 279 | ---- 280 | 281 | [{java_source_attr}] 282 | ---- 283 | new ByteBuddy() 284 | .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS) 285 | .defineConstructor(Arrays.>asList(int.class), Visibility.PUBLIC) 286 | .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())) 287 | .make() 288 | ---- 289 | 290 | [#accessing-fields] 291 | == 访问属性 292 | 293 | [{java_source_attr}] 294 | ---- 295 | class UserType { 296 | public String doSomething() { return null; } 297 | } 298 | 299 | interface Interceptor { 300 | String doSomethingElse(); 301 | } 302 | 303 | interface InterceptionAccessor { 304 | Interceptor getInterceptor(); 305 | void setInterceptor(Interceptor interceptor); 306 | } 307 | 308 | interface InstanceCreator { 309 | Object makeInstance(); 310 | } 311 | ---- 312 | 313 | [{java_source_attr}] 314 | ---- 315 | Class dynamicUserType = new ByteBuddy() 316 | .subclass(UserType.class) 317 | .method(not(isDeclaredBy(Object.class))) 318 | .intercept(MethodDelegation.toField("interceptor")) 319 | .defineField("interceptor", Interceptor.class, Visibility.PRIVATE) 320 | .implement(InterceptionAccessor.class).intercept(FieldAccessor.ofBeanProperty()) 321 | .make() 322 | .load(getClass().getClassLoader()) 323 | .getLoaded(); 324 | ---- 325 | 326 | [{java_source_attr}] 327 | ---- 328 | InstanceCreator factory = new ByteBuddy() 329 | .subclass(InstanceCreator.class) 330 | .method(not(isDeclaredBy(Object.class))) 331 | .intercept(MethodDelegation.construct(dynamicUserType)) 332 | .make() 333 | .load(dynamicUserType.getClassLoader()) 334 | .getLoaded().newInstance(); 335 | ---- 336 | 337 | [{java_source_attr}] 338 | ---- 339 | class HelloWorldInterceptor implements Interceptor { 340 | @Override 341 | public String doSomethingElse() { 342 | return "Hello World!"; 343 | } 344 | } 345 | 346 | UserType userType = (UserType) factory.makeInstance(); 347 | ((InterceptionAccessor) userType).setInterceptor(new HelloWorldInterceptor()); 348 | ---- 349 | 350 | [#miscellaneous] 351 | == 杂项 352 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/java/com/diguage/cafe/divecode/AsmTest.java: -------------------------------------------------------------------------------- 1 | package com.diguage.cafe.divecode; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.junit.jupiter.api.Test; 5 | import org.objectweb.asm.*; 6 | import org.objectweb.asm.commons.AdviceAdapter; 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.objectweb.asm.tree.FieldNode; 9 | import org.objectweb.asm.tree.MethodNode; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.net.URI; 15 | import java.net.URISyntaxException; 16 | import java.util.Objects; 17 | 18 | import static org.objectweb.asm.ClassReader.SKIP_CODE; 19 | import static org.objectweb.asm.ClassReader.SKIP_DEBUG; 20 | import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; 21 | import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; 22 | import static org.objectweb.asm.Opcodes.*; 23 | 24 | public class AsmTest { 25 | @Test 26 | public void test1() throws IOException { 27 | ClassReader cr = new ClassReader(getAsStream()); 28 | ClassWriter cw = new ClassWriter(0); 29 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 30 | @Override 31 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 32 | System.out.println("field:" + name); 33 | return super.visitField(access, name, descriptor, signature, value); 34 | } 35 | 36 | @Override 37 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 38 | System.out.println("method:" + name); 39 | return super.visitMethod(access, name, descriptor, signature, exceptions); 40 | } 41 | }; 42 | cr.accept(cv, SKIP_CODE | SKIP_DEBUG); 43 | } 44 | 45 | private static InputStream getAsStream() { 46 | return ClassLoader.getSystemResourceAsStream("com/diguage/cafe/divecode/MyMain.class"); 47 | } 48 | 49 | @Test 50 | public void test2() throws IOException { 51 | ClassReader cr = new ClassReader(getAsStream()); 52 | ClassNode cn = new ClassNode(); 53 | cr.accept(cn, SKIP_DEBUG | SKIP_CODE); 54 | for (FieldNode field : cn.fields) { 55 | System.out.println("field:" + field.name); 56 | } 57 | for (MethodNode method : cn.methods) { 58 | System.out.println("method:" + method.name); 59 | } 60 | ClassWriter cw = new ClassWriter(0); 61 | cr.accept(cw, 0); 62 | byte[] modifiedBytes = cw.toByteArray(); 63 | } 64 | 65 | @Test 66 | public void test3() throws IOException { 67 | URI uri = getUri(); 68 | System.out.println(uri); 69 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 70 | ClassReader cr = new ClassReader(bytes); 71 | ClassWriter cw = new ClassWriter(0); 72 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 73 | @Override 74 | public void visitEnd() { 75 | super.visitEnd(); 76 | // TODO 查看生产的代码,感觉这里的 xyz 的类型声明不对。 77 | FieldVisitor fv = cv.visitField(ACC_PUBLIC, "xyz", "Ljava/lang/String", null, null); 78 | if (Objects.nonNull(fv)) { 79 | fv.visitEnd(); 80 | } 81 | } 82 | }; 83 | cr.accept(cv, SKIP_CODE | SKIP_DEBUG); 84 | byte[] modifiedBytes = cw.toByteArray(); 85 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 86 | } 87 | 88 | private static URI getUri() { 89 | try { 90 | return ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyNewMain.class").toURI(); 91 | } catch (URISyntaxException e) { 92 | throw new RuntimeException(e); 93 | } 94 | } 95 | 96 | @Test 97 | public void test4() throws IOException { 98 | URI uri = getUri(); 99 | System.out.println(uri); 100 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 101 | ClassReader cr = new ClassReader(bytes); 102 | ClassWriter cw = new ClassWriter(0); 103 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 104 | @Override 105 | public void visitEnd() { 106 | super.visitEnd(); 107 | // TODO 查看生产的代码,感觉这里的 xyz 的类型声明不对。 108 | MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "xyz", "(ILjava/lang/String;)V", null, null); 109 | if (Objects.nonNull(mv)) { 110 | mv.visitEnd(); 111 | } 112 | } 113 | }; 114 | cr.accept(cv, SKIP_CODE | SKIP_DEBUG); 115 | byte[] modifiedBytes = cw.toByteArray(); 116 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 117 | } 118 | 119 | @Test 120 | public void test5() throws URISyntaxException, IOException { 121 | URI uri = ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyMain.class").toURI(); 122 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 123 | ClassReader cr = new ClassReader(bytes); 124 | ClassWriter cw = new ClassWriter(0); 125 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 126 | @Override 127 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 128 | if ("abc".equals(name)) { 129 | return null; 130 | } 131 | return super.visitField(access, name, descriptor, signature, value); 132 | } 133 | 134 | @Override 135 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 136 | if ("xyz".equals(name)) { 137 | return null; 138 | } 139 | return super.visitMethod(access, name, descriptor, signature, exceptions); 140 | } 141 | }; 142 | cr.accept(cv, SKIP_CODE | SKIP_DEBUG); 143 | byte[] modifiedBytes = cw.toByteArray(); 144 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 145 | } 146 | 147 | /** 148 | * 本实例有错误!看下面一个示例的代码。 149 | */ 150 | @Test 151 | public void test6() throws URISyntaxException, IOException { 152 | URI uri = ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyMain.class").toURI(); 153 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 154 | ClassReader cr = new ClassReader(bytes); 155 | ClassWriter cw = new ClassWriter(0); 156 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 157 | private String methodName = "foo"; 158 | 159 | @Override 160 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 161 | if (methodName.equals(name)) { 162 | // 删除 foo 方法 163 | return null; 164 | } 165 | return super.visitMethod(access, name, descriptor, signature, exceptions); 166 | } 167 | 168 | @Override 169 | public void visitEnd() { 170 | MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, methodName, "(I)I", null, null); 171 | mv.visitCode(); 172 | mv.visitVarInsn(ILOAD, 1); 173 | mv.visitIntInsn(BIPUSH, 100); 174 | mv.visitInsn(IADD); 175 | mv.visitInsn(IRETURN); 176 | mv.visitEnd(); 177 | } 178 | }; 179 | cr.accept(cv, 0); 180 | byte[] modifiedBytes = cw.toByteArray(); 181 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 182 | } 183 | 184 | /** 185 | * 使用 ASM 自动计算 stack 和 locals 186 | */ 187 | @Test 188 | public void test7() throws URISyntaxException, IOException { 189 | URI uri = ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyMain.class").toURI(); 190 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 191 | ClassReader cr = new ClassReader(bytes); 192 | ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); 193 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 194 | private String methodName = "foo"; 195 | 196 | @Override 197 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 198 | if (methodName.equals(name)) { 199 | // 删除 foo 方法 200 | return null; 201 | } 202 | return super.visitMethod(access, name, descriptor, signature, exceptions); 203 | } 204 | 205 | @Override 206 | public void visitEnd() { 207 | MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, methodName, "(I)I", null, null); 208 | mv.visitCode(); 209 | mv.visitVarInsn(ILOAD, 1); 210 | mv.visitIntInsn(BIPUSH, 100); 211 | mv.visitInsn(IADD); 212 | mv.visitInsn(IRETURN); 213 | // 触发计算 214 | mv.visitMaxs(0, 0); 215 | mv.visitEnd(); 216 | } 217 | }; 218 | cr.accept(cv, 0); 219 | byte[] modifiedBytes = cw.toByteArray(); 220 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 221 | } 222 | 223 | @Test 224 | public void test8() throws IOException, URISyntaxException { 225 | URI uri = ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyMain.class").toURI(); 226 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 227 | ClassReader cr = new ClassReader(bytes); 228 | ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); 229 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 230 | @Override 231 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 232 | MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); 233 | if (!"foo".equals(name)) { 234 | return mv; 235 | } 236 | return new AdviceAdapter(ASM9, mv, access, name, descriptor) { 237 | @Override 238 | protected void onMethodEnter() { 239 | super.onMethodEnter(); 240 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 241 | mv.visitLdcInsn("enter:" + getName()); 242 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 243 | } 244 | 245 | @Override 246 | protected void onMethodExit(int opcode) { 247 | super.onMethodExit(opcode); 248 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 249 | if (opcode == ATHROW) { 250 | mv.visitLdcInsn("err exit:" + getName()); 251 | } else { 252 | mv.visitLdcInsn("normal exit:" + getName()); 253 | } 254 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 255 | } 256 | }; 257 | } 258 | }; 259 | cr.accept(cv, 0); 260 | byte[] modifiedBytes = cw.toByteArray(); 261 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 262 | } 263 | 264 | @Test 265 | public void test9() throws IOException, URISyntaxException { 266 | URI uri = ClassLoader.getSystemResource("com/diguage/cafe/divecode/MyMain.class").toURI(); 267 | byte[] bytes = FileUtils.readFileToByteArray(new File(uri)); 268 | ClassReader cr = new ClassReader(bytes); 269 | ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); 270 | ClassVisitor cv = new ClassVisitor(ASM9, cw) { 271 | @Override 272 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 273 | MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); 274 | if (!"divzero".equals(name)) { 275 | return mv; 276 | } 277 | return new AdviceAdapter(ASM9, mv, access, name, descriptor) { 278 | Label startLabel = new Label(); 279 | 280 | @Override 281 | protected void onMethodEnter() { 282 | super.onMethodEnter(); 283 | mv.visitLabel(startLabel); 284 | 285 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 286 | mv.visitLdcInsn("enter:" + getName()); 287 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 288 | } 289 | 290 | @Override 291 | public void visitMaxs(int maxStack, int maxLocals) { 292 | // 生成异常表 293 | Label endLabel = new Label(); 294 | mv.visitTryCatchBlock(startLabel, endLabel, endLabel, null); 295 | mv.visitLabel(endLabel); 296 | 297 | //生成异常代码 298 | finallyBlock(ATHROW); 299 | mv.visitInsn(ATHROW); 300 | super.visitMaxs(maxStack, maxLocals); 301 | } 302 | 303 | private void finallyBlock(int opcode) { 304 | mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 305 | if (opcode == ATHROW) { 306 | mv.visitLdcInsn("err exit:" + getName()); 307 | } else { 308 | mv.visitLdcInsn("normal exit:" + getName()); 309 | } 310 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 311 | } 312 | 313 | @Override 314 | protected void onMethodExit(int opcode) { 315 | super.onMethodExit(opcode); 316 | if (opcode != ATHROW) { 317 | finallyBlock(opcode); 318 | } 319 | } 320 | }; 321 | } 322 | }; 323 | cr.accept(cv, 0); 324 | byte[] modifiedBytes = cw.toByteArray(); 325 | FileUtils.writeByteArrayToFile(new File(uri), modifiedBytes); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /docs/creating-a-class.adoc: -------------------------------------------------------------------------------- 1 | [#creating-a-class] 2 | = 创建一个类 3 | 4 | 任何一个由 Byte Buddy 创建的类型都是通过 `ByteBuddy` 类的实例来完成的。通过简单地调用 `new ByteBuddy()` 就可以创建一个新实例,然后就可以出发了。希望你使用一个集成开发环境,这样在调用一个给定实例的方法时就能得到相应的提示。这样,你的集成开发环境就会引导你完成相应的方法调用,防止手动在 Byte Buddy 文档中查阅某个类的 API。正如之前所说,Byte Buddy 提供了一个领域特定语言,这样就可以尽可能地提高人类可读性。集成开发环境的提示在大部分情况下会指引你到正确的方向。说的够多了,让我们在 Java 编程环境中创建第一个类吧: 5 | 6 | [{java_source_attr}] 7 | ---- 8 | DynamicType.Unloaded dynamicType = new ByteBuddy() 9 | .subclass(Object.class) 10 | .make(); 11 | ---- 12 | 13 | 正如前面所设想的,上面的示例代码会创建一个继承至 `Object` 类型的类。这个动态创建的类型与直接扩展 `Object` 并且没有实现任何方法、属性和构造函数的类型是等价的。你可能已经注意到,我们都没有命名动态生成的类型,通常在定义 Java 类时却是必须的。当然,你也可以很容易地明确地命名这个类型: 14 | 15 | [{java_source_attr}] 16 | ---- 17 | DynamicType.Unloaded dynamicType = new ByteBuddy() 18 | .subclass(Object.class) 19 | .name("example.Type") 20 | .make(); 21 | ---- 22 | 23 | 如果没有明确的命名会怎么样呢?Byte Buddy 与 https://en.wikipedia.org/wiki/Convention_over_configuration[约定大于配置^] 息息相关,为你提供了我们认为比较方便的默认配置。至于类型命名,Byte Buddy 的默认配置提供了 `NamingStrategy`,它基于动态类型的超类名称来随机生成类名。此外,名称定义在与父类相同的包下,这样父类的包级访问权限的方法对动态类型也可见。如果你将示例子类命名为 `example.Foo`,那么生成的名称将会类似于 `example.Foo$$ByteBuddy$$1376491271`,这里的数字序列是随机的。这个规则的例外情况就是当子类是从 `java.lang` 包下的类扩展时,就是 `Object` 所在的包。Java 的安全模型不允许自定义类型存放在这个命名空间下。因此,默认命名策略下,这些类型名称将会冠以 `net.bytebuddy.renamed` 的前缀。 24 | 25 | 默认行为也许对你来说并不方便。感谢约定大于配置原则,你总是可以根据你的需要来选择默认行为。这正是 `ByteBuddy` 的优越之处。通过 `new ByteBuddy()` 创建实例,你就创建了整套的默认配置。通过调用在这个配置上的方法,你就可以根据你的需要来订制它。让我们试试: 26 | 27 | [{java_source_attr}] 28 | ---- 29 | DynamicType.Unloaded dynamicType = new ByteBuddy() 30 | .with(new NamingStrategy.AbstractBase() { 31 | @Override 32 | protected String name(TypeDescription superClass) { 33 | return "i.love.ByteBuddy." + superClass.getSimpleName(); 34 | } 35 | }) 36 | .subclass(Object.class) 37 | .make(); 38 | ---- 39 | 40 | 在上面这里例子中,我们创建了一个新的配置,在类型命名方面,不同于默认配置。匿名类被简单实现为连接 `i.love.ByteBuddy` 和基类的简要名称。当扩展 `Object` 类型时,动态类将被命名为 `i.love.ByteBuddy.Object`。当创建自己的命名策略时,需要特别小心。Java 虚拟机就是使用名字来区分不同的类型的,这正是为什么要避免命名冲突的原因。如果你需要定制命名行为,请考虑使用 Byte Buddy 内置的 `NamingStrategy.SuffixingRandom`,你可以通过引入比默认对你应用更有意义的前缀来定制命名行为。 41 | 42 | [#domain-specific-language-and-immutability] 43 | == 领域特定语言和不变性 44 | 45 | 在看过 Byte Buddy 这种领域特定语言的实际效果之后,我们需要简要看一下这种语言的实现方式。有一个细节需要特别注意,这个语言是围绕 https://en.wikipedia.org/wiki/Immutable_object[不可变对象^] 构建的。事实上,Byte Buddy 中,几乎所有的类都被构建成不可变的;极少数情况,我们不可能把对象构建成不可变的,我们会在该类的文档中明确指出。如果你为 Byte Buddy 实现自定义功能,我们建议你遵守此原则。 46 | 47 | 作为所提到的不可变性的含义,例如配置 `ByteBuddy` 实例时,一定要小心。你也许会犯下面的错误: 48 | 49 | [{java_source_attr}] 50 | ---- 51 | ByteBuddy byteBuddy = new ByteBuddy(); 52 | byteBuddy.withNamingStrategy(new NamingStrategy.SuffixingRandom("suffix")); 53 | DynamicType.Unloaded dynamicType = byteBuddy.subclass(Object.class).make(); 54 | ---- 55 | 56 | 你或许希望使用 `new NamingStrategy.SuffixingRandom("suffix")` 来自定义动态类型的命名策略。不是修改存储在 `byteBuddy` 变量中的实例,调用 `withNamingStrategy` 方法返回一个自定义的 `ByteBuddy` 实例,但是它却直接被丢弃了。结果,还是使用原来创建的默认配置来创建动态类型。 57 | 58 | [#redefining-and-rebasing-existing-classes] 59 | == 重新定义或者重定基底已经存在的类 60 | 61 | [NOTE] 62 | ==== 63 | *D瓜哥注* 64 | 65 | *`type rebasing`* 不知如何翻译是好,暂且翻译为“*重定基底*”。下文中,根据语句通顺需要,偶尔也翻译成“*重定义*”。如果有好的翻译,欢迎给发PR。 66 | ==== 67 | 68 | 到目前为止,我们仅仅演示了如何使用 Byte Buddy 来创建已知类的子类。相同的 API 还可用于增强已有类。增加已有类有两种方式: 69 | 70 | 类型重定义(type redefinition):: 71 | 当重定义一个类时,Byte Buddy 可以对一个已有的类添加属性和方法,或者删除已经存在的方法实现。如果使用其他的方法实现替换已经存在的方法实现,则原来存在的方法实现就会消失。例如,我们重定义下面这个类型 72 | + 73 | [{java_source_attr}] 74 | ---- 75 | class Foo { 76 | String bar() { return "bar"; } 77 | } 78 | ---- 79 | + 80 | 从 `bar` 方法返回 `"qux"`,那么该方法原来返回的 `"bar"` 等信息就会都被丢失掉。 81 | 82 | 类型重定基底(type rebasing):: 83 | 当重定基底一个类时,Byte Buddy 保存基底类所有方法的实现。当 Byte Buddy 如执行类型重定义时,它将所有这些方法实现复制到具有兼容签名的重新命名的私有方法中,而不是抛弃重写的方法。这样,就没有实现会被丢失。重定义的方法可以继续通过它们重命名过的名称调用原来的方法。通过这种方式,上述 `Foo` 类就会被重定义成下面这个样子: 84 | + 85 | [{java_source_attr}] 86 | ---- 87 | class Foo { 88 | String bar() { return "foo" + bar$original(); } 89 | private String bar$original() { return "bar"; } 90 | } 91 | ---- 92 | + 93 | 原来返回 `bar` 的方法被保存到了另外一个方法里,因此还可以访问。当重定基底一个类时,Byte Buddy 对待所有方法定义就像你定义一个子类一样,例如,如果你想调用重定义方法的超类方法是,它会调用被重定义的方法。__相反,它最终将这个假设的超级类别变成了上面显示的重定义类型。__ 94 | 95 | 任何重定基底、重定义或子类都是使用相同的 API 来执行,接口由 `DynamicType.Builder` 来定义。__这样,可以将类定义为子类,然后更改代码来替换重定类。__你只需要修改 Byte Buddy 领域特定语言的一个单词就能达到这个目的。这样,在定义的未来阶段,你就可以透明地切换任何一种方法: 96 | 97 | [{java_source_attr}] 98 | ---- 99 | new ByteBuddy().subclass(Foo.class) 100 | new ByteBuddy().redefine(Foo.class) 101 | new ByteBuddy().rebase(Foo.class) 102 | ---- 103 | 104 | 这在本教程的其余部分都有所解释。因为定义子类对于 Java 开发人员来说是如此地熟悉,所以,接下来的所有解释以及 Byte Buddy 领域特定语言的实例都是用创建子类来演示。但是,请记住,所有类可以类似地通过重定义或重定基类来定义。 105 | 106 | [#loading-a-class] 107 | == 加载类 108 | 109 | 到目前为止,我们只定义并创建了一个动态类型,但是我们没有使用它。由 Byte Buddy 创建的类型使用 `DynamicType.Unloaded` 的实例表示。顾名思义,这些类型不会加载到 Java 虚拟机中。相反,由 Byte Buddy 创建的类以二进制,Java 类文件格式形式表示。这样,您可以决定要使用生成的类型做什么。例如,您可能希望从构建脚本运行 Byte Buddy,该脚本仅在部署之前生成 Java 类以增强应用程序。为此,`DynamicType.Unloaded` 类允许提取表示动态类型的字节数组。为方便起见,该类型还提供了一个 `saveIn(File)` 方法,可以将类存储在给定的文件夹中。此外,它允许您使用 `inject(File)` 方法将类注入到现有的 Jar 文件中。 110 | 111 | 尽管直接访问类的二进制形式简单直接,但不幸的是,加载类型却非常复杂。在 Java 中,所有的类都使用 `ClassLoader` 来加载。这种类加载器的一个例子是引导类加载器,它负责加载 Java 类库中发布的类。另一方面,系统类加载器负责加载在 Java 应用程序的类路径上的类。显然,这些先前存在的类加载器都不知道我们创建的任何动态类。为了解决这个问题,我们必须找到能加载运行时生成类的其他可能性。 Byte Buddy 通过不同的方法提供解决方案: 112 | 113 | * 我们简单地创建一个新的 `ClassLoader`,并明确地告知它一个特定动态创建的类的存在位置。因为 Java 类加载器是以层次结构组织的,所以我们将此类加载器定义为运行中的 Java 应用程序中已经存在的给定类加载器的子类。这样,运行的Java程序的所有类型对于使用新的 `ClassLoader` 加载的动态类型都是可见的。 114 | 115 | * 通常,Java 类加载器在尝试直接加载给定名称的类型之前查询其双亲 `ClassLoader`。__这意味着类加载器通常不会加载类型,以防其父类加载程序知道具有相同名称的类型。__为了这个目的,Byte Buddy提供了一个子类优先的类加载器的创建功能,它尝试在查询父类之前自己加载一个类型。除此之外,这种方法类似于上述方法。请注意,此方法不会覆盖父类加载器的类型,__而是影响此其他类型。__ 116 | 117 | * 最后,我们可以使用反射来将类型注入到现有的 `ClassLoader` 中。通常,类加载器被要求以其名称提供给定类型。使用反射,我们可以围绕这个原理,并调用一个protected方法,将类添加到类加载器中,而类加载器实际上并不知道如何定位这个动态类。 118 | 119 | 不幸的是,上面的方式有两个缺点: 120 | 121 | * 如果我们创建一个新的 `ClassLoader`,这个类加载器就会定义一个新的命名空间。有意义的是,可以加载两个具有相同名称的类,只要这些类由两个不同的类加载器加载即可。即使这两个类代表相同的类实现,这两个类也不会被 Java 虚拟机视为相等。这个等式的规则也适用于Java包。这意味着一个类 `example.Foo` 不能访问另一个类 `example.Bar` 的包私有级的方法,如果两个类不是由相同的类加载器加载的话。另外,如果 `exam​​ple.Bar` 扩展 `example.Foo`,任何覆盖的包私有级的方法将变得不起作用,将会委托给原始实现。 122 | 123 | * 每当加载类时,一旦引用另一种类型的代码段被解析,其类加载器就会查找该类中引用的任何类型。这个查找会委托给同一个类加载器。想象一下,我们动态创建两个类 `example.Foo` 和 `example.Bar`。如果我们将 `example.Foo` 注入到一个现有的类加载器中,这个类加载器可能会尝试找到 `example.Bar`。然而,这种查找会失败,因为后一类是动态创建的,对于我们刚注入 `example.Foo` 类的类加载器是不可访问的。因此,__反射方法不能用于在类加载期间变得有效的循环依赖性的类。__幸运的是,大多数 Java 虚拟机实现会在第一次主动使用时惰性地解析引用的类,这就是为什么类注入通常可以工作而没有这些限制。另外在实践中,由 Byte Buddy 创建的类通常不会受到这种循环的影响。 124 | 125 | __您可能会考虑到遇到循环依赖关系的可能性与您一次创建一个动态类型相关联。__但是,动态创建类型可能会触发所谓的辅助类型的创建。这些类型由 Byte Buddy 自动创建,以提供对您正在创建的动态类型来访问。我们在下一节中详细了解辅助类型,现在不用担心。但是,由于这个原因,我们建议您通过创建一个特定的 `ClassLoader` 来加载动态创建的类,而不是将它们注入现有类。 126 | 127 | 创建 `DynamicType.Unloaded` 之后,可以使用 `ClassLoadingStrategy` 加载此类型。__如果没有提供这样的策略,Byte Buddy 会根据提供的类加载器推测出这样的策略,并为引导类加载器创建一个新的类加载器,其中不能使用反射注入类型,否则为默认值。__Byte Buddy提供了几种类加载策略,其中每种都遵循上述概念之一。这些策略定义在 `ClassLoadingStrategy.Default` 中,其中 `WRAPPER` 策略创建一个新的包装 `ClassLoader`;`CHILD_FIRST` 策略创建一个类似于第一个子类优先的类加载器;`INJECTION` 策略使用反射注入动态类型。 `WRAPPER` 和 `CHILD_FIRST` 策略也可以在所谓的__清单版本__中使用,即使在加载类之后,类型的二进制格式也被保留。这些替代版本使得类加载器的类的二进制表示可以通过 `ClassLoade::getResourceAsStream` 方法访问。但是,请注意,这需要这些类加载器来维护对类的完整二进制表示的引用,这将占用 Java 虚拟机堆上的空间。因此,如果您打算实际访问二进制格式,则应仅使用清单版本。由于 `INJECTION` 策略通过反射工作,并且无法更改 `ClassLoader::getResourceAsStream` 方法的语义,因此它在清单版本中自然不可用。 128 | 129 | 我们来看看类加载的实际操作: 130 | 131 | [{java_source_attr}] 132 | ---- 133 | Class type = new ByteBuddy() 134 | .subclass(Object.class) 135 | .make() 136 | .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) 137 | .getLoaded(); 138 | ---- 139 | 140 | 在上面的例子中,我们创建并加载了一个类。我们使用 `WRAPPER` 策略来加载适合大多数情况的类,就像我们之前提到的那样。最后,`getLoaded` 方法返回一个 Java `Class` 的实例,它就表示现在加载的动态类。 141 | 142 | __请注意,加载类时,通过应用当前执行上下文的 `ProtectionDomain` 来执行预定义的类加载策略。或者,所有默认策略通过调用 `withProtectionDomain` 方法来提供明确保护域的规范。使用安全管理员(security manager)或使用已签名的 Jar 中定义的类时,定义显式保护域很重要。__ 143 | 144 | [#reloading-a-class] 145 | == 重新加载类 146 | 147 | 在前面章节,我们学习了如何使用 Byte Buddy 去重定义或者重定基底一个已存在的类。然而,在 Java 程序的执行过程中,通常不可能保证特定的类没有被加载。(此外,Byte Buddy目前只将加载的类作为它的参数,这将在未来的版本中改变,现有的API可以用于同等地处理未加载的类。)由于 Java 虚拟机的“热替换(HotSwap)”特性,即使在加载后也可以重新定义现有类。通过 Byte Buddy 的 `ClassRelodingsTrategy` 即可使用此功能。让我们通过重新定义类 `Foo` 来演示这种策略: 148 | 149 | [{java_source_attr}] 150 | ---- 151 | class Foo { 152 | String m() { return "foo"; } 153 | } 154 | 155 | class Bar { 156 | String m() { return "bar"; } 157 | } 158 | ---- 159 | 160 | 使用 Byte Buddy,我们现在可以轻松地将类 `Foo` 重新定义为 `Bar`。使用热替换,这个重定义甚至可以应用于先前存在的实例: 161 | 162 | [{java_source_attr}] 163 | ---- 164 | ByteBuddyAgent.install(); 165 | Foo foo = new Foo(); 166 | new ByteBuddy() 167 | .redefine(Bar.class) 168 | .name(Foo.class.getName()) 169 | .make() 170 | .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 171 | 172 | assertThat(foo.m(), is("bar")); 173 | ---- 174 | 175 | TIP: 为了方便对比原文与翻译,以求有志之士改进翻译,以下增加英语原文,原文下面增加翻译。 176 | 177 | HotSwap is only accessible using a https://docs.oracle.com/en/java/javase/17/docs/api/java.instrument/java/lang/instrument/package-summary.html[so-called Java agent^]. Such an agent can be installed by either specifying it on the startup of the Java virtual machine by using the `-javaagent` parameter where the parameter's argument needs to be Byte Buddy's agent jar which can be https://search.maven.org/search?q=a:byte-buddy-agent[downloaded from Maven Central^]. However, when a Java application is run from a JDK-installation of the Java virtual machine, Byte Buddy can load a Java agent even after application startup by `ByteBuddyAgent.installOnOpenJDK()`. Because class redefinition is mostly used to implement tooling or testing, this can be a very convenient alternative. Since Java 9, an agent installation is also possible at runtime without a JDK-installation. 178 | 179 | 热替换仅能通过所谓的 https://docs.oracle.com/en/java/javase/17/docs/api/java.instrument/java/lang/instrument/package-summary.html[Java Agent^] 进行访问。__可以通过使用 `-javaagent` 参数在Java 虚拟机的启动时指定 Java Agent,其中参数值是使用 Byte Buddy 开发的 Jar 包,而 Byte Buddy 相关依赖则可以从 https://search.maven.org/search?q=a:byte-buddy-agent[Maven Central^] 下载。__然而,当 Java 应用程序是在从 JDK 安装的 Java 虚拟机中运行时,Byte Buddy 甚至可以通过 `ByteBuddyAgent.installOnOpenJDK()` 在启动应用程序后加载 Java Agent。因为类重定义主要用于实现工具或测试,所以这是一种非常方便的替代方法。从 Java 9 开始,在运行时不需要 JDK 安装也可以进行代理安装。 180 | 181 | One thing that might first appear counter-intuitive about the above example is the fact that Byte Buddy is instructed to redefine the `Bar` type where the `Foo` type is eventually redefined. The Java virtual machine identifies types by their name and a class loader. Therefore, by renaming `Bar` to `Foo` and applying this definition, we eventually redefine the type we renamed `Bar` into. It is of course equally possible to redefine Foo directly without renaming a different type. 182 | 183 | 关于上面的示例,首先看起来可能与直觉相反的一件事是,Byte Buddy 被指示重新定义 `Bar` 类型,而 `Foo` 类型最终被重新定义。Java 虚拟机通过类型的名称和类加载器来识别类型。因此,通过将 `Bar` 重命名为 `Foo` 并应用此定义,我们最终重新定义了将 `Bar` 重命名为的类型。当然,同样可以直接重新定义 `Foo`,而无需重命名其他类型。 184 | 185 | Using Java's HotSwap feature, there is however one huge drawback. Current implementations of HotSwap require that the redefined classes apply the same class schema both before and after a class redefinition. This means that it is not allowed to add methods or fields when reloading classes. We already discussed that Byte Buddy defines copies of the original methods for any rebased class such that class rebasing does not work for the `ClassReloadingStrategy`. Also, class redefinition does not work for classes with an explicit class initializer method (a static block within a class) because this initializer needs to be copied into an extra method as well. Unfortunately OpenJDK has withdrawn from https://openjdk.org/jeps/159[extending HotSwap functionality^], so there is no way to work around this limitation using the HotSwap feature. In the mean time, Byte Buddy's HotSwap support can be used for corner-cases where it seems useful. Otherwise, class rebasing and redefinition can be a convenient feature when enhancing existing classes from for example a build script. 186 | 187 | 然而,使用 Java 的热交换功能,有一个巨大的缺点。当前的热替换实现要求重定义的类在重定义之前和之后都应用相同的模式。这意味着在重载类时不允许添加方法或字段。我们已经讨论过,Byte Buddy 为任何重定基类定义了原始方法的副本,这样重定基底的类,对于 `ClassReloadingStrategy` 来说,都是不适用的。同样,类重定义不适用于具有显式类初始化器方法(类中的静态块)的类,因为这个初始化器还需要复制到一个额外的方法中。不幸的是,OpenJDK 已经退出了 https://openjdk.org/jeps/159[对热替换功能的扩展^],因此使用热替换功能就无法绕过这一限制。同时,Byte Buddy的热替换对于一些极端情况,似乎特别有用。同时,当增加现有类时,比如一个构建脚本,类的重定基底和类的重定义将是一个非常方便的特效。 188 | 189 | [#working-with-unloaded-classes] 190 | == 操作没有加载的类 191 | 192 | With this realization about the limits of Java's HotSwap feature, one might think that the only meaningful application of the `rebase` and `redefinition` instructions would be during build time. By applying build-time manipulation, one can assert that a processed class is not loaded before its initial class loading simply because this class loading is accomplished in a different instance of the JVM. Byte Buddy is however equally capable of working with classes that were not yet loaded. For this, Byte Buddy abstracts over Java's reflection API such that a `Class` instance is for example internally represented by an instance of a `TypeDescription`. As a matter of fact, Byte Buddy only knows how to process a provided `Class` by an adapter that implements the TypeDescription interface. The big advantage over this abstraction is that information on classes do not need to be provided by a `ClassLoader` but can be provided by any other sources. 193 | 194 | 通过对 Java “热替换”特性的限制的认识,人们可能会认为,类型重定基底和类型重定义的唯一有意义的应用之处是在构建时。通过应用构建时间操作,可以断言,在初始类加载之前不会加载已处理的类,因为该类的加载是在 JVM 的不同实例中完成的。然而,Byte Buddy 同样能够处理尚未加载的类。为此,Byte Buddy 通过 Java 的反射 API 进行抽象,例如,一个 `Class` 实例在 Byte Buddy 内由一个 `TypeDescription` 实例来表示。事实上,Byte Buddy 只知道如何处理由实现 `TypeDescription` 接口的适配器提供的类。与此抽象相比,最大的优势在于,类的信息不需要由 `ClassLoader` 提供,而可以由任何其他来源提供。 195 | 196 | 197 | Byte Buddy provides a canonical manner for getting hold of a class's `TypeDescription` using a `TypePool`. A default implementation of such a pool is of course also provided. This `TypePool.Default` implementation parses the binary format of a class and represents it as the required `TypeDescription`. Similarly to a `ClassLoader` it maintains a cache for represented classes which is also customizable. Also, it normally retrieves the binary format of a class from a `ClassLoader`, however without instructing it to load this class. 198 | 199 | Byte Buddy 提供了一种使用 `TypePool` 获取类的 `TypeDescription` 的规范方式。当然也提供了这样一个此类型池的默认实现。`TypePool.Default` 实现解析类的二进制格式,并将其表示为所需的 `TypeDescription`。与 `ClassLoader` 类似,它为所表示的类维护一个缓存,这也是可定制的。此外,它通常从 `ClassLoader` 中检索类的二进制格式,但是无需指示加载此类。 200 | 201 | 202 | The Java virtual machine only loads a class on its first usage. As a consequence, we can for example safely redefine a class such as 203 | 204 | Java 虚拟机仅在第一次使用时加载类。因此,我们可以安全地重新定义一个类,例如 205 | 206 | [{java_source_attr}] 207 | ---- 208 | package foo; 209 | class Bar { } 210 | ---- 211 | 212 | right at program startup before running any other code: 213 | 214 | 在程序启动时,在运行任何其他代码之前: 215 | 216 | [{java_source_attr}] 217 | ---- 218 | class MyApplication { 219 | public static void main(String[] args) throws Exception { 220 | TypePool typePool = TypePool.Default.ofSystemLoader(); 221 | Class type = new ByteBuddy() 222 | .redefine(typePool.describe("foo.Bar").resolve(), // do not use 'Bar.class' 223 | ClassFileLocator.ForClassLoader.ofClassPath()) 224 | .defineField("qux", String.class) // we learn more about defining fields later 225 | .make() 226 | .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER) 227 | .getLoaded(); 228 | assertThat(type.getDeclaredField("qux"), notNullValue()); 229 | } 230 | } 231 | ---- 232 | 233 | WARNING: 这个示例有错!已经在 GitHub 上提交了 Issue: https://github.com/raphw/byte-buddy/issues/1293[The example in "Working with unloaded classes" is wrong.^]。 234 | 235 | By explicitly loading the redefined class before its first use in the assertion statement, we forestall the JVM's built-in class loading. This way, the redefined definition of `foo.Bar` is loaded and used throughout our application's runtime. Note however that we do not reference the class by a class literal when we use the `TypePool` to provide a description. If we did use a class literal for `foo.Bar`, the JVM would have loaded this class before we had a chance to redefine it and our redefinition attempt would be without effect. Also, note that when working with unloaded classes, we further need to specify a `ClassFileLocator` which allows to locate a class's class file. In the example above, we simply create a class file locator which scans the running application's class path for such files. 236 | 237 | 通过在断言语句中、首次使用之前显式加载重定义类型,我们阻止了 JVM 内置的类加载。这样,经过类型重定义的 `foo.Bar`,就可以在应用程序的整个运行时被加载和使用。但是请注意,在使用 `TypePool` 提供类型描述时,并没有通过类名引用类。如果确实通过类名引用了类,则 JVM 在有机会进行重定义它之前就已经加载了这个类,那么类型重定义的尝试将无效。另外,请注意,在处理未加载的类时,我们还需要指定一个 `ClassFileLocator`,它允许定位类的类文件。在上面的示例中,我们只需创建一个类文件定位器,用于扫描正在运行的应用程序的类路径以查找此类文件。 238 | 239 | [#creating-java-agents] 240 | == 创建 Java Agents 241 | 242 | When an application grows bigger and becomes more modular, applying such a transformation at a specific program point is of course a cumbersome constraint to enforce. And there is indeed a better way to apply such class redefinitions on demand. Using a Java agent, it is possible to directly intercept any class loading activity that is conducted within a Java application. A Java agent is implemented as a simple jar file with an entry point that is specified in this jar file's manifest file as it is described under the linked resource. Using Byte Buddy, the implementation of such an agent is straight forward by using an `AgentBuilder`. Assuming that we previously defined a simple annotation named `ToString`, it would be trivial to implement `toString` methods for all annotated classes simply by implementing the Agent's `premain` method as follows: 243 | 244 | 当应用程序变得更大、更模块化时,在特定的程序点应用这样的转换当然是一个难以执行的约束。确实有更好的方法可以按需应用此类类型重定义。使用 https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html[Java Agent^],可以直接拦截 Java 应用程序中执行的任何类加载活动。Java 代理被实现为一个简单的 Jar 文件,其入口点在该 Jar 文件的清单文件中指定,正如在链接资源中描述的那样。使用 Byte Buddy,通过使用 `AgentBuilder` 直接实现此类代理。假设我们以前定义了一个名为 `ToString` 的简单注解,那么只需 Java Agent 的 `premain` 方法,就可以为所有带注解的类实现 `toString` 方法。代码如下所示: 245 | 246 | [{java_source_attr}] 247 | ---- 248 | class ToStringAgent { 249 | public static void premain(String arguments, Instrumentation instrumentation) { 250 | new AgentBuilder.Default() 251 | .type(isAnnotatedWith(ToString.class)) 252 | .transform(new AgentBuilder.Transformer() { 253 | @Override 254 | public DynamicType.Builder transform(DynamicType.Builder builder, 255 | TypeDescription typeDescription, 256 | ClassLoader classloader) { 257 | return builder.method(named("toString")) 258 | .intercept(FixedValue.value("transformed")); 259 | } 260 | }).installOn(instrumentation); 261 | } 262 | } 263 | ---- 264 | 265 | [#loading-classes-in-android-applications] 266 | == 在 Android 应用中加载类 267 | 268 | [#working-with-generic-types] 269 | == 使用泛型类 270 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.diguage.cafe 9 | jiadao 10 | 0.0.1-SNAPSHOT 11 | jiadao,贾岛。取义贾岛的“推敲”典故,意指字节码学习要仔细推敲。 12 | https://www.diguage.com/ 13 | 14 | 15 | 1.12.12 16 | 17 | 2.5.5 18 | 2.1.6 19 | 1.5.1 20 | 2.2.3 21 | 2.2.2 22 | 0.0.16 23 | 9.3.4.0 24 | ${project.basedir}/cfg/gems 25 | 26 | UTF-8 27 | UTF-8 28 | 1.8 29 | 1.8 30 | 31 | 32 | 33 | 34 | 35 | com.sun 36 | tools 37 | 1.8 38 | system 39 | ${env.JAVA_HOME}/lib/tools.jar 40 | 41 | 42 | 43 | net.bytebuddy 44 | byte-buddy 45 | ${byte-buddy.version} 46 | 47 | 48 | net.bytebuddy 49 | byte-buddy-agent 50 | ${byte-buddy.version} 51 | 52 | 53 | net.bytebuddy 54 | byte-buddy-benchmark 55 | ${byte-buddy.version} 56 | test 57 | 58 | 59 | 60 | org.junit.jupiter 61 | junit-jupiter-engine 62 | 63 | 64 | org.assertj 65 | assertj-core 66 | 3.23.1 67 | 68 | 69 | 70 | org.ow2.asm 71 | asm-util 72 | 73 | 74 | org.ow2.asm 75 | asm-tree 76 | 77 | 78 | org.ow2.asm 79 | asm-commons 80 | 81 | 82 | 83 | org.javassist 84 | javassist 85 | 3.29.1-GA 86 | 87 | 88 | 89 | commons-io 90 | commons-io 91 | 2.11.0 92 | 93 | 94 | 95 | rubygems 96 | asciidoctor-multipage 97 | ${asciidoctor-multipage.version} 98 | gem 99 | 100 | 101 | rubygems 102 | asciidoctor 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.junit 112 | junit-bom 113 | 5.8.2 114 | pom 115 | import 116 | 117 | 118 | org.ow2.asm 119 | asm-bom 120 | 9.3 121 | pom 122 | import 123 | 124 | 125 | 126 | 127 | 128 | ${project.artifactId} 129 | 130 | 131 | org.torquebox.mojo 132 | mavengem-wagon 133 | 1.0.3 134 | 135 | 136 | 137 | 138 | de.saumya.mojo 139 | gem-maven-plugin 140 | 2.0.1 141 | 142 | ${jruby.version} 143 | 144 | 145 | ${gem-path} 146 | 147 | 148 | ${gem-path} 149 | 150 | 151 | 152 | 153 | install-gems 154 | 155 | initialize 156 | 157 | initialize 158 | 159 | 160 | 161 | 162 | org.asciidoctor 163 | asciidoctor-maven-plugin 164 | ${asciidoctor-maven-plugin.version} 165 | 166 | docs 167 | index.adoc 168 | 169 | asciidoctor-diagram 170 | 171 | 172 | ${project.build.sourceDirectory}/com/diguage/cafe/jiadao 173 | ${project.basedir} 174 | rouge 175 | github 176 | . 177 | font 178 | true 179 | assets/styles 180 | 3 181 | true 182 | 3 183 | true 184 | /usr/local/bin/dot 185 | linenums,indent=0,subs="attributes,verbatim,quotes" 186 | source%nowrap,java,{source_attr} 187 | source%nowrap,html,{source_attr} 188 | align="center",width=100% 189 | format=svg,align="center",width=90% 190 | 191 | 192 | 193 | 194 | generate-multipage 195 | package 196 | 197 | process-asciidoc 198 | 199 | 200 | multipage_html5 201 | book 202 | 203 | ${gem-path} 204 | 205 | asciidoctor-multipage 206 | 207 | 208 | ${project.build.directory}/docs/multipage 209 | 210 | 211 | left 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | org.asciidoctor 300 | asciidoctorj 301 | ${asciidoctorj.version} 302 | 303 | 304 | org.asciidoctor 305 | asciidoctorj-pdf 306 | ${asciidoctorj-pdf.version} 307 | 308 | 309 | org.asciidoctor 310 | asciidoctorj-epub3 311 | ${asciidoctorj-epub3.version} 312 | 313 | 314 | org.asciidoctor 315 | asciidoctorj-diagram 316 | ${asciidoctorj-diagram.version} 317 | 318 | 319 | org.jruby 320 | jruby-complete 321 | ${jruby.version} 322 | 323 | 324 | 325 | 326 | 327 | org.apache.maven.plugins 328 | maven-shade-plugin 329 | 3.3.0 330 | 331 | 332 | package 333 | 334 | shade 335 | 336 | 337 | 338 | 339 | rubygems:* 340 | 341 | 342 | org.ow2.asm:* 343 | com.sun:tools 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | org.apache.maven.plugins 352 | maven-jar-plugin 353 | 3.2.2 354 | 355 | 356 | 357 | com.diguage.cafe.divecode.AgentMain 358 | com.diguage.cafe.divecode.AgentMain 359 | true 360 | true 361 | 362 | 363 | true 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | mavengems 375 | mavengem:https://rubygems.org 376 | 377 | 378 | 379 | --------------------------------------------------------------------------------