干货 | 实现一个属于你的“语言”-携程Kotlin DSL开发与实践

探索 Kotlin 协程原理 接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解。 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上图所示,这个流程是各种类型的协程执行时都大致遵循的流程,不是一个严格精确的执行流程。 下面先来看下协程执行过程中的一些关键类的介绍。 1. 协程代码块 当我们调用 launch() 或 withContext() 等协程构建器函数时,对应的协程代码块会被 Kotlin 编译器转换为一个匿名内部类,这个匿名内部 阅读详情

作者简介

刘媛,携程金融高级开发工程师,主要负责中文版、国际版支付Android端的开发及维护工作。

每一个DSL,都是一定意义上专有的语言,这篇文章希望能够用浅显易懂的方式,将Kotlin DSL的应用与实践经验分享给大家。希望对你有所启发,能够构建一门属于自己的专有“语言”。

一、简介

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言。由于它是以简洁的形式进行表达,整体上直观易懂,使得调用代码和读代码的成本都得以降低,即使是不懂编程语言的一般人都可以进行使用,所以近年来频频被提起,颇受关注。

DSL分为外部DSL和内部DSL。

DSL:在主程序设计语言之外,用一种单独的语言表示领域专有语言。可以是定制语法,或者遵循另外一种语法,如XML、JSON。

内部DSL:通常是基于通用编程语言实现,具有特定的风格,如 iOS 的依赖管理组件 CocoaPods 和 Android 的主流编译工具 Gradle。

这里主要分享在Kotlin中构建使用DSL。


二、应用

Kotlin DSL的应用广泛,包括gradle编写、编写js、html、SQL等。下面列举几个使用场景:

2.1 Trip.com支付网络封装实践

在编写网络代码时,出现频率最高的就是request配置和大篇幅的response回调处理,那么这两部分的代码该如何优化?在Trip.com支付中利用kotlin DSL对网络进行二次封装,针对以上问题进行解决。

定义request配置,使得最终在做request配置时更为简洁:

fun requestBean(request: () -> BusinessBean) {    payClientBuilder.setRequestBean(request())}fun needRetry(needRetry: () -> Boolean) {    payClientBuilder.setNeedRetry(needRetry())}......

定义回调模版,解决以下问题:部分网络请求,我们不关心结果,或者不关心onFailed的场景,避免掉这部分的冗余代码:

private var callSubSuccess: ((T) -> Unit)? = nullprivate var callSubFailed: ((Client.Error?) -> Unit)? = nullprivate var subCallback: PayNetCallback<T> = object : PayNetCallback<T> {    override fun onSucceed(response: T) {        callSubSuccess?.invoke(response)    }
override fun onFailed(error: Client.Error?) { callSubFailed?.invoke(error) }}fun subSuccess(subSuccess: (T) -> Unit) { callSubSuccess = subSuccess payClientBuilder.setSubCallBack(subCallback)}
fun subFailed(subFailed: (Client.Error?) -> Unit) { callSubFailed = subFailed payClientBuilder.setSubCallBack(subCallback)}

预定义扩展函数

object PayNetworkClient {    fun <T : BusinessBean> init(        costClass: Class<T>,        config: PayClientConfigBuilder<T>.() -> Unit    ): PayClientBuilder.NetworkClient? {        var networkClient: payClientBuilder.NetworkClient?        with(PayClientConfigBuilder(costClass)) {            networkClient = build(config)        }        return networkClient    }}

最终调用

val networkClient = PayNetworkClient.init(BusinessResponse::class.java) {    //配置部分    requestBean { request }    needRetry { false }    cancelOtherSession { "sendGetPayInfo" }    //回调部分,可根据需求添加subSuccess或subFailed    subSuccess { serviceSuccess(it) }    subFailed { serviceFailed(it) }}networkClient?.send()

在定义DSL的过程中需要权衡冗余度、自由度、可扩展性。上面给出的伪代码消除了重复的模版代码,减少代码冗余,同时也做到自由选择配置项,有一定的自由度和可扩展性。

2.2 海外支付SDK DSL构建项目实践

众所周知Android studio中是使用groovy编写gradle脚本,而groovy由于是动态语言,不可避免的存在一个问题,就是代码提示不够智能,我们在使用groovy时往往需要配合文档进行编写;而kotlin是一种静态语言,使用它编写gradle脚本则可以有比较好的智能提示体验。

在Gradle5.0中,官方提供可以选择在项目中生成Groovy或者kotlin DSL构建脚本,并进一步的优化代码自动完成、重构和其他 IDE 辅助功能,为使用Kotlin DSL的 IDE 用户带来了极大的便利。

可见gradle官方也在努力将kotlin DSL推向大家视野中。

在我们最近的海外支付SDK中,采用该种方式构建项目, 部分gradle代码如下:

import org.jetbrains.kotlin.config.KotlinCompilerVersion
plugins { id("com.android.application") kotlin("android") kotlin("android.extensions")}
android { compileSdkVersion(28) defaultConfig { applicationId = "trip.pay.app" minSdkVersion(21) targetSdkVersion(28) versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } }}
dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) implementation("com.android.support:appcompat-v7:28.0.0") implementation("com.android.support.constraint:constraint-layout:1.1.3") implementation(project(":TripPay"))}
repositories { mavenLocal() maven(url = "maven地址")}

可以看到使用kotlin编写和groovy编写区别不大,所以即使我们要将现有工程中的groovy脚本重写为kotlin脚本,工作量也不会过大。

以上种种都表明Kotlin DSL相对于groovy的优势非常明显,那么我们是不是应该立马开始改造现有的项目?


答案是“否”,因为它目前存在一个致命的缺陷,在首次编译项目时比groovy DSL慢很多,大项目中这一点会被放大,所以大家在上手之前需要慎重权衡利弊。

目前我们在海外支付SDK中利用kotlin DSL构建大约在17s,利用groovy DSL构建大约在16s,时间上来说几乎没有区别,所以小型项目推荐尝试使用!

相信在不久的未来kotlin DSL可以解决这个问题,那么利用kotlin DSL构建项目势必会成为趋势。

2.3 Anko

Anko库包括Anko Commons、Anko Layouts、Anko SQLite、Anko Coroutines,这些都是使用kotlin DSL编写,这里主要介绍Anko Layouts。


在写Android布局时,我们都习惯性的使用XML进行编写,但是可以考虑丢下冗长的XML写法,尝试使用Anko Layout来实现。

XML写法:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">
<EditText android:id="@+id/todo_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/main_edit_hint" />
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/main_button_text" />
</LinearLayout>

Anko Layout写法:

verticalLayout {    setGravity(Gravity.CENTER_VERTICAL)    editText {        hintResource = R.string.main_edit_hint         }.lparams(width = matchParent, height = wrapContent)    button {        textResource = R.string.main_button_text        onClick {            toast("click!")        }    }.lparams(width = matchParent, height = wrapContent)}

实际上前文提到过,XML本质上也是一种DSL,但是明显使用Anko Layout风格更加简单、也更加灵活。


XML编写后,我们需要findViewById找到控件,再对控件进行操作、赋值;Anko Layout编写过程中,可以在布局中就直接做显示隐藏、赋值操作等,同时这种写法也有类型安全、空安全、代码复用性强的优势。

Anko Layout由于是直接在kt文件中编写控件,那么它相对于xml来说,还有一个优势,即:减少了XML格式的解析过程,从而实现CPU资源和电量的节省。

XML的执行流程:

Anko Layout执行流程:

Anko库实际上是用kotlin对相关类做了一层扩展包装,基于这一点,它的局限性也体现在于会增加包大小,在使用之前可以根据项目评估一下是否适合引入Anko库。


2.4、创建一个自己的DSL

Kotlin DSl的优势这么多,那么如何自定义一个DSL?


kotlin的扩展函数、高阶函数、lambda表达式、中缀调用、invoke 约定和函数小括号省略等特性,使得Kotlin编写DSL尤为顺畅,我们可以使用这些特性来实现自己的“领域特定语言”。这里给一个简单的示例:

定义Trip、Department类

data class Trip(var name: String? = "", var address: String? = "", var departments: List<Department>? = mutableListOf(), var city: List<String>? = mutableListOf(), var culture: String? = "")
data class Department(var name: String = "", var nameEn: String = "")

定义中间类,主要是为了实现直接DSL方式添加department的效果

class TripBuilder {        var name: String? = ""        var address: String? = ""        var departments = mutableListOf<Department>()
fun department(block: DepartmentBuilder.() -> Unit) {// 简单的写法departments.add(DepartmentBuilder().apply(block).build())即可// 演示invoke实现 val departmentBuilder = DepartmentBuilder() block.invoke(departmentBuilder) departments.add(departmentBuilder.build()) }
fun build(): Trip = Trip(name, address, departments) }
class DepartmentBuilder { var name: String = "" var nameEn: String = ""
fun build(): Department = Department(name, nameEn)}

创建trip的DSL写法

fun trip(block: TripBuilder.() -> Unit): Trip = TripBuilder().apply(block).build()

实现中缀culture方法(只为了演示所用,实际上可以直接赋值)

infix fun Trip.culture(culture: String) {    this.culture = culture}

最终调用效果:

val trip = trip {    name = "Trip"    address = "上海市长宁区金钟路968号凌空SOHO"    department {        name = "机票"        nameEn = "flight"    }    department {        name = "酒店"       nameEn = "hotel"    }    department {        name = "火车票"        nameEn = "train"    }}trip culture "Customer、Teamwork、Respect、Integrity、Partner"Log.i("result",trip)

result结果:

Trip(name=Trip, address=上海市长宁区金钟路968号凌空SOHO, departments=[Department(name=机票, nameEn=flight), Department(name=酒店, nameEn=hotel), Department(name=火车票, nameEn=train)], culture=Customer、Teamwork、Respect、Integrity、Partner)

一个简单的Kotlin DSL就这样实现了,通过封装成结构化的 API 达到了直观易懂、最终调用时代码量减少的效果。即使是一个非kotlin开发人员也可以理解以上格式的含义,完成“Trip”对象的配置使用。

三、写在最后

1)Kotlin编写完的DSL整体简洁直观,调用代码和读代码的成本都得以降低,在生产项目中可以稳定使用。

2)DSL是通过简化语言中的元素,降低使用者的负担,使用者需要按照既定的规范进行编写。所以我们需要提供完善使用文档,以保证接入者学习成本降低。

3)在我们编写的DSL应用范围越来越大时,已有DSL往往满足不了现有的需求,我们仍然需要对DSL进行补充,所以在定义自己的DSL时需要评估后期开发维护效率,注意其可扩展性。

【推荐阅读】

Kotlin协程详解(学习协程这一篇就够了) 一、什么是协程协程是一种优雅处理异步任务的解决方案 ,可以理解为是Kotlin对「线程」和「Handler」 API 的一种封装形式。它可以在不同的线程间来回切换,这样就可以让代码通过编写的顺序来执行,并且不会阻塞当前线程,省去了在各种耗时操作写回调的情形。在 JVM 平台上,Kotlin 协程是无栈协程,所谓无栈协程,是指协程在 suspend 状态时不需要保存调用栈。 阅读详情

相关推荐

揭秘Kotlin编程精华,讲干货、不废话:《Kotlin从入门到进阶实战》 阿里巴巴资深程序员力作...

《Kotlin从入门到进阶实战》 前言 Kotlin是一门非研究性的语言,它是一门非常务实的工业级编程语言,它的使命就是帮助程序员们解决实际工程实践中的问题。使用Kotlin 让 Java程序员们的生活变得更好,Java中的那些空指针错误,浪费时间的冗长的样板代码,啰嗦的语法限制等等,在Kotlin中统统消失。Kotlin 简单务实,语法简洁而强大,安全且表达力强,极富生产力。 Java诞生于19...

AI天才研究院 9374

一文看透 Kotlin 协程本质

前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果……实在拿不出手! 为了更好的加深记忆和理解,更全面系统深入地学习 Kotlin 协程的知识,协程将分为三部分来讲解,本文是第一篇: Kotlin 协程实战进阶(基础篇) Kotlin 协程实战进阶(进阶篇) Kotlin 协程实战进阶(高级篇) 本文大纲 一、概述 协程的概念在1958年就开始出现(比线程还早), 目前很多语言开始原生支, Java

https://juejin.cn/user/1654096907477549/posts 7729

保姆级教程:用QSTEM和DrProbe搞定你的第一张STEM模拟图(附Fe3O4实例文件)

本文提供了一份详细的STEM模拟图像生成教程,涵盖从晶体结构获取到最终图像模拟的全过程。通过QSTEM和DrProbe软件的操作指南,帮助材料科学研究者快速掌握STEM模拟技术,特别适合初学者。教程包含Fe3O4实例文件,详细解析参数设置、计算优化和结果分析等关键步骤。

weixin_33730836的博客 523

Kotlin学习(十九)—— 携程

协程的引入 注意:kotlin中的协程在1.1中还是实验性的(小编理解为,先不要用) ⼀些 API 启动⻓时间运⾏的操作(例如⽹络 IO、⽂件 IO、CPU 或 GPU 密集型任务等),并要求调⽤者阻塞直到它们完成。协程提供了⼀种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的⽅法:协程挂起 协程通过将复杂性放⼊库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异...

a907691592的博客 5810

【全开源】旅行吧旅游门票预订系统源码(FastAdmin+ThinkPHP+Uniapp)

此外,系统还会根据我的旅行计划和喜好,为我推荐合适的景点和门票,让我的旅行更加丰富多彩。旅游门票预订系统的出现,不仅简化了门票预订的流程,还提高了旅行的便捷性和效率。一款基于FastAdmin+ThinkPHP+Uniapp开发的旅游门票预订系统,支持景点门票、导游产品便捷预订、美食打卡、景点分享、旅游笔记分享等综合系统,提供前后台无加密源码,支持私有化部署。如今,有了旅游门票预订系统,这些问题都能迎刃而解。智能推荐:系统还会根据你的旅行计划和喜好,为你推荐合适的景点和门票,让你的旅行更加丰富多彩。

2401_84413757的博客 1301

Jetson Nano 【1】入手开箱、烧系统

类似于半开箱哈哈 怀着小期待的心情,屁颠屁颠跑到村口领快递,拿到了这个香喷喷的Jetson Nano 开发板 开箱: 由于太激动了忘记拍点啥,于是在最后留了张全家福 板子的结构: 我觉得这个有必要记录一下,于是去官方文档拿了张图来:官方文档传送 1=SD card 的卡槽,位于第二层板子的背部,也就是大风扇屁股位置 2=40针扩展接头(还没开用) 3=USB的供电口,根据大佬测试(某哩...

椰子奶糖的博客 4194

Kotlin 协程的基本概念及用法

在 Kotlin 中,协程就是基于线程来实现的一种更上层的工具 API,类似于 Android 自带的 Handler 系列 API。使用了协程的代码看似阻塞,但由于协程内部做了很多工作(包括自动切换线程),它实际上是非阻塞的。此处的 launch 方法含义是:创建一个新的协程,并在指定的线程上运行它。传给 launch 方法的连续代码段就被叫做一个协程,传给 launch 方法的方法参数可以用于指定执行这段代码的线程。协程在写法上和普通的顺序代码类似,可以让开发者用同步的方式写出异步的代码。

金戈鐡馬 4901

软件测试面试题之易错题

(1)白盒测试:又称功能功能 测试、数据驱动测试和行为测试,是一种从用户观点出发的测试,被测程序为黑盒子, 通过测试来检测每个功能是否能够正常使用。(2)黑盒测试:又称结构测试和逻辑驱动 测试,是知道产品内部工作过程,通过测试来检测产品内部动作是否按照规格说明书 的规定去正常运行。(1)、测试需求分析;(2)动态白盒测试:需要对各个模块功能、模块间的接口、局部数据结构、主要执行路径、 错误处理等方面的测试。(2) 集成测试所测试的对象是模块间的接口,其目的是在找出在模块接口上面,包括整体体系结构上的问题;

m0_37449634的博客 678

干货 | 携程机票 App Kotlin Multiplatform 初探

作者简介陈琦,携程机票研发部无线研发总监,负责携程App机票业务的技术研发和管理工作。从2017年9月到2019年5月,经过一年半的努力,携程机票App团队完成 90% ...

携程技术中心 3292

Sparse-ICP—(4) 加权稀疏迭代最近点算法(matlab版)

本文介绍了加权稀疏迭代最近点算法的实现。

点云侠的博客 642

将指定文件夹下每张用LabelImg标注的图片中标注部分抠出来保存到指定文件夹下。

将指定文件夹下每张用LabelImg标注的图片中标注部分抠出来保存到指定文件夹下。

weixin_44119674的博客 440

Kotlin将继续助力Android开发,作为正在开发的你了解过哪些,附Android面经

四、高阶函数 高阶函数的基本概念 常见高阶函数(forEach,map,flatmap,fold,reduce,filter,takeWhile,let,apply,with,use) 尾递归优化 (tailrec) 闭包(函数式编程的福音) 函数复合(f(g(x)),infix) 柯里化(Currying)-函数调用链 偏函数 五、Lambdas编程 lambdas简介 lambdas和集合 lambda表达式 访问作用域中的变量 成员引用 集合的函数式API之filter和map all、any、

m0_66264938的博客 1945

Nvidia Jetson 编解码开发(3)解决H265解码报错“PPS id out of range”

基于之前的开发程序通过Jetson Xavier NX 硬编码的H265发出后,上位机断点播放发出来的H265码流,会报“PPS id out of range” 错误。

jzwjzw19900922的博客 3810

我是如何使用Kotlin优化项目代码(1)

我是如何使用Kotlin优化项目代码(1) 这篇代码中全都是干货,需要读者具有一定的Kotlin基础,了解Kotlin中扩展函数、高阶函数、委托、DSL等特性,当然如果看的不是太明白,也可以拿来直接使用哈!~~ 推荐下好文会更有利于对于本文的理解: https://juejin.cn/post/6844904038589267982 一.快捷单例的实现 object声明单例 object Demo1{ } 可以看到声明一个单例一行代码就可以搞定,反编译成Java代码看一看: public fina

ssAfterHard的博客 480

Quartus II编译时出现Error (119013): Current license file does not support the EP4CE6E22C6 device

Quartus II编译时出现Error (119013): Current license file does not support the EP4CE6E22C6 device, Quartus的安装过程不同于ISE/vivado的安装。“Current license file does not support”这种情况多出现在高版本的quartus中综合很久以前的代码,使用很早型号的FPGA或是CPLD。对于这种情况,就得在官网或是其他网站上下载包含所需Device的库文件。

夜半少年的博客 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

adapter为null_Kotlin扩展函数 + DSL 封装Adapter,是时候提高你撸RecycleView的效率了!...

点击上方“技术最TOP”,星标公众号重磅干货,第一时间送达作者:L_Xian, 链接:https://juejin.im/post/5effe30e6fb9a07ea10bc869前言在项目开发中,总离不开列表,说到列表,就会有无穷无尽的 Adapter 需要你去实现。从而出现了很多优秀的 adapter 库。不过很多库功能都很全面了,有很多个类,很多功能,但是实际上只用其中的一两个我感...

weixin_30481539的博客 334

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Kotlin开发实战:高频痛点解析与避坑指南

在软件开发中,构建工具和语言特性的正确使用是保障项目稳定性的基础。Gradle作为主流的构建工具,其版本管理与依赖配置直接影响编译结果;而Kotlin作为现代编程语言,其协程、空安全等特性在提升开发效率的同时,也带来了新的挑战。理解Gradle的依赖解析机制和Kotlin协程的结构化并发原理,对于构建可靠应用具有重要技术价值。在实际开发中,尤其是在多模块项目或与Java生态互操作时,版本冲突、协程泄漏等问题频发。本文聚焦于Kotlin开发中的高频痛点,如元数据版本不兼容、协程泄漏等,提供从根因分析到一劳永逸

weixin_34062469的博客 300

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

干货拿走:深入解析Kotlin 泛型

0. 引子 Kotlin 100% 与 Java 兼容,所以抛开语言表面上面的种种特质之外,背后的语言逻辑或者说“灵魂”与 Java 总是想通的。本文只涉及 Kotlin Jvm,Kotlin Js、Kotlin Native 的具体实现可能有差异。 最近一段时间在网上发了一套 Kotlin 的入门视频,涵盖了基础语法、面向对象、高阶函数、DSL、协程等比较有特色的知识点,不过有朋友提出了疑问:这门课为什么不专门讲讲泛型、反射和注解呢? 我最早听到这个问题的时候,反应比较懵逼,因为我居然没有感觉到 K

粉丝交流群:569545109 670

Kotlin 协程实现原理

目录 前言 先从线程谈起 设计思想 CPS 变换 续体与续体拦截器 状态机 标准库 实现细节 Main 调度器 Default 以及 IO 调度器的线程池配置 前言 在文章正式上干货之前,先说一点背景吧;我是 Kotlin 协程官方文档的译者,大家在 Kotlin 中文官网上看到的绝大多数协程的中文官方文档都是我翻译的。 官方文档可以说是比较全面的介绍了协程的使用,但是就我的感觉来说,这些文档分布的比较散乱,甚至还有三篇分布在协程的官方 Github 的 pro

chuyouyinghe的专栏 1147

索然无味!Kotlin开发从入门到上天,一篇文章就搞定了!(万字长文)

标题党? 看起来可能有点标题党的意思,但我知道,不这样,你们可能看不到这篇。 关于Kotlin相关记录,如果有意查看我的github,其超10w字(其中8w是代码吗,哈哈)。。 为什么要学? 在不牺牲性能或安全性的前提下,许多的Kotlin功能使代码比Java更加简洁易懂。 Kotlin编译为字节码,因此其性能与Java一样好。它具有与Java相同的编译时检查(还有更多内容,例如内置的可空性检查)。最重要的是,Kotlin的语言功能和标准库功能可实现简洁有效的代码。 简洁,因为这是提高程序员工作效率的关.

Androiddddd的博客 2216

技术沙龙 | Kotlin 我们一路走来

&#13; &#13; &#13; &#13; &#13; &#13; &#13; 谷歌宣布,将 Kotlin 语言作为安卓开发的一级编程语言,而且从 Android Studio 3...

技术实验室 297

【Bugly 技术干货】Android开发必备知识:为什么说Kotlin值得一试

为什么80%的码农都做不了架构师?>>> ...

weixin_34268310的博客 127

Gradle与Maven深度对比:从构建工具到构建框架的进阶实践

构建工具是软件工程的基础设施,Maven 以固定的生命周期和插件机制著称,而 Gradle 则基于任务依赖图,成为更灵活、可高度定制的构建框架。理解这一本质差异,才能摆脱“Gradle 只是替代品”的认知误区。在实际工程中,环境配置、国内镜像加速、依赖管理、多模块构建等环节直接决定工具链的落地效果。本文从构建工具的核心概念讲起,深入解析 Gradle 与 Maven 的设计差异,梳理 Groovy DSL 语法、implementation 与 api 的作用范围、Spring Boot 与 Android

weixin_42531923的博客 210
上一篇: 干货 | 千人千面营销系统在携程金融支付的实践
下一篇: 干货 | 携程 Trip.com App 首页动态化探索
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值