您可以开发多种不同类型的 Gradle“附加组件”,例如插件任务项目扩展工件转换,它们全部实现为可以在 JVM 上运行的类和其他类型。本章讨论这些类型的一些共同特征和概念。您可以使用这些功能来帮助实现自定义 Gradle 类型并为您的用户提供一致的 DSL。

本章适用于以下类型:

  • 插件类型。

  • 任务类型。

  • 工件变换参数类型。

  • Worker API 工作操作参数类型。

  • 使用 创建的扩展对象ExtensionContainer.create(),例如由插件注册的项目扩展。

  • 使用创建的对象ObjectFactory.newInstance()

  • 为托管嵌套属性创建的对象。

  • a 的元素NamedDomainObjectContainer

使用属性进行配置

您实现的自定义 Gradle 类型通常包含一些您希望可用于构建脚本和其他插件的配置。例如,下载任务可能具有指定从中下载的 URL 以及将结果写入的文件系统位置的配置。

托管物业

Gradle 提供了自己的托管属性概念,允许您将每个属性声明为抽象 getter(Java、Groovy)或抽象属性(Kotlin)。然后 Gradle 自动提供此类属性的实现。它称为托管属性,因为 Gradle 负责管理该属性的状态。属性可以是可变的,这意味着它同时具有get()方法和set()方法,也可以是只读的,这意味着它只有一个get()方法。 只读属性也称为providers

可变的托管属性

要声明可变托管属性,请添加类型的抽象 getter 方法Property<T>- 其中T可以是任何可序列化类型或完全 Gradle托管类型。 (有关更具体的属性类型,请参阅下面的列表。)该属性不得具有任何 setter 方法。以下是具有uritype 属性的任务类型示例URI

Download.java
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且 具有publicprotected可见性。属性类型必须是以下类型之一:

  • Property<T>

  • RegularFileProperty

  • DirectoryProperty

  • ListProperty<T>

  • SetProperty<T>

  • MapProperty<K, V>

  • ConfigurableFileCollection

  • ConfigurableFileTree

  • DomainObjectSet<T>

  • NamedDomainObjectContainer<T>

  • ExtensiblePolymorphicDomainObjectContainer<T>

  • DependencyCollector

Gradle 以与ObjectFactory相同的方式为托管属性创建值。

只读托管属性

要声明只读托管属性(也称为提供程序),请添加类型的 getter 方法Provider<T>。然后,方法实现需要派生值,例如从其他属性派生值。

uri以下是具有从属性派生的提供程序的任务类型的示例location

Download.java
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)
    }
}

只读托管嵌套属性

要声明只读托管嵌套属性,请将该属性的抽象 getter 方法添加到用 注释的类型。该属性不应有任何 setter 方法。 Gradle 提供了 getter 方法的实现,并且还为属性创建了一个值。嵌套类型也被视为自定义类型,并且可以使用本章中讨论的功能。@Nested

当自定义类型具有具有相同生命周期的嵌套复杂类型时,此模式非常有用。如果生命周期不同,请考虑改用Property<NestedType>

以下是具有属性的任务类型的示例resource。该Resource类型也是自定义 Gradle 类型,并定义了一些托管属性:

Download.java
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 方法必须是abstractpublicprotected可见性。该属性不得有任何 setter 方法。此外,属性获取器必须用 进行注释。@Nested

只读托管“名称”属性

如果类型包含类型的名为“name”的抽象属性String,Gradle 会提供 getter 方法的实现,并使用“name”参数扩展每个构造函数,该参数位于所有其他构造函数参数之前。如果类型是接口,Gradle 将提供具有单个“名称”参数和@Inject语义的构造函数。

您可以让您的类型实现或扩展Named接口,该接口定义了这样一个只读“name”属性。

托管类型

托管类型是没有字段且其属性全部托管的抽象类或接口。也就是说,它是一种状态完全由 Gradle 管理的类型。

命名托管类型是另外具有 type 的抽象属性“name”的托管类型String。命名托管类型作为NamedDomainObjectContainer的元素类型特别有用(见下文)。

Resource.java
public interface Resource {
    @Input
    Property<String> getHostName();
    @Input
    Property<String> getPath();
}

Java bean 属性。

有时您可能会看到以 Java bean 属性样式实现的属性。也就是说,它们不使用 aProperty<T>Provider<T>类型,而是使用具体的 setter 和 getter 方法(或 Groovy 或 Kotlin 中的相应便利方法)实现。这种属性定义风格是 Gradle 中的遗留问题,不鼓励使用。 Gradle 核心插件中仍属于这种风格的属性将在未来版本中迁移到托管属性。

DSL 支持和可扩展性

当 Gradle 创建自定义类型的实例时,它会装饰该实例以混合 DSL 和可扩展性支持。

每个修饰实例都实现ExtensionAware,因此可以附加扩展对象。

请注意,由于向后兼容性问题,使用Project.container()创建的插件和容器元素目前尚未修饰。

服务注入

Gradle 提供了许多有用的服务,可供自定义 Gradle 类型使用。例如,任务可以使用WorkerExecutor服务来并行运行工作,如工作 API部分所示。这些服务是通过服务注入提供的。

可用服务

可提供以下注射服务:

除上述之外,ProjectLayout服务WorkerExecutor仅可用于项目插件中的注入。

构造函数注入

对象可以通过两种方式接收其所需的服务。第一个选项是将服务添加为类构造函数的参数。构造函数必须带有注解javax.inject.Inject。 Gradle 使用每个构造函数参数的声明类型来确定对象所需的服务。构造函数参数的顺序及其名称并不重要,可以是您喜欢的任何顺序。

下面是一个示例,显示了通过其构造函数接收的任务类型ObjectFactory

Download.java
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 方法必须是publicor protected。该方法可以有abstract一个虚拟方法体,或者在不可能的情况下,可以有一个虚拟方法体。方法主体被丢弃。

下面的示例显示了通过属性 getter 方法接收两个服务的任务类型:

Download.java
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

DownloadExtension.java
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管理一组对象,其中每个元素都有一个与其关联的名称。容器负责创建和配置元素,并提供构建脚本可用于定义和配置元素的 DSL。它旨在保存本身可配置的对象,例如一组自定义 Gradle 对象。

GradleNamedDomainObjectContainer在整个 API 中广泛使用类型。例如,project.tasks用于管理项目任务的对象是NamedDomainObjectContainer<Task>.

您可以使用ObjectFactory服务创建容器实例,该服务提供ObjectFactory.domainObjectContainer()方法。这也可以使用Project.container()方法来实现,但是在自定义 Gradle 类型中,通常最好使用注入的ObjectFactory服务而不是传递Project实例。

您还可以使用如上所述的只读托管属性创建容器实例。

为了将类型与任何domainObjectContainer()方法一起使用,它必须

  • 命名的托管类型;或者

  • 将名为“name”的属性公开为对象的唯一且常量名称。该方法的变domainObjectContainer(Class) 体通过调用带有字符串参数(即所需的对象名称)的类的构造函数来创建新实例。

以这种方式创建的对象被视为自定义 Gradle 类型,因此可以利用本章中讨论的功能,例如服务注入或托管属性。

请参阅上面的链接,了解domainObjectContainer()允许自定义实例化策略的方法变体。

DownloadExtension.java
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 添加一个块,您可以使用它来配置容器的内容:

Example 9. Configure block
build.gradle.kts
plugins {
    id("org.gradle.sample.download")
}

download {
    // Can use a block to configure the container contents
    resources {
        register("gradle") {
            uri = uri("https://gradle.org")
        }
    }
}
build.gradle
plugins {
    id("org.gradle.sample.download")
}

download {
    // Can use a block to configure the container contents
    resources {
        register('gradle') {
            uri = uri('https://gradle.org')
        }
    }
}

可扩展多态域对象容器

ExtensiblePolymorphicDomainObjectContainer允许您为不同类型NamedDomainObjectContainer的对象定义实例化策略。

命名域对象集

NamedDomainObjectSet保存一组可配置对象,其中每个元素都有一个与其关联的名称这与 类似NamedDomainObjectContainer,但是 aNamedDomainObjectSet不管理集合中的对象。它们需要手动创建和添加。

命名域对象列表

NamedDomainObjectList保存可配置对象的列表,其中每个元素都有一个与其关联的名称。这与 类似NamedDomainObjectContainer,但是 aNamedDomainObjectList不管理集合中的对象。它们需要手动创建和添加。

域对象集

DomainObjectSet保存一组可配置对象。与 相比NamedDomainObjectContainer, aDomainObjectSet不管理集合中的对象。它们需要手动创建和添加。