❝沉淀、分享、成长,让自己和他人都能有所收获 ❞
截至到本章节关于字节码框架 Byte-buddy
的大部分常用 API 的使用已经通过案例介绍比较全面了,接下来介绍关于如何去实现一个抽象类以及创建出相应注解(包括类的注解和方法的注解)的知识点。而注解的这部分内容在一些监控或者拦截处理的场景下还是比较常用的,所以在这章节我们会通过一个例子来创建出含有自定义注解的类和方法。
如果你已经阅读了之前的系列文章,这部分学习的内容并不会有太多的陌生,主要是关于委托(MethodDelegation)
方法的使用以及补充自定义注解。
「那么」,接下来我们就使用委托和注解方式来创建这样的案例进行学习。
itstack-demo-bytecode-2-03
,可以关注「公众号」:bugstack虫洞栈
,回复源码下载获取。你会获得一个下载链接列表,打开后里面的第17个「因为我有好多开源代码」
,记得给个Star
!在这里我们定义了一个抽象并且含有泛型的接口类,如下;
public abstract class Repository<T> {
public abstract T queryData(int id);
}
那么接下来的案例会使用到委托的方式进行实现抽象类方法并加入自定义注解,也就相当于我们使用代码进行编程实现的效果。
@RpcGatewayClazz( clazzDesc = "查询数据信息", alias = "dataApi", timeOut = 350L )
public class UserRepository extends Repository<String> {
@RpcGatewayMethod( methodName = "queryData", methodDesc = "查询数据" )
public String queryData(int var1) {
// ...
}
}
在技术实现的过程中会逐步的去实现我们需要的功能,将需要的用到知识点信息拆开讲解,以达到最终的案例目标。
「模拟网关类注解」
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RpcGatewayClazz {
String clazzDesc() default "";
String alias() default "";
long timeOut() default 350;
}
「模拟网关方法注解」
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RpcGatewayMethod {
String methodName() default "";
String methodDesc() default "";
}
public class UserRepositoryInterceptor {
public static String intercept(@Origin Method method, @AllArguments Object[] arguments) {
return "小傅哥博客,查询文章数据:https://bugstack.cn/?id=" + arguments[0];
}
}
@Origin
可以绑定到以下类型的参数:Method 被调用的原始方法 Constructor 被调用的原始构造器 Class 当前动态创建的类 MethodHandle MethodType String 动态类的toString()的返回值 int 动态方法的修饰符.@AllArguments
绑定所有参数的数组。// 生成含有注解的泛型实现字类
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build()) // 创建复杂类型的泛型注解
.name(Repository.class.getPackage().getName().concat(".").concat("UserRepository")) // 添加类信息包括地址
.method(ElementMatchers.named("queryData")) // 匹配处理的方法
.intercept(MethodDelegation.to(UserRepositoryInterceptor.class)) // 交给委托函数
.annotateMethod(AnnotationDescription.Builder.ofType(RpcGatewayMethod.class).define("methodName", "queryData").define("methodDesc", "查询数据").build())
.annotateType(AnnotationDescription.Builder.ofType(RpcGatewayClazz.class).define("alias", "dataApi").define("clazzDesc", "查询数据信息").define("timeOut", 350L).build())
.make();
Byte-buddy
的模板方法,通过核心API;subclass
、name
、method
、intercept
、annotateMethod
、annotateType
的使用构建方法。Repository<T>
,通过 TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build()
来构建。concat
函数是字符串的连接符,替换 +
号。method
,设定匹配处理方法名称。MethodDelegation.to(UserRepositoryInterceptor.class)
,最终的核心是关于委托函数的使用。这里的使用也就可以调用到我们上面定义的委托函数,等最终我们通过字节码生成的 class
类进行查看。annotateMethod
、annotateType
,定义类和方法的注解,通过 define
设定值(可以多次使用)。// 输出类信息到目标文件夹下
dynamicType.saveIn(new File(ApiTest.class.getResource("/").getPath()));
Byte-buddy
提供的 API 方法;saveIn
,把字节码信息写成 class
到执行的文件夹下。这样就可以非常方便的验证通过字节码框架创建的方法内容。「字节码方法内容」
package org.itstack.demo.bytebuddy;
@RpcGatewayClazz(
clazzDesc = "查询数据信息",
alias = "dataApi",
timeOut = 350L
)
public class UserRepository extends Repository<String> {
@RpcGatewayMethod(
methodName = "queryData",
methodDesc = "查询数据"
)
public String queryData(int var1) {
return FindOneInterceptor.intercept(cachedValue$aGmAjHXh$iha1qv0, new Object[]{var1});
}
public UserRepository() {
}
static {
cachedValue$aGmAjHXh$iha1qv0 = Repository.class.getMethod("queryData", Integer.TYPE);
}
}
// 从目标文件夹下加载类信息
Class<Repository<String>> repositoryClass = (Class<Repository<String>>) Class.forName("org.itstack.demo.bytebuddy.UserRepository");
// 获取类注解
RpcGatewayClazz rpcGatewayClazz = repositoryClass.getAnnotation(RpcGatewayClazz.class);
System.out.println("RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz.clazzDesc());
System.out.println("RpcGatewayClazz.alias:" + rpcGatewayClazz.alias());
System.out.println("RpcGatewayClazz.timeOut:" + rpcGatewayClazz.timeOut());
// 获取方法注解
RpcGatewayMethod rpcGatewayMethod = repositoryClass.getMethod("queryData", int.class).getAnnotation(RpcGatewayMethod.class);
System.out.println("RpcGatewayMethod.methodName:" + rpcGatewayMethod.methodName());
System.out.println("RpcGatewayMethod.methodDesc:" + rpcGatewayMethod.methodDesc());
Class.forName
,进行加载类信息。也可以像以前的章节一样使用;unloadedType.load(XXX.class.getClassLoader())
的方式进行直接处理字节码。// 实例化对象
Repository<String> repository = repositoryClass.newInstance();
// 测试输出
System.out.println(repository.queryData(10001));
Class.forName
的方式就可以直接调用方法,如果加载字节码的方式就需要通过反射进行处理(以往章节有案例可以对照学习)。「测试结果」
RpcGatewayClazz.clazzDesc:查询数据信息
RpcGatewayClazz.alias:dataApi
RpcGatewayClazz.timeOut:350
RpcGatewayMethod.methodName:queryData
RpcGatewayMethod.methodDesc:查询数据
小傅哥博客,查询文章数据:https://bugstack.cn/?id=10001
Process finished with exit code 0
委托方法使用
、复杂类型的泛型创建
、类和方法自定义注解的添加
以及写入字节码信息到文件中
。