事务隔离级别
如果您在遥远的村庄中远程工作,在飞机上飞行了8个小时,或者公司的全球网络访问受到限制,如何为舒适的开发过程构建实际的框架和库堆栈?您不能使用公共文物存储库,例如Maven Central? 我们如何交付我们所依赖的所有必要工件,包括传递依赖? 如何使用新的框架版本更新它们?
让我们尝试找到这些问题的答案。 在本文中,我将向您介绍CUBA SDK –一种命令行工具,该工具可以解决Maven库的所有传递依赖关系并在远程存储库中对其进行管理。 另外,我们将概述该示例,该示例可以帮助您将最佳实践用于使用Maven依赖项的任何Java应用程序。
如您所知,我们的团队正在研究CUBA –用于企业开发的开源Java框架。 CUBA平台是一个完整的生态系统,由框架和提供附加功能的各种附加组件组成。 几下单击即可使用此功能。 在过去的几年中,该框架获得了巨大的普及。 现在,全世界有超过2万名开发人员在使用它。 随着受欢迎程度的增长,我们遇到了许多有趣的案例。 本文将重点介绍其中之一。 可能对您的项目有帮助,特别是如果您在拥有严格安全系统的公司中工作。
将传递依赖项加载到内部存储库
几乎所有使用Apache Maven或Gradle作为构建工具的现代JVM项目,对于正在使用的每个i库和/或框架都具有大量的传递依赖项。 通过网络可访问的共享存储库,这些依赖关系将由依赖关系管理器自动解决。
但是,如果内部网络无法访问公共存储库,我们该怎么办?
解决方案选项
解决方案似乎很简单–我们仅采用Nexus并通过它代理对存储库的访问。 确实,在大多数情况下,这是可行的,但如果您有特别严格的安全要求,则不行。 您可以在特定类型的公司中面对它们:军事,金融,政府部门和其他政府机构。 此类公司对互联网的访问受到严格的监管,通常不允许传统的代理方式。
我们该怎么办?
- 选项0。请求安全团队。
- 选项1.网关。
- 选项2.手动依赖性管理。
没有考虑选项0,让我们考虑选项1和2。
网关选项表示公司拥有网关计算机,该计算机可以连接到外部或内部网络。 从外部网络加载并检查必要的库之后,网关将切换到内部网络,并且只有在这些库被加载到内部存储库之后。 为单个项目添加新的依赖项或更新现有的依赖项通常会花费很多时间,因为您必须访问网关,加载必要的工件,检查所有可传递的依赖项是否已正确加载,然后将依赖项检出到内部存储库。 如果公司中有许多项目,则在从内部存储库访问所需的依赖项之前,工作流程可能会处于闲置状态。
考虑到具有手动依赖项管理的选项,每次更新或添加组件时,都必须检查库依赖项列表并将其与已加载的库进行比较。
如我们所见,添加或更新依赖项可能是一个相当复杂的过程。
如果您不考虑公司安全系统,则存储库访问问题仍然存在。 例如,开发人员将在互联网连接薄弱的偏远乡村工作。 为了准备这种情况,您可以始终尝试使用Gradle或Maven的脱机插件。 但是,如果您有多个项目,则每个项目都必须
- 配置离线插件
- 将所有可能有用的依赖项添加到项目中,以便脱机插件可以将它们正确添加到依赖项缓存中,以防万一
- 将所有依赖项下载到本地文件夹
这不太舒服,因为项目可能会充满依赖关系和配置,您必须牢记这些依赖关系和配置,并在以后从项目中删除。 即使存在所有问题,我们仍然无法创建一个新项目,因为它可能缺少必要的模块。
那么,我们提供什么来解决这些问题呢?
CUBA SDK
出于安全原因,在CUBA平台用户中,有一些公司限制或限制对外部网络的访问。
我们决定让用户的生活更轻松,并制作一个命令行工具CUBA SDK,该工具可以解决CUBA Platform,附加组件和任何其他具有maven坐标的库的所有依赖项。
那么,CUBA SDK和Gradle或Maven的脱机插件之间有什么区别?主要区别在于CUBA SDK不缓存特定的项目依赖项。 它使您可以在内部和外部存储库之间同步工件,从而使隔离环境中的开发过程轻松自如。CUBA SDK不需要项目,因此可以创建具有所有依赖项的框架,附加组件和库的必需脱机堆栈。
如果开发人员在多个项目上工作或计划一个新项目,因此他们不知道项目中将使用哪些模块,开发人员会发现它很有用。 借助SDK,这些模块可以预先加载到本地或内部存储库中。
通过将SDK用于集中式内部存储库同步,公司可以从中受益。
CUBA SDK可以通过一些简单的命令来解析,导出CUBA框架,附加组件或任何外部库的所有依赖项,并将其导出并上传到外部存储库。 对于完全隔离的网络,可以使用导入和导出命令或在网关上安装CUBA SDK。
CUBA SDK的优势:
- 自动使用已加载库的源代码收集所有依赖项
- 解决了CUBA平台和加载项的依赖性
- 检查新的库版本并安装它们
- 一次可以使用多个存储库进行工件搜索,包括本地Maven存储库
- 有一个嵌入式存储库Nexus OSS
- 提供了一次将工件上传到多个存储库的功能,包括本地Maven存储库
- 导入和导出具有所有依赖关系的工件
- 提供交互模式,并提示安装CUBA Platform和加载项
- 使用Gradle工具解决依赖关系
- 与IDE无关
- 可以安装在CI服务器上
SDK命令
可用命令的完整列表可以在GitHub上找到。
CUBA SDK最初支持三种组件类型:CUBA Framework,CUBA附加组件和可通过maven坐标加载的库。 可以通过CUBA SDK插件将该列表扩展为其他组件类型。
您可以通过install命令将组件安装到远程存储库。 在创建SDK时,我们预见到SDK可能会安装在网关计算机或便携式设备上。 在这种情况下,可以通过resolve和push命令安装组件。
解决–只需解决所有依赖项并将其下载到本地SDK缓存中push –将加载的工件及其依赖项传递到设置的目标存储库
对于使用存储库,SDK具有嵌入式存储库管理器。
存储库管理器支持本地和远程存储库,它们在SDK中分为两组
- 源–用于搜索工件的存储库
- 目标–工件将被加载到的存储库
SDK本身可用作存储库。 使用命令setup-nexus SDK下载,安装和配置Nexus OSS存储库。 要启动和停止存储库,请使用start和stop命令。
要检查和安装更新,只需运行命令check-updates 。
解决依赖性
SDK旨在解决的主要问题是正确解析和收集组件的依赖项。 在开发过程中,我们尝试了几种方法来解决组件的传递依赖关系。 最初,我们有一个想法,就是可以解析.pom文件并组成一个依赖关系树。 但是实际上,手动依赖性解析并不是一个好主意,特别是因为Apache Maven可以直接使用。
Maven作为依赖性管理器
因此,我们将Apache Maven用作传递依赖项管理的工具。
为了实现此目的,CUBA SDK将maven分发文件加载到SDK主文件夹中,并通过Java Runtime运行命令。
例如,命令
dependency:resolve -Dtransitive= true -DincludeParents= true -DoverWriteSnapshots= true -Dclassifier=<classifier> -f pom.xml
帮助我们解决了pom.xml中描述的所有组件的传递依赖关系,并且这些组件已自动加载到本地Maven Cash中。 之后,我们运行命令
org.apache.maven.plugins:maven-deploy-plugin: 3.0 . 0 -M1:deploy-file -Durl=<repository URL>
将工件加载到所需的存储库中。
以下命令使我们可以将库加载到本地存储库。
org.apache.maven.plugins:maven-dependency-plugin: 3.1 . 1 :get -Dartifact=<maven coordinates>
为了在CUBA SDK应用程序中运行Maven命令,我们生成了settings.xml文件。 它包含必须用于获取和加载工件的所有存储库的列表。
Gradle作为依赖管理器
在第一个应用程序版本中,依赖关系正确但缓慢地解决了,在测试过程中,当我们解决某些CUBA Platform附加组件的依赖关系时遇到冲突。 但是,在使用Gradle构建项目的过程中没有此类问题。
因此,我们决定将依赖关系解决逻辑切换为Gradle。 为了做到这一点,我们创建了一个build.gradle脚本,其中包含加载和解决组件依赖关系所需的任务。
为了调用Gradle任务,我们使用了Gradle Tooling API。
为了通过Gradle定义依赖路径,我们使用了工件解析查询API。 以下代码帮助我们获得了库源代码的路径:
def component = project.dependencies.createArtifactResolutionQuery()
.forComponents(artifact.id.componentIdentifier)
.withArtifacts(JvmLibrary, SourcesArtifact)
.execute()
.resolvedComponents[ 0 ]
def sourceFile = component?.getArtifacts(SourcesArtifact)[ 0 ]?.file
因此,我们获得了本地Gradle缓存中所有文件的路径,并将它们保存到SDK存储中。
为了解决组件的依赖关系并将其加载到本地缓存,我们将组件添加到配置中,并使用lenientConfiguration
获取所有依赖关系。
project.ext.properties[ "toResolve" ].tokenize( ';' ).each {
dependencies.add 'extraLibs' , it
}
def resolved = [:]
configurations.all.collect {
if (it.canBeResolved) {
it.resolvedConfiguration.lenientConfiguration.artifacts.each { art ->
try {
...
} catch (e) {
logger.error( "Error: " + e.getMessage(), e)
logger.error( "could not find pom for {}" , art.file)
}
}
}
}
我们使用lenientConfiguration
来防止Gradle脚本崩溃,以防在存储库中找不到该组件。
为了将工件加载到存储库,SDK运行PublishToMavenRepository
Gradle任务。
task publishArtifact(type: PublishToMavenRepository) {
doLast {
if (project.ext.hasProperty( "toUpload" )) {
def toUpload = new JsonSlurper().parseText(project.ext.properties[ "toUpload" ])
def descriptors = new JsonSlurper().parseText(project.ext.properties[ "descriptors" ])
artifactId toUpload.artifactId
groupId toUpload.groupId
version toUpload.version
descriptors.each { descriptor ->
artifact(descriptor.filePath) {
classifier descriptor.classifier.type
extension descriptor.classifier.extenstion
}
}
}
}
}
感谢Gradle,我们在解决传递依赖项时避免了冲突,并显着加快了应用程序的运行速度。
项目建设
为了构建CUBA SDK,我们使用了与CUBA CLI相同的方法。 使用jlink工具,我们构建了所有必要的模块,以将它们与应用程序一起提供的自定义JRE捆绑在一起。 这种方法使SDK与安装的Java版本无关。 您可以在CLI Core Sample项目中找到此类构建的示例。
第三方插件支持
由于CUBA SDK基于CLI Core库,因此它支持第三方插件。 目前,SDK具有maven和gradle组件依赖性管理器以及通过第三方插件实现的CUBA组件的提供程序。
让我们看一下如何使用插件扩展SDK功能的示例。 我们将从广为人知的Spring Initializr中为Spring Boot启动程序创建提供程序。
首先让我们创建一个新项目。 作为一个例子,我们将使用CUBA CLI插件,因为它描述了这里,并添加依赖关系:
implementation "com.haulmont.cli.core:cli-core:1.0.0"
implementation "com.haulmont.cli.sdk:cuba-sdk:1.0.1"
为Spring Boot Starters创建一个新的提供程序– SpringBootProvider,它扩展了BintraySearchComponentProvider。 BintraySearchComponentProvider允许使用Bintray API自动搜索可访问的组件版本。
SpringBootProvider : BintraySearchComponentProvider() { class SpringBootProvider : BintraySearchComponentProvider() {
var springComponentsInfo: SpringComponentsInfo? = var springComponentsInfo: SpringComponentsInfo? = null
override fun getType() = "boot-starter"
override fun getName() = "Spring boot starter"
...
override fun load() {
springComponentsInfo = Gson().fromJson(readSpringFile(), SpringComponentsInfo:: class .java)
}
private fun readSpringFile(): String {
return SpringComponentsPlugin:: class .java.getResourceAsStream( "spring-components.json" )
.bufferedReader()
.use { it.readText() }
}
该提供程序将从spring-components.json文件中搜索可访问的组件,该文件是Spring Initializr应用程序中yml文件的json版本。
为了从json映射到对象,让我们创建简单的数据类:
data class SpringComponent(
val name: String,
val id: String,
val groupId: String?,
val artifactId: String?,
val description: String?,
val starter: Boolean? = val starter: Boolean? = true
)
data class SpringComponentCategory(
val name: String,
val content: List<SpringComponent>
)
data class SpringInitializr(
val dependencies: List<SpringComponentCategory>
)
data class SpringComponentsInfo(
val initializr: SpringInitializr
)
要将此提供程序添加到其他SDK提供程序,我们需要在插件的init事件中注册该提供程序:
class SpringBootComponentsPlugin : CliPlugin {
private val componentRegistry: ComponentRegistry by sdkKodein.instance<ComponentRegistry>()
@Subscribe
fun onInit(event: InitPluginEvent) {
val bootProvider = SpringBootProvider()
componentRegistry.addProviders(bootProvider)
bootProvider.load()
}
}
就是这样。 现在,要通过终端或IDE安装插件,请运行gradle installPlugin命令。
运行SDK
我们可以看到我们的插件已成功加载。 现在,让我们在resolve boot-starter命令的帮助下检查逻辑是否正常运行:
如我们所见,组件及其版本的提示将按其应有的方式工作。
如果您在存储设备中存储工件和依赖项的方式与在Maven存储库中存储方式不同,则可以使用插件来实现自己的ArtifactManager接口,该接口可以包含用于存储的逻辑。
可以在GitHub页面上找到测试插件的源代码。
结论
首先,由于安全要求,CUBA SDK对于对外部网络访问受限的公司很有用。
如果我们从公司的安全策略中抽象出来,那么存储库可用性问题对于开发人员来说也很重要,例如,如果开发人员要在乡下独自工作并且那里的互联网连接较差。 在这种情况下,CUBA SDK是一个很好的选择,它将帮助您在个人计算机上本地构建高效的库和框架堆栈。
事务隔离级别