一次有趣的 Kotlin 语法解析实践

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 一次有趣的 Kotlin 语法解析实践

最近遇到一个业务需求,需要统计业务方提供了哪些能力,这些能力通过一个总的 json 配置文件进行描述,以方便本地和平台都能解析这份配置,配置文件例如:


{
  "components": [
   {
      "dependency": "com.codelang.module:check:1.0.0",
      "name": "zhangsan",
      "verifiedContainer": [
        "list",
        "home"
      ],
      "verifiedProtocol": [
        "public"
      ],
      "version": "1.0.0"
    }
   ]
}
复制代码


最简单的方式就是写一个 json 文件,让各个业务线都来改这份 json 文件,确实是个偷懒的方案,但这有几个缺点:


  • json 这种纯文本文件会导致业务方录入不规范,比如 json key 大小写写错或是拼写单词错误了,导致平台和本地无法解析该字段
  • 业务方不知道哪些 key 是必选的,导致每次都要去看下文档,哪些需要录入
  • 无法知道这么多 key 对应着什么功能,json 里面也无法写注释,导致每次都要去查看文档该 key 表述的是什么意思


那有什么办法解决这些问题呢?我想到了是用注解的方式,对于业务方来说,他们只要按照注解需要的 value 进行录入即可,可选参数用默认值代替,并且还可以注解提示,来看下注解的定义:


annotation class Component(
     // 必选: 模块名称
    val name: String,
     // 必选:模块版本
    val version: String,
     // 必选:模块依赖
    val dependency: String,
    //  可选:校验容器
    val verifiedContainer: Array<String> = arrayOf(),
    //  可选:校验协议
    val verifiedProtocol: Array<String> = arrayOf()
)
复制代码


那么,业务方只需要写一个类,用该注解进行描述即可,例如:


@Component(
    name = "zhangsan",
    version = "1.0.0",
    dependency = "com.aa.bb",
    verifiedContainer = ["list", "homeContainer"],
    verifiedProtocol = ["public"]
)
class AComponent
复制代码


嗯,规范业务方录入这块完成了,那么,怎么将这份注解翻译成 json 文件呢? APT?这也太重了,如果模块新增功能了还要改注解处理器模块,我们只是写一个脚本而已。


之前看过基础部门关于隐私 API 的收集,采用 javaparse 去静态解析 sdk 里面的 sourceCode,如果方法是被 RequiresPermission 注解的话,就给收集起来。


静态解析确实是个好主意,但目前可参考的只有 java,如果业务方是用 kotlin 写的呢?既然有 java 文件解析,那一定就有 kotlin 文件解析,随着搜了一下,查到了三个库:


  • kotlin-parser : 调研发现有点难用,无法根据注解方法的回调遍历注解参数
  • kastree:遍历简单,可以拿到 Node 节点进行向下遍历
  • kotlinx.ast :大而全的 ast 解析库,适配的规则非常多,但使用起来有点重


在简单了解和 demo 测试中,决定使用 kastree 这个轻量级的库来实现,在 README 的描述中,可以写个简单的伪代码:


// 读取 kt 文件内容
val code = File("xx/test.kt").readText()
// 生成解析器
val file = Parser.parseFile(code)
// 开始解析语法
Visitor.visit(file) { v, _ ->
    // v 为 Node 节点
    Log.i("node",v)
}
复制代码


用法非常简单,我们可以尝试解析我们的注解类了,不过,我们得先了解下如果遍历 Node 节点的,我们可以打印输出一下 Node 的结构是什么样的,以下去除了无用的信息,只保留了注解的 Node,如果想查看完全的 log 输出,可查看 demo 的 test.txt 文件,如下代码稍微整理了下结构:


Structured(
  // 注解的类名
  name=App2Component, 
  mods=[
    AnnotationSet(
       target=null, 
       anns=[
           Annotation(
               // 注解类 Component
               names=[Component], 
               typeArgs=[], 
               args=[
                   // 注解参数 name
                   ValueArg(name=name, 
                            asterisk=false, 
                            expr=StringTmpl(
                                // 注解参数 name 对应的值 zhangsan
                                elems=[Regular(str=zhangsan)], 
                                raw=false)), 
                   // 注解参数  version
                   ValueArg(name=version, 
                            asterisk=false, 
                            expr=StringTmpl(
                                 // 注解参数 version 对应的值 1.0.0
                                elems=[Regular(str=1.0.0)], 
                                raw=false)), 
                   // 注解参数  dependency
                   ValueArg(name=dependency, 
                            asterisk=false, 
                            expr=StringTmpl(
                                 // 注解参数 dependency 对应的值 com.aa.bb
                                elems=[Regular(str=com.aa.bb)], 
                                raw=false))])])
  ], 
    ...
)
复制代码


整体 Node 节点跟 json 文件格式很像,每个节点都是一个类型,我们只需要根据节点类型一步步解析出我们要的数据即可,例如:


// 判断 node 节点是否是 Structured
if (v is Node.Decl.Structured) {
   // 取出注解的类名 App2Component
   val className = v.name
   // mods 数组的第一个元素强转成 AnnotationSet 节点
   val annotationSet = (v.mods[0] as Node.Modifier.AnnotationSet)
   // 拿到 Annotation 节点
   val anno = annotationSet.anns[0]
   // 取出注解类名 Component
   val annoName = anns.names[0]
   // 遍历注解的参数值
   anno.args.forEach { node ->
     val expr = node.expr
     if (expr is Node.Expr.StringTmpl) {
        val elems = expr.elems[0]
        if (elems is Node.Expr.StringTmpl.Elem.Regular) {
            // 输出注解参数名称和值
            println("key=" + node.name + " value=" + elems.str)
        } 
     }
   }
   ...
}
复制代码


整体解析非常简单,参数名和值都可以通过遍历的方式拿到,这也即意味着,即使以后模块新增了功能点,只需要动我们的注解类就可以了,脚本完全不需要再改造。


在我们解析拿到了内容之后,那接下来的生成 json 文件就更简单了,我们只需给每个待解析的 kt 文件创建个 JSONObject 节点,然后将解析到的信息都 put 进去,如果有多个文件的话,则创建个 JSONArray,然后将 JSONObject add 进去即可,然后创建个 File,将 JSONArray 转成 string 写入即可。


当然,其中也遇到过坑,比如刚开始集成 kastree  时候,跟着 README 写了下示例,运行直接报错了,有点劝退的感觉:


Exception in thread "main" java.lang.IllegalStateException: LOGGING: Loading modules: [java.se, jdk.accessibility, jdk.attach, jdk.compiler, jdk.dynalink, jdk.httpserver, jdk.jartool, jdk.javadoc, jdk.jconsole, jdk.jdi, jdk.jfr, jdk.jshell, jdk.jsobject, jdk.management, jdk.management.jfr, jdk.naming.ldap, jdk.net, jdk.scripting.nashorn, jdk.sctp, jdk.security.auth, jdk.security.jgss, jdk.unsupported, jdk.unsupported.desktop, jdk.xml.dom, java.base, java.compiler, java.datatransfer, java.desktop, java.xml, java.instrument, java.logging, java.management, java.management.rmi, java.rmi, java.naming, java.net.http, java.prefs, java.scripting, java.security.jgss, java.security.sasl, java.sql, java.transaction.xa, java.sql.rowset, java.xml.crypto, jdk.internal.jvmstat, jdk.management.agent, jdk.jdwp.agent, jdk.internal.ed, jdk.internal.le, jdk.internal.opt] (no MessageCollector configured)
  at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.report(ClasspathRootsResolver.kt:312)
  at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.report$default(ClasspathRootsResolver.kt:310)
  at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.addModularRoots(ClasspathRootsResolver.kt:253)
  at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.computeRoots(ClasspathRootsResolver.kt:123)
  at org.jetbrains.kotlin.cli.jvm.compiler.ClasspathRootsResolver.convertClasspathRoots(ClasspathRootsResolver.kt:79)
  at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:279)
  at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:127)
  at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.createForProduction(KotlinCoreEnvironment.kt:463)
  at kastree.ast.psi.Parser$proj$2.invoke(Parser.kt:16)
  at kastree.ast.psi.Parser$proj$2.invoke(Parser.kt:14)
  at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
  at kastree.ast.psi.Parser.getProj(Parser.kt)
  at kastree.ast.psi.Parser.parsePsiFile(Parser.kt:30)
  at kastree.ast.psi.Parser.parseFile(Parser.kt:23)
  at KtParseKt.parseKotlinFile(KtParse.kt:44)
  at KtParseKt.main(KtParse.kt:27)
复制代码


但仔细看了下日志,觉得可能跟 JDK 版本有关系,尝试将 jdk11 更改成 jdk8 运行,完美运行


总结


最终,我们通过 注解+脚本 的方式,规范了业务方的编码。对于 kt 、java 文件的解析,我们也可以玩出很多花样,比如 findbugs 、lint 等功能。

目录
相关文章
|
1月前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
38 6
|
27天前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
61 1
|
1月前
|
消息中间件 存储 缓存
十万订单每秒热点数据架构优化实践深度解析
【11月更文挑战第20天】随着互联网技术的飞速发展,电子商务平台在高峰时段需要处理海量订单,这对系统的性能、稳定性和扩展性提出了极高的要求。尤其是在“双十一”、“618”等大型促销活动中,每秒需要处理数万甚至数十万笔订单,这对系统的热点数据处理能力构成了严峻挑战。本文将深入探讨如何优化架构以应对每秒十万订单级别的热点数据处理,从历史背景、功能点、业务场景、底层原理以及使用Java模拟示例等多个维度进行剖析。
55 8
|
20天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
115 30
|
20天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
104 14
|
24天前
|
存储 算法
深入解析PID控制算法:从理论到实践的完整指南
前言 大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论学习打下基础。 在很多的自动化控制领域。都会遇到PID控制算法,这种算法具有很好的控制模式,可以让系统具有很好的鲁棒性。 基本介绍 PID 深入理解 (1)闭环控制系统:讲解 PID 之前,我们先解释什么是闭环控制系统。简单说就是一个有输入有输出的系统,输入能影响输出。一般情况下,人们也称输出为反馈,因此也叫闭环反馈控制系统。比如恒温水池,输入就是加热功率,输出就是水温度;比如冷库,
192 15
|
26天前
|
弹性计算 持续交付 API
构建高效后端服务:微服务架构的深度解析与实践
在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
|
20天前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
21天前
|
机器学习/深度学习 搜索推荐 API
淘宝/天猫按图搜索(拍立淘)API的深度解析与应用实践
在数字化时代,电商行业迅速发展,个性化、便捷性和高效性成为消费者新需求。淘宝/天猫推出的拍立淘API,利用图像识别技术,提供精准的购物搜索体验。本文深入探讨其原理、优势、应用场景及实现方法,助力电商技术和用户体验提升。
|
28天前
|
安全 持续交付 Docker
深入理解并实践容器化技术——Docker 深度解析
深入理解并实践容器化技术——Docker 深度解析
54 2

热门文章

最新文章

推荐镜像

更多