在上一篇中尝试了 使用 Javassist 运行时生成泛型子类,这里要用另一个更方便的字节码增加组件 Byte Buddy 来实现类似的功能, 但代码上要直白一些。就是运用 Byte Buddy 在运行时生成一个类的子类,带泛型的,给类加上一个注解,可生成类文件或 Class 实例,不过这里更进一步,实现的方法是带参数的。
用 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 类
1 2 3 4 5 |
package cc.unmi; public abstract class Repository<T> { abstract T findOne(int id); } |
与前面有所不同的是上面的抽象方法带有一个参数
注解 Scope
1 2 3 4 5 6 7 8 9 |
package cc.unmi; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) public @interface Scope { String value(); } |
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package cc.unmi; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.matcher.ElementMatchers; import java.io.File; import java.lang.reflect.Method; public class Main { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { //泛型类型需要这么声明参数类型 TypeDescription.Generic genericSuperClass = TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build(); //new ByteBuddy().subclass(Repository.class) //简单非泛型类可以这么做 DynamicType.Unloaded<?> unloadedType = new ByteBuddy().subclass(genericSuperClass) .name(Repository.class.getPackage().getName().concat(".").concat("UserRepository")) .method(ElementMatchers.named("findOne")) //ElementMatchers 提供了多种方式找到方法 //.intercept(FixedValue.value("Yanbin")) //最简单的方式就是返回一个固定值 .intercept(MethodDelegation.to(FindOneInterceptor.class)) //使用 FindOneInterCeptor 中的实现,定义在下方 .annotateType(AnnotationDescription.Builder.ofType(Scope.class).define("value", "Session").build()) .make(); // 在 Maven 项目中,写类文件在 target/classes/cc/unmi/UserRepository.class 中 unloadedType.saveIn(new File("target/classes")); //可以这样生成字节码得到 Class 实例来加载使用 //Class<?> subClass = unloadedType.load(Main.class.getClassLoader(), // ClassLoadingStrategy.Default.WRAPPER).getLoaded(); Class<Repository<String>> repositoryClass = (Class<Repository<String>>) Class.forName("cc.unmi.UserRepository"); System.out.println(repositoryClass.getAnnotation(Scope.class).value()); //输出 Session Repository<String> repository = repositoryClass.newInstance(); System.out.println(repository.findOne(7792)); //输出 http://yanbin.blog/?p=7792 } private static class FindOneInterceptor { //通过 method 和 arguments 可获得原方法引用与实际传入参数 //如果不关心它们或其中某个,可在声明 intercept() 方法时省略。也能加更多参数,如 @SuperCall Callable<?> call static String intercept(@Origin Method method, @AllArguments Object[] arguments) { return "http://yanbin.blog/?p=" + arguments[0]; } } } |
请从运行输出结果与上面的注释中去理解操作过程。
上面的实现与 使用 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://yanbin.blog/?p=7792
不妨看看生成的 target/classes/cc/unmi/UserRepository.class
文件在 IntelliJ IDEA 中反编译后的样子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package cc.unmi; import cc.unmi.Repository; import cc.unmi.Scope; import cc.unmi.Main.FindOneInterceptor; @Scope("Session") public class UserRepository extends Repository<String> { String findOne(int var1) { return FindOneInterceptor.intercept(cachedValue$kkUnaNUd$e1iccd2, new Object[]{Integer.valueOf(var1)}); } public UserRepository() { } static { cachedValue$kkUnaNUd$e1iccd2 = Repository.class.getDeclaredMethod("findOne", new Class[]{Integer.TYPE}); } } |
应用拓展可参考前一篇 使用 Javassist 运行时生成泛型子类 的末尾部分
在搜索关于 Byte Buddy 使用疑问时常常可以看到作者 Rafael Winterhalter 本人热心解答。
相关链接: