Gradle 有多种不同的“附加组件”,您可以开发这些组件,例如 插件、任务、项目扩展 或 工件转换,所有这些都作为类和其他类型实现,可以在 JVM 上运行。本章讨论了这些类型共有的某些特性和概念。您可以使用这些特性来帮助实现自定义 Gradle 类型,并为您的用户提供一致的 DSL。
本章适用于以下类型
-
插件类型。
-
任务类型。
-
工件转换参数类型。
-
Worker API 工作操作参数类型。
-
使用
ExtensionContainer.create()
创建的扩展对象,例如由插件注册的项目扩展。 -
使用
ObjectFactory.newInstance()
创建的对象。 -
为托管嵌套属性创建的对象。
-
NamedDomainObjectContainer
的元素。
使用属性进行配置
您实现的自定义 Gradle 类型通常会保存一些配置,您希望将这些配置提供给构建脚本和其他插件。例如,下载任务可能具有指定要从中下载的 URL 和要将结果写入的文件系统位置的配置。
托管属性
Gradle 提供了自己的托管属性概念,允许你将每个属性声明为抽象 getter(Java、Groovy)或抽象属性(Kotlin)。然后,Gradle 会自动为该属性提供实现。它被称为托管属性,因为 Gradle 会负责管理属性的状态。属性可以是可变的,这意味着它既有 get()
方法,又有 set()
方法,或者只读的,这意味着它只有 get()
方法。只读属性也称为提供者。
可变托管属性
要声明可变托管属性,请添加类型为 Property<T>
的抽象 getter 方法 - 其中 T
可以是任何可序列化的类型或完全的 Gradle 托管类型。(有关更具体的属性类型,请参见下方列表。)属性不得有任何 setter 方法。以下是具有类型为 URI
的 uri
属性的任务类型的示例
public abstract class Download extends DefaultTask {
@Input
public abstract Property<URI> getUri(); // abstract getter of type Property<T>
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` property
}
}
请注意,要将属性视为可变托管属性,属性的 getter 方法必须为 abstract
,并且具有 public
或 protected
可见性。属性类型必须是以下之一
-
Property<T>
-
RegularFileProperty
-
DirectoryProperty
-
ListProperty<T>
-
SetProperty<T>
-
MapProperty<K, V>
-
ConfigurableFileCollection
-
ConfigurableFileTree
-
DomainObjectSet<T>
-
NamedDomainObjectContainer<T>
-
ExtensiblePolymorphicDomainObjectContainer<T>
-
DependencyCollector
Gradle 以与 ObjectFactory 相同的方式为托管属性创建值。
只读托管属性
要声明只读托管属性(也称为提供者),请添加类型为 Provider<T>
的 getter 方法。然后,方法实现需要从其他属性(例如)派生值。
以下是具有从 location
属性派生的 uri
提供者的任务类型的示例
public abstract class Download extends DefaultTask {
@Input
public abstract Property<String> getLocation();
@Internal
public Provider<URI> getUri() {
return getLocation().map(l -> URI.create("https://" + l));
}
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` provider (read-only property)
}
}
只读托管嵌套属性
要声明一个只读的受管理的嵌套属性,请为使用 @Nested
注释的类型添加一个属性的抽象 getter 方法。该属性不应有任何 setter 方法。Gradle 为 getter 方法提供了一个实现,还会为该属性创建一个值。嵌套类型还被视为自定义类型,可以使用本章讨论的功能。
当自定义类型具有具有相同生命周期的嵌套复杂类型时,此模式非常有用。如果生命周期不同,请考虑改用 Property<NestedType>
。
下面是一个具有 resource
属性的任务类型的示例。Resource
类型也是一个自定义 Gradle 类型,并定义了一些受管理的属性
public abstract class Download extends DefaultTask {
@Nested
public abstract Resource getResource(); // Use an abstract getter method annotated with @Nested
@TaskAction
void run() {
// Use the `resource` property
System.out.println("Downloading https://" + getResource().getHostName().get() + "/" + getResource().getPath().get());
}
}
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
请注意,要将属性视为只读受管理的嵌套属性,该属性的 getter 方法必须为 abstract
,并且具有 public
或 protected
可见性。该属性不应有任何 setter 方法。此外,属性 getter 必须使用 @Nested
注释。
只读受管理的“name”属性
如果该类型包含一个名为“name”的抽象属性,类型为 String
,则 Gradle 会为 getter 方法提供一个实现,并使用一个“name”参数扩展每个构造函数,该参数位于所有其他构造函数参数之前。如果该类型是一个接口,则 Gradle 会提供一个具有单个“name”参数和 @Inject
语义的构造函数。
您可以让您的类型实现或扩展 Named 接口,该接口定义了这样一个只读的“name”属性。
受管理的类型
受管理的类型是一个抽象类或接口,没有字段,其属性都是受管理的。也就是说,它是一个其状态完全由 Gradle 管理的类型。
已命名的受管理类型是一个受管理的类型,它还具有一个名为“name”的抽象属性,类型为 String
。已命名的受管理类型特别有用,可以用作 NamedDomainObjectContainer 的元素类型(见下文)。
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
Java bean 属性。
有时你可能会看到以 Java bean 属性样式实现的属性。也就是说,它们不使用 Property<T>
或 Provider<T>
类型,而是使用具体的 setter 和 getter 方法(或 Groovy 或 Kotlin 中相应的便利方法)实现。这种属性定义样式在 Gradle 中已过时,不建议使用。Gradle 核心插件中仍然采用这种样式的属性将在未来版本中迁移到受管属性。
DSL 支持和可扩展性
当 Gradle 创建自定义类型的实例时,它会装饰该实例以混合 DSL 和可扩展性支持。
每个装饰的实例都实现了 ExtensionAware,因此可以附加扩展对象。
请注意,由于向后兼容性问题,目前未使用 Project.container() 创建的插件和容器元素进行装饰。
服务注入
Gradle 提供了许多可供自定义 Gradle 类型使用的有用服务。例如,任务可以使用 WorkerExecutor 服务并行运行工作,如 worker API 部分中所示。这些服务通过服务注入提供。
可注入服务
以下服务可用于注入
-
ObjectFactory - 允许创建模型对象。有关更多详细信息,请参阅 显式创建对象。
-
ProjectLayout - 提供对关键项目位置的访问。有关更多详细信息,请参阅 延迟配置。此服务在 Worker API 操作中不可用。
-
BuildLayout - 提供对 Gradle 构建的重要位置的访问。此服务仅可用于设置插件中的注入。
-
ProviderFactory - 创建
Provider
实例。有关更多详细信息,请参阅 延迟配置。 -
WorkerExecutor - 允许任务并行运行工作。有关更多详细信息,请参阅 工作器 API。
-
FileSystemOperations - 允许任务在文件系统上运行操作,例如删除文件、复制文件或同步目录。
-
ArchiveOperations - 允许任务在存档文件(例如 ZIP 或 TAR 文件)上运行操作。
-
ExecOperations - 允许任务运行外部进程,并专门支持运行外部
java
程序。 -
ToolingModelBuilderRegistry - 允许插件注册 Gradle 工具化 API 模型。
在上述服务中,ProjectLayout
和 WorkerExecutor
服务仅可用于在项目插件中注入。
构造函数注入
对象可以接收其所需服务的方式有 2 种。第一个选项是将服务作为类构造函数的参数添加。构造函数必须使用 javax.inject.Inject
注解进行注释。Gradle 使用每个构造函数参数的声明类型来确定对象所需的服务。构造函数参数的顺序及其名称并不重要,可以是您喜欢的任何内容。
以下示例显示通过其构造函数接收 ObjectFactory
的任务类型
public class Download extends DefaultTask {
private final DirectoryProperty outputDirectory;
// Inject an ObjectFactory into the constructor
@Inject
public Download(ObjectFactory objectFactory) {
// Use the factory
outputDirectory = objectFactory.directoryProperty();
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
return outputDirectory;
}
@TaskAction
void run() {
// ...
}
}
属性注入
或者,可以通过向类添加使用 javax.inject.Inject
注解进行注释的属性 getter 方法来注入服务。这很有用,例如,当由于向后兼容性约束而无法更改类的构造函数时。此模式还允许 Gradle 延迟创建服务,直到调用 getter 方法,而不是在创建实例时。这有助于提高性能。Gradle 使用 getter 方法声明的返回类型来确定要提供的服务。属性的名称并不重要,可以是您喜欢的任何名称。
属性 getter 方法必须为 public
或 protected
。该方法可以是 abstract
,或者在不可能的情况下,可以具有虚拟方法体。方法体将被丢弃。
以下示例显示了通过属性 getter 方法接收两个服务的任务类型
public abstract class Download extends DefaultTask {
// Use an abstract getter method
@Inject
protected abstract ObjectFactory getObjectFactory();
// Alternatively, use a getter method with a dummy implementation
@Inject
protected WorkerExecutor getWorkerExecutor() {
// Method body is ignored
throw new UnsupportedOperationException();
}
@TaskAction
void run() {
WorkerExecutor workerExecutor = getWorkerExecutor();
ObjectFactory objectFactory = getObjectFactory();
// Use the executor and factory ...
}
}
显式创建对象
最好通过使用 受管属性 让 Gradle 自动创建对象。 |
自定义 Gradle 类型可以使用 ObjectFactory 服务来创建 Gradle 类型的实例以用于其属性值。这些实例可以利用本章中讨论的功能,允许您创建对象和嵌套 DSL。
在以下示例中,项目扩展通过其构造函数接收一个 ObjectFactory
实例。构造函数使用它来创建嵌套的 Resource
对象(也是自定义 Gradle 类型),并通过 resource
属性提供此对象。
public class DownloadExtension {
// A nested instance
private final Resource resource;
@Inject
public DownloadExtension(ObjectFactory objectFactory) {
// Use an injected ObjectFactory to create a Resource object
resource = objectFactory.newInstance(Resource.class);
}
public Resource getResource() {
return resource;
}
}
public interface Resource {
Property<URI> getUri();
}
集合类型
Gradle 提供了用于维护对象集合的类型,旨在很好地扩展 Gradle 的 DSL 并提供诸如延迟配置之类的有用功能。
NamedDomainObjectContainer
一个 NamedDomainObjectContainer 管理一组对象,其中每个元素都有一个与之关联的名称。容器负责创建和配置元素,并提供构建脚本可用于定义和配置元素的 DSL。它旨在保存本身可配置的对象,例如一组自定义 Gradle 对象。
Gradle 在整个 API 中广泛使用 NamedDomainObjectContainer
类型。例如,用于管理项目任务的 project.tasks
对象是一个 NamedDomainObjectContainer<Task>
。
您可以使用提供 ObjectFactory.domainObjectContainer() 方法的 ObjectFactory 服务创建容器实例。也可以使用 Project.container() 方法,但是,在自定义 Gradle 类型中,通常最好使用注入的 ObjectFactory
服务,而不是传递 Project
实例。
您还可以使用上面描述的 只读托管属性创建容器实例。
为了使用 domainObjectContainer()
方法中的任何一种方法,它必须
-
是 命名的托管类型;或
-
公开一个名为“name”的属性作为对象的唯一且不变的名称。该方法的
domainObjectContainer(Class)
变体通过调用类的构造函数(该构造函数采用字符串参数,即对象的所需名称)来创建新实例。
通过这种方式创建的对象被视为自定义 Gradle 类型,因此可以使用本章中讨论的功能,例如服务注入或托管属性。
有关允许自定义实例化策略的 domainObjectContainer()
方法变体,请参阅上述链接。
public interface DownloadExtension {
NamedDomainObjectContainer<Resource> getResources();
}
public interface Resource {
// Type must have a read-only 'name' property
String getName();
Property<URI> getUri();
Property<String> getUserName();
}
对于每个容器属性,Gradle 都会自动向 Groovy 和 Kotlin DSL 中添加一个块,您可以使用该块来配置容器的内容
plugins {
id("org.gradle.sample.download")
}
download {
// Can use a block to configure the container contents
resources {
register("gradle") {
uri = uri("https://gradle.org.cn")
}
}
}
plugins {
id("org.gradle.sample.download")
}
download {
// Can use a block to configure the container contents
resources {
register('gradle') {
uri = uri('https://gradle.org.cn')
}
}
}
ExtensiblePolymorphicDomainObjectContainer
ExtensiblePolymorphicDomainObjectContainer 是一个 NamedDomainObjectContainer
,它允许您为不同类型的对象定义实例化策略。
您可以使用 ObjectFactory.polymorphicDomainObjectContainer() 方法创建实例。
NamedDomainObjectSet
NamedDomainObjectSet 保存一组可配置对象,其中每个元素都有一个与其关联的名称。这类似于 NamedDomainObjectContainer
,但是 NamedDomainObjectSet
不会管理集合中的对象。它们需要手动创建和添加。
您可以使用 ObjectFactory.namedDomainObjectSet() 方法创建实例。
NamedDomainObjectList
一个 NamedDomainObjectList 保存一个可配置对象的列表,其中每个元素都有一个与之关联的名称。这与 NamedDomainObjectContainer
类似,但 NamedDomainObjectList
不会管理集合中的对象。它们需要手动创建和添加。
你可以使用 ObjectFactory.namedDomainObjectList() 方法创建一个实例。
DomainObjectSet
一个 DomainObjectSet 只保存一个可配置对象集合。与 NamedDomainObjectContainer
相比,一个 DomainObjectSet
不会管理集合中的对象。它们需要手动创建和添加。
你可以使用 ObjectFactory.domainObjectSet() 方法创建一个实例。