
在上一篇Javassist 自动化生成实体类:让 Debezium 数据解析更高效 文章中,我们通过 javassist 来实现自动创建实体类,但是当我想要将创建的 RowData 类放入到 DebeziumEvent 的时候,才想起来我们没有改变运行中的类。

那么所以,我们也要动态生成 Debezium 这个类...在本篇文章中,我们用到了反射,为啥要用到反射呢。因为我们使用的 javassist 生成了 CtClass,但是熟悉 jvm 的同学就会知道,要想使用类,就要使用类加载器加载到 jvm 中,说起这个就让我想起来了 双亲委托模型 等等知识点。言归正传,反射的作用就是获取 defineClass,将 ctClass 加载到 JVM 中。
一切的一切都是为了 Gson 解析准备的,所以先从解析开始:
gson.fromJson(record.value(), debeziumEvent);debeziumEvent 是一个 class 类型,所以我们需要构建这个 debeziumEvent.class,同样使用 javassist 生成,然后使用反射加载。
public static Class<?> generateDebeziumEventClass(Class<?> rowDataClass) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.test.debezium.DebeziumEventDynamic");
// 添加 before 字段
CtField beforeField = new CtField(pool.get(rowDataClass.getName()), "before", ctClass);
beforeField.setModifiers(Modifier.PUBLIC);
ctClass.addField(beforeField);
// 添加 after 字段
CtField afterField = new CtField(pool.get(rowDataClass.getName()), "after", ctClass);
afterField.setModifiers(Modifier.PUBLIC);
ctClass.addField(afterField);
// 添加 op 字段
ctClass.addField(CtField.make("public String op;", ctClass));
ctClass.addField(CtField.make("public long ts_ms;", ctClass));
// 加载类
byte[] bytes = ctClass.toBytecode();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
return (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), ctClass.getName(), bytes, 0, bytes.length);
}我们看到通过 getDeclaredMethod 反射获取了 defineClass 这个方法,defineClass 就是用来加载类的。同时,这里需要一个 Class 类型的 rowDataClass 参数,所以我们就修改一下上一篇文章生成 ctClass 的代码,也让其返回一个 Class。
public static Class<?> generateClass(String className, String fields, String connector) {
ClassPool pool = ClassPool.getDefault();
// 初始化CtClas
CtClass ctClass = pool.makeClass(className);
for (String field : fields.split(",")) {
// 添加字段
try {
ctClass.addField(CtField.make("private String " + field + ";", ctClass));
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
// 添加toString方法
try {
ctClass.addMethod(CtMethod.make("public String toString() { String result = " + fields.replace(",", "+ \"" + connector + "\" +") + "; return result;}", ctClass));
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
byte[] byteClass;
Class rowDataClass = null;
try {
byteClass = ctClass.toBytecode();
// 反射
Class<?> clazz = Class.forName("java.lang.ClassLoader");
Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
rowDataClass = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), className, byteClass, 0, byteClass.length);
} catch (Exception e) {
e.printStackTrace();
}
return rowDataClass;
}这样我们就有了一个 rowDataClass,然后就开始回到 gson 解析,开始构造 debeziumEvent 这个参数。
Class debeziumEvent = generateDebeziumEventClass(generateClass("RowData", "id,name,age,update_at", "|"));我们执行程序,可以看到 a 这个变量被 gson 解析成了我们自定义的类。

但是我们没有办法明确指定 a 的类型为 DebeziumEvent,在解析的时候只能使用 Object 来接收,否则就会报错。
Object a = gson.fromJson(record.value(), debeziumEvent);因为我们使用的 Debezium 的版本必须大于 JDK17,但是 JDK17移除部分反射 API 的默认访问权限,所以如果我们直接运行程序就会报错:

所以我们就要添加 JVM 参数开放模块,在 IDEA 的启动配置中点击 Modify options 找到 Add VM options。

添加 VM 参数开发模块:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens jdk.internal.loader=ALL-UNNAMED然后保存即可正常运行:

本篇文章主要通过反射获取类加载方法,然后实现动态类的创建。但是它的弊端就是,DebeziumEvent 因为是通过反射加载的类,所以也没法使用它的具体类型,如果想要获取其中的私有变量和方法,也只能通过反射。通常这种方案适合直接调用toString()将json转换成csv的场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。