前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我攻克的技术难题 - 我宣布,Java Json再也不用定义实体类了

我攻克的技术难题 - 我宣布,Java Json再也不用定义实体类了

原创
作者头像
叫我阿柒啊
发布2024-02-03 02:22:08
3600
发布2024-02-03 02:22:08
举报
文章被收录于专栏:Java放弃之路入门到放弃之路

前言

之前有一篇文章讲了Java的Gson、FastJson等解析json常用类,与Python的json模块比较,繁琐之处是要定义各种实体类。那么,Java中有没有自动定义实体类的方法呢?

数据接入是我在大数据工作中的一部分,定长、csv、json是比较常见的几种数据格式。通常我都是使用Flume来完成数据的接入,根据对端数据源配置source,在数据源配置Interceptor(拦截器),将channel设置为kafka(通常是memory,为了流计算所以放在kafka),sink设置成文件等待后续调度入库。

Flume提供的的source、channel、sink基本可以覆盖日常需求,通过conf配置就可以轻松使用。而Interceptor很多时候都需要自己开发打包,放到Flume的lib中,然后在conf配置中绑定在source中,这样就能在进入channel之前对数据进行处理。

随着接入的Json数据越来越多,每接入一种格式的json,都要定义一个实体类,然后定义一个Interceptor,来将Json解析成CSV。

后来有一天就想着,能不能开发一个适配性Json的Interceptor,在配置文件中配置字段名称,就自动生成实体类,然后自动在Gson中引入这个实体类,对json数据进行解析。

javassist

说起自动生成类,就想起了我在2017年自学Java时学到的javassist类。

javassist提供了动态生成class的功能,接下来就看看如何使用javassist来创建一个类。

代码语言:xml
复制
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.29.2-GA</version>
</dependency>

CtClass

既然是创建class,那么一定会有个类,来表示正在构建的class对象,这样才能对class进行添加字段和方法等操作,这个类就是CtClass,即compile-time class,编译时class文件。

接着就是看看如何都创建CtClass对象。

从注释中看,CtClass是从ClassPool中获取。

ClassPool

ClassPool就是存放被JVM加载后class的容器,我们通常使用ClassPool.getDefault() 来获取CLASSPATH的class。

代码语言:java
复制
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("java.lang.String");
System.out.println(ctClass);

我们来获取String的CtClass。

可以看到String的字段被封装成CtMethod类型,constructor封装成CTConstructor,同样字段会被封装成CtField

构建实体类

接下来就是构建一个json数据对应的实体类。调用CtPool的makeClass() 方法,就可以新建一个空的CtClass,然后添加字段和方法。

Flume主要是从外部配置参数,然后通过脚本启动,所以我将json实体类的字段名称都通过参数配置。日常开发中比较常见的json主要有两种格式:

  1. 简单类型,都是一个字段对应着一个简单类型。
  2. 复杂类型,某些字段对应的是一个自定义类数据,或者是一个List

这里就先写简单类型的实体类如何构建。

制定规范

先根据要处理的数据定制规范。

代码语言:java
复制
String json = "{\"name\": \"Tom\", \"age\": \"10\", \"time\": \"2024\" }";
String className = "A";
String fields = "name,age,time";
String connector = "|";

代码中json是要处理的json数据,className、fields、connector是外部传入的参数,分别代表着要创建类的名称、字段和toString方法的连接符。

构建ctClass

根据上面讲的方法,在初始化了ctClass之后,就要添加字段和toString

代码语言:java
复制
public static CtClass generateClass(String className, String fields, String connector) throws CannotCompileException {
    ClassPool pool = ClassPool.getDefault();
    // 初始化CtClas
    CtClass ctClass = pool.makeClass(className);
    for (String field : fields.split(",")) {
        // 添加字段
        ctClass.addField(CtField.make("private String " + field + ";", ctClass));
    }
    // 添加toString方法
    ctClass.addMethod(CtMethod.make("public String toString() { String result = " + fields.replace(",", "+ \"" + connector + "\" +") + "; return result;}", ctClass));
    return ctClass;
}

通过ctClass.addField添加字段,参数类型是CtField,通过CtField.make可以直接构建。分割之后遍历传入的fields,直接将字段添加到ctClass中,这里我直接默认为String类型,如果想做其他类型,可以对传入参数精准到数据类型。

因为结果要输出csv格式,所以最后一定要添加toString方法,来定义输出的格式。使用addMethod就可以直接添加一个toString方法。connector是每个字段的分隔符,通过对fields.replace将name,age,time换成了 name + "|" + age + "|" + time,用于构建toString代码。

最后返回构建完成的ctClass。

loadClass

这里以Gson为例,在调用fromJson解析json串的时候,第一个参数是json字符串,第二个参数是Class对象。

如何能通过CtClass获取到Class,这里肯定首先想到类加载器ClassLoader,在Class源码注释中,也提供了这个方法。

JVM使用ClassLoder.defineClass,将编译后的字节形式的class文件加载构建成Class对象。而Ctclass中的toBytecode就可以将ctClass转换成class文件。

所以这里就定义一个loadClass方法,来生成Class。

代码语言:java
复制
public  static Class loadClass(CtClass ctClass, String className) {
    Class c = null;
    byte[] byteClass;
    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);
        c = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), className, byteClass, 0, byteClass.length);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return c;
}

因为ClassLoder.defineClass是非public方法,所以我们只能通过反射来调用这个方法,最后生成一个class对象。

这样,Gson需要的Class参数也生成了,现在进行测试。

解析json

对上面定义的json进行测试:

代码语言:java
复制
String json = "{\"name\": \"Tom\", \"age\": \"10\", \"time\": \"2024\" }";
String className = "A";
String fields = "name,age,time";
String connector = "|";
CtClass ctClass = generateClass(className, fields, connector);
Gson gson = new Gson();
Object o = gson.fromJson(json, loadClass(ctClass, className));
System.out.println(o);

结果输出:

更换一个其他字段的json进行测试:

代码语言:java
复制
String json = "{\"a\": \"11\", \"b\": \"10\", \"c\": \"22\", \"d\": \"22\"}";
String className = "A";
String fields = "a,b,c,d,e";
String connector = ",";
CtClass ctClass = generateClass(className, fields, connector);
Gson gson = new Gson();
Object o = gson.fromJson(json, loadClass(ctClass, className));
System.out.println(o);

结果输出:

经过测试,对于简单类型的json可以做到配置化解析。

结语

对于复杂类型json解析配置化解析,后面也可以写一写。我在开发list类型的json解析类时,使用javassist就遇到了编译时问题,后面在研究一下吧。

当然,我在18年开始使用flume,不仅仅是做一些数据接入,也做过大数据量的应用场景,有着使用flume从Kafka落地到HDFS超过8000亿条/天(存储500T/天)的实践,所以还略有一些心得,所以对flume有兴趣的可以一起学习!

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • javassist
    • CtClass
      • ClassPool
      • 构建实体类
        • 制定规范
          • 构建ctClass
            • loadClass
              • 解析json
              • 结语
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档