使用 Byte Buddy 运行时生成泛型子类
在上一篇中尝试了 使用 Javassist 运行时生成泛型子类,这里要用另一个更方便的字节码增加组件 Byte Buddy 来实现类似的功能, 但代码上要直白一些。就是运用 Byte Buddy 在运行时生成一个类的子类,带泛型的,给类加上一个注解,可生成类文件或 Class 实例,不过这里更进一步,实现的方法是带参数的。
用 Byte Buddy 操作起来更简单,根本不需要接触任何字节码相关的,诸如常量池等概念。与 Javassist 相比,Byte Buddy 更为先进的是能生成的类文件都是可加载运行的,不像 Javassist 生成的类文件反编译出来是看起来是正常的,但一加载执行却不那回事。
本例所使用的 Byte Buddy 的版本是当前最新的 1.6.7,在 Maven 项目中用下面的方式引入依赖
泛型的 Repository 类
与前面有所不同的是上面的抽象方法带有一个参数
注解 Scope
测试代码如下
请从运行输出结果与上面的注释中去理解操作过程。
上面的实现与 使用 Javassist 运行时生成泛型子类 相比要简洁了许多:
控制台的输出为
应用拓展可参考前一篇 使用 Javassist 运行时生成泛型子类 的末尾部分
在搜索关于 Byte Buddy 使用疑问时常常可以看到作者 Rafael Winterhalter 本人热心解答。
相关链接:
永久链接 https://yanbin.blog/leverage-bytebuddy-generate-generic-subclass/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
用 Byte Buddy 操作起来更简单,根本不需要接触任何字节码相关的,诸如常量池等概念。与 Javassist 相比,Byte Buddy 更为先进的是能生成的类文件都是可加载运行的,不像 Javassist 生成的类文件反编译出来是看起来是正常的,但一加载执行却不那回事。
本例所使用的 Byte Buddy 的版本是当前最新的 1.6.7,在 Maven 项目中用下面的方式引入依赖
<dependency>下面是几个需要在本例中用到的类定义
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.6.7</version>
</dependency>
泛型的 Repository 类
1package cc.unmi;
2
3public abstract class Repository<T> {
4 abstract T findOne(int id);
5}与前面有所不同的是上面的抽象方法带有一个参数
注解 Scope
1package cc.unmi;
2
3import java.lang.annotation.Retention;
4import static java.lang.annotation.RetentionPolicy.RUNTIME;
5
6@Retention(RUNTIME)
7public @interface Scope {
8 String value();
9}测试代码如下
1package cc.unmi;
2
3import net.bytebuddy.ByteBuddy;
4import net.bytebuddy.description.annotation.AnnotationDescription;
5import net.bytebuddy.description.type.TypeDescription;
6import net.bytebuddy.dynamic.DynamicType;
7import net.bytebuddy.implementation.MethodDelegation;
8import net.bytebuddy.implementation.bind.annotation.AllArguments;
9import net.bytebuddy.implementation.bind.annotation.Origin;
10import net.bytebuddy.matcher.ElementMatchers;
11
12import java.io.File;
13import java.lang.reflect.Method;
14
15public class Main {
16
17 @SuppressWarnings("unchecked")
18 public static void main(String[] args) throws Exception {
19 //泛型类型需要这么声明参数类型
20 TypeDescription.Generic genericSuperClass =
21 TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build();
22
23 //new ByteBuddy().subclass(Repository.class) //简单非泛型类可以这么做
24 DynamicType.Unloaded<?> unloadedType = new ByteBuddy().subclass(genericSuperClass)
25 .name(Repository.class.getPackage().getName().concat(".").concat("UserRepository"))
26 .method(ElementMatchers.named("findOne")) //ElementMatchers 提供了多种方式找到方法
27 //.intercept(FixedValue.value("Yanbin")) //最简单的方式就是返回一个固定值
28 .intercept(MethodDelegation.to(FindOneInterceptor.class)) //使用 FindOneInterCeptor 中的实现,定义在下方
29 .annotateType(AnnotationDescription.Builder.ofType(Scope.class).define("value", "Session").build())
30 .make();
31
32 // 在 Maven 项目中,写类文件在 target/classes/cc/unmi/UserRepository.class 中
33 unloadedType.saveIn(new File("target/classes"));
34
35 //可以这样生成字节码得到 Class 实例来加载使用
36 //Class<?> subClass = unloadedType.load(Main.class.getClassLoader(),
37 // ClassLoadingStrategy.Default.WRAPPER).getLoaded();
38
39 Class<Repository<String>> repositoryClass = (Class<Repository<String>>) Class.forName("cc.unmi.UserRepository");
40 System.out.println(repositoryClass.getAnnotation(Scope.class).value()); //输出 Session
41
42 Repository<String> repository = repositoryClass.newInstance();
43 System.out.println(repository.findOne(7792)); //输出 http://unmi.cc/?p=7792
44 }
45
46 private static class FindOneInterceptor {
47 //通过 method 和 arguments 可获得原方法引用与实际传入参数
48 //如果不关心它们或其中某个,可在声明 intercept() 方法时省略。也能加更多参数,如 @SuperCall Callable<?> call
49 static String intercept(@Origin Method method, @AllArguments Object[] arguments) {
50 return "http://unmi.cc/?p=" + arguments[0];
51 }
52 }
53}请从运行输出结果与上面的注释中去理解操作过程。
上面的实现与 使用 Javassist 运行时生成泛型子类 相比要简洁了许多:
- 其中子类中默认构造调用父类默认构造的情况不需要特别说明
- 给泛型指定参数类型也不容易产生问题
- 给类加个注解也无需涉到了常量池的概念
- 并且泛型方法无须关心类型擦除的机制, 这一点可对比两篇中生成的字节码反编译出的 findOne() 方法返回类型, Object v.s. String
控制台的输出为
Feb 01, 2017 10:32:30 PM net.bytebuddy.dynamic.DynamicType$Default saveIn不妨看看生成的
INFO: Writing file to existing folder structure: target/classes/cc/unmi
Session
http://unmi.cc/?p=7792
target/classes/cc/unmi/UserRepository.class 文件在 IntelliJ IDEA 中反编译后的样子 1//
2// Source code recreated from a .class file by IntelliJ IDEA
3// (powered by Fernflower decompiler)
4//
5
6package cc.unmi;
7
8import cc.unmi.Repository;
9import cc.unmi.Scope;
10import cc.unmi.Main.FindOneInterceptor;
11
12@Scope("Session")
13public class UserRepository extends Repository<String> {
14 String findOne(int var1) {
15 return FindOneInterceptor.intercept(cachedValue$kkUnaNUd$e1iccd2, new Object[]{Integer.valueOf(var1)});
16 }
17
18 public UserRepository() {
19 }
20
21 static {
22 cachedValue$kkUnaNUd$e1iccd2 = Repository.class.getDeclaredMethod("findOne", new Class[]{Integer.TYPE});
23 }
24}应用拓展可参考前一篇 使用 Javassist 运行时生成泛型子类 的末尾部分
在搜索关于 Byte Buddy 使用疑问时常常可以看到作者 Rafael Winterhalter 本人热心解答。
相关链接:
永久链接 https://yanbin.blog/leverage-bytebuddy-generate-generic-subclass/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。