功能作为一级概念
组件提供许多功能,这些功能通常与提供这些功能的软件架构无关。例如,一个库可能在一个工件中包含多个功能。但是,这样的库将在单个 GAV(组、工件和版本)坐标下发布。这意味着,在单个坐标下,组件的潜在“功能”可能共存。
使用 Gradle,显式声明组件提供的功能变得很有趣。为此,Gradle 提供了功能的概念。
一个功能通常是通过组合不同的功能来构建的。
在理想情况下,组件不应该声明对显式 GAV 的依赖关系,而应该用功能来表达它们的依赖关系
-
“给我一个提供日志记录功能的组件”
-
“给我一个脚本引擎”
-
“给我一个支持 Groovy 的脚本引擎”
通过对功能进行建模,依赖关系管理引擎可以更智能,并在依赖关系图中存在不兼容的功能时通知你,或者在图中的不同模块提供相同功能时要求你选择。
为外部模块声明功能
值得注意的是,Gradle 支持为构建的组件声明功能,也支持为外部组件声明功能,以防它们没有声明。
例如,如果你的构建文件包含以下依赖关系
dependencies {
// This dependency will bring log4:log4j transitively
implementation("org.apache.zookeeper:zookeeper:3.4.9")
// We use log4j over slf4j
implementation("org.slf4j:log4j-over-slf4j:1.7.10")
}
dependencies {
// This dependency will bring log4:log4j transitively
implementation 'org.apache.zookeeper:zookeeper:3.4.9'
// We use log4j over slf4j
implementation 'org.slf4j:log4j-over-slf4j:1.7.10'
}
按现状,很难弄清楚最终会在类路径中获得两个日志框架。实际上,zookeeper
会引入 log4j
,而我们想要使用的是 log4j-over-slf4j
。我们可以通过添加一条规则来预先检测冲突,该规则将声明这两个日志框架提供相同的功能
dependencies {
// Activate the "LoggingCapability" rule
components.all(LoggingCapability::class.java)
}
class LoggingCapability : ComponentMetadataRule {
val loggingModules = setOf("log4j", "log4j-over-slf4j")
override
fun execute(context: ComponentMetadataContext) = context.details.run {
if (loggingModules.contains(id.name)) {
allVariants {
withCapabilities {
// Declare that both log4j and log4j-over-slf4j provide the same capability
addCapability("log4j", "log4j", id.version)
}
}
}
}
}
dependencies {
// Activate the "LoggingCapability" rule
components.all(LoggingCapability)
}
@CompileStatic
class LoggingCapability implements ComponentMetadataRule {
final static Set<String> LOGGING_MODULES = ["log4j", "log4j-over-slf4j"] as Set<String>
void execute(ComponentMetadataContext context) {
context.details.with {
if (LOGGING_MODULES.contains(id.name)) {
allVariants {
it.withCapabilities {
// Declare that both log4j and log4j-over-slf4j provide the same capability
it.addCapability("log4j", "log4j", id.version)
}
}
}
}
}
}
通过添加此规则,我们将确保 Gradle *将* 检测到冲突并适当地失败
> Could not resolve all files for configuration ':compileClasspath'. > Could not resolve org.slf4j:log4j-over-slf4j:1.7.10. Required by: project : > Module 'org.slf4j:log4j-over-slf4j' has been rejected: Cannot select module with conflict on capability 'log4j:log4j:1.7.10' also provided by [log4j:log4j:1.2.16(compile)] > Could not resolve log4j:log4j:1.2.16. Required by: project : > org.apache.zookeeper:zookeeper:3.4.9 > Module 'log4j:log4j' has been rejected: Cannot select module with conflict on capability 'log4j:log4j:1.2.16' also provided by [org.slf4j:log4j-over-slf4j:1.7.10(compile)]
请参阅 文档中的功能部分,了解如何解决功能冲突。
为本地组件声明附加功能
所有组件都具有与组件相同的 GAV 坐标相对应的 *隐式功能*。但是,也可以为组件声明额外的 *显式功能*。当以不同 GAV 坐标发布的库是同一 API 的 *替代实现* 时,这很方便
configurations {
apiElements {
outgoing {
capability("com.acme:my-library:1.0")
capability("com.other:module:1.1")
}
}
runtimeElements {
outgoing {
capability("com.acme:my-library:1.0")
capability("com.other:module:1.1")
}
}
}
configurations {
apiElements {
outgoing {
capability("com.acme:my-library:1.0")
capability("com.other:module:1.1")
}
}
runtimeElements {
outgoing {
capability("com.acme:my-library:1.0")
capability("com.other:module:1.1")
}
}
}
功能必须附加到 *传出配置*,即组件的 可消费配置。
此示例显示我们声明了两个功能
-
com.acme:my-library:1.0
,对应于库的 *隐式功能* -
com.other:module:1.1
,对应于该库的另一个功能
值得注意的是,我们需要这样做 1. 因为一旦你开始声明 *显式* 功能,那么 *所有* 功能都需要声明,包括 *隐式* 功能。
第二个功能可以特定于此库,也可以对应于外部组件提供的功能。在这种情况下,如果 com.other:module
出现在同一个依赖关系图中,构建将失败,并且消费者 将不得不选择使用哪个模块。
功能发布到 Gradle 模块元数据。但是,它们在 POM 或 Ivy 元数据文件中 *没有等效项*。因此,在发布此类组件时,Gradle 会警告你,此功能仅供 Gradle 消费者使用
Maven publication 'maven' contains dependencies that cannot be represented in a published pom file. - Declares capability com.acme:my-library:1.0 - Declares capability com.other:module:1.1