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

本章适用于以下类型:

  • 插件类型.

  • 任务类型.

  • 伪影转换参数类型.

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

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

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

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

  • Elements of a NamedDomainObjectContainer.

Configuration using bean properties

您实现的自定义Gradle类型通常包含一些您希望使其可用于构建脚本和其他插件的配置. 例如,下载任务可以具有指定要从中下载的URL和将结果写入的文件系统位置的配置. 此配置表示为Java bean属性.

Kotlin and Groovy provide conveniences for declaring Java bean properties, which make them good language choices to use to implement Gradle types. These conveniences are demonstrated in the samples below.

Gradle还为使用bean属性实现类型提供了一些便利.

Managed properties

Gradle可以提供抽象属性的实现. 这称为托管属性 ,因为Gradle 负责管理属性的状态. 属性可以是可变的 ,这意味着它既具有getter方法又具有setter方法,或者是只读的 ,意味着它仅具有getter方法.

托管属性当前是一个孵化功能.

Mutable managed properties

要声明可变的托管属性,请为该类型的属性添加一个抽象的getter方法和一个抽象的setter方法.

这是带有uri属性的任务类型的示例:

例子1.可变的托管属性
Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // Use an abstract getter and setter method
    @Input
    abstract URI getUri();
    abstract void setUri(URI uri);

    @TaskAction
    void run() {
        // Use the `uri` property
        System.out.println("Downloading " + getUri());
    }
}
Download.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import java.net.URI

abstract class Download : DefaultTask() {
    // Use an abstract var
    @get:Input
    abstract var uri: URI

    @TaskAction
    fun run() {
        // Use the `uri` property
        println("Downloading $uri")
    }
}
Download.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

abstract class Download extends DefaultTask {
    // Use an abstract property
    @Input
    abstract URI uri

    @TaskAction
    void run() {
        // Use the `uri` property
        println "downloading ${uri}"
    }
}

请注意,要使某个属性被视为可变的托管属性,该属性的所有 getter方法和setter方法必须是abstract并且具有publicprotected可见性.

Read-only managed properties

要声明只读托管属性,请为该类型的属性添加一个抽象的getter方法. 该属性不应具有任何setter方法. Gradle将提供getter的实现,并为该属性创建一个值.

这是与Gradle可配置的惰性属性或容器类型之一一起使用的有用模式.

这是带有uri属性的任务类型的示例:

例子2.只读托管属性
Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // Use an abstract getter method
    @Input
    abstract Property<URI> getUri();

    @TaskAction
    void run() {
        // Use the `uri` property
        System.out.println("Downloading " + getUri().get());
    }
}
Download.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.provider.Property
import java.net.URI

abstract class Download : DefaultTask() {
    // Use an abstract val
    @get:Input
    abstract val uri: Property<URI>

    @TaskAction
    fun run() {
        // Use the `uri` property
        println("Downloading ${uri.get()}")
    }
}
Download.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskAction

abstract class Download extends DefaultTask {
    // Use an abstract getter method
    @Input
    abstract Property<URI> getUri()

    @TaskAction
    void run() {
        // Use the `uri` property
        println "downloading ${uri.get()}"
    }
}

请注意,要将某个属性视为只读托管属性,该属性的所有 getter方法都必须是abstract并且必须具有publicprotected可见性. 该属性不得具有任何setter方法. 此外,属性类型必须具有以下之一:

  • Property<T>

  • RegularFileProperty

  • DirectoryProperty

  • ListProperty<T>

  • SetProperty<T>

  • MapProperty<K, V>

  • ConfigurableFileCollection

  • ConfigurableFileTree

  • NamedDomainObjectContainer<T>

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

Read-only managed nested properties

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

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

这是带有resource属性的任务类型的示例. Resource类型也是自定义的Gradle类型,并定义了一些托管属性:

例子3.只读托管嵌套属性
Download.java
public abstract class Download extends DefaultTask {
    // Use an abstract getter method annotated with @Nested
    @Nested
    abstract Resource getResource();

    @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();
}
Download.kt
abstract class Download : DefaultTask() {
    // Use an abstract getter method annotated with @Nested
    @get:Nested
    abstract val resource: Resource

    @TaskAction
    fun run() {
        // Use the `resource` property
        println("Downloading https://${resource.hostName.get()}/${resource.path.get()}")
    }
}

interface Resource {
    @get:Input
    val hostName: Property<String>
    @get:Input
    val path: Property<String>
}
Download.groovy
abstract class Download extends DefaultTask {
    // Use an abstract getter method annotated with @Nested
    @Nested
    abstract Resource getResource()

    @TaskAction
    void run() {
        // Use the `resource` property
        println("Downloading https://${resource.hostName.get()}/${resource.path.get()}")
    }
}

interface Resource {
    @Input
    Property<String> getHostName()
    @Input
    Property<String> getPath()
}

请注意,要将一个属性视为只读的托管嵌套属性,该属性的所有 getter方法必须是abstract并且具有publicprotected可见性. 该属性不得具有任何setter方法. 另外,必须使用@ Nested注释属性获取器.

Managed types

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

DSL support and extensibility

当Gradle创建自定义类型的实例时,它将实例装饰为混合DSL和可扩展性支持.

每个装饰的实例都实现ExtensionAware ,因此可以将扩展对象附加到它.

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

Service injection

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

Available services

以下服务可用于注入:

Constructor injection

对象可以通过两种方式接收其所需的服务. 第一种选择是将服务添加为类构造函数的参数. 构造函数必须使用javax.inject.Inject注释javax.inject.Inject注释. Gradle使用每个构造函数参数的声明类型来确定对象所需的服务. 构造函数参数及其名称的顺序并不重要,可以随便更改.

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

例子4.构造函数服务注入
Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;

import javax.inject.Inject;

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() {
        // ...
    }
}
Download.kt
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.OutputDirectory

open class Download
// Inject an ObjectFactory into the constructor
@Inject constructor(objectFactory: ObjectFactory) : DefaultTask() {
    // Use the factory
    @OutputDirectory
    val outputDirectory = objectFactory.directoryProperty()

    @TaskAction
    fun run() {
        // ...
    }
}
Download.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction

import javax.inject.Inject

class Download extends DefaultTask {
    @OutputDirectory
    final DirectoryProperty outputDirectory

    // Inject an ObjectFactory into the constructor
    @Inject
    Download(ObjectFactory objectFactory) {
        // Use the factory
        outputDirectory = objectFactory.directoryProperty()
    }

    @TaskAction
    void run() {
        // ...
    }
}

Property injection

另外,可以通过在类中添加带有javax.inject.Inject批注的属性getter方法来注入服务. 例如,当由于向后兼容性约束而无法更改类的构造函数时,此功能很有用. 这种模式还允许Gradle将服务的创建推迟到调用getter方法之前,而不是在创建实例时进行. 这可以帮助提高性能. Gradle使用getter方法的声明的返回类型来确定要提供的服务. 该属性的名称并不重要,并且可以是您喜欢的任何名称.

属性获取器方法必须是publicprotected . 该方法可以是abstract或者在不可能的情况下可以具有虚拟方法主体. 方法主体将被丢弃.

这是一个示例,显示了一个通过属性getter方法接收两项服务的任务类型:

例子5.物业服务注入
Download.java
import javax.inject.Inject;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkerExecutor;

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 ...
    }
}
Download.kt
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkerExecutor

abstract class Download : DefaultTask() {
    // Use an abstract property
    // Note that the @Inject annotation must be attached to the getter
    @get:Inject
    abstract val objectFactory: ObjectFactory

    // Alternatively, use a property getter with a dummy implementation
    // Note that the property must be open and the @Inject annotation must be attached to the getter
    @get:Inject
    open val workerExecutor: WorkerExecutor
        get() {
            // Getter body is ignored
            throw UnsupportedOperationException()
        }

    @TaskAction
    fun run() {
        // Use the executor and factory ...
    }
}
Download.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkerExecutor

import javax.inject.Inject

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() {
        // Use the executor and factory ...
    }
}

Creating nested objects

自定义Gradle类型可以使用ObjectFactory服务创建Gradle类型的实例以用于其属性值. 这些实例可以利用本章讨论的功能,使您可以创建"嵌套"对象和嵌套DSL.

您还可以让Gradle使用托管的嵌套属性为您创建嵌套对象.

在以下示例中,项目扩展通过其构造函数接收一个ObjectFactory实例. 构造函数使用它创建一个嵌套的Resource对象(也是一个自定义的Gradle类型),并使该对象可通过resource属性使用.

例子6.嵌套对象的创建
DownloadExtension.java
import org.gradle.api.model.ObjectFactory;

import javax.inject.Inject;

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 class Resource {
    private URI uri;

    public URI getUri() {
        return uri;
    }
    public void setUri(URI uri) {
        this.uri = uri;
    }
}
DownloadExtension.kt
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory

open class DownloadExtension @Inject constructor(objectFactory: ObjectFactory) {
    // Use an injected ObjectFactory to create a nested Resource object
    val resource: Resource = objectFactory.newInstance(Resource::class.java)
}

open class Resource {
    var uri: URI? = null
}
DownloadExtension.groovy
import org.gradle.api.model.ObjectFactory

import javax.inject.Inject

class DownloadExtension {
    // A nested instance
    final Resource resource

    @Inject
    DownloadExtension(ObjectFactory objectFactory) {
        // Use an injected ObjectFactory to create a Resource object
        resource = objectFactory.newInstance(Resource)
    }
}

class Resource {
    URI uri
}

Collection types

Gradle提供了用于维护对象集合的类型,旨在与Gradle DSL配合使用并提供有用的功能,例如延迟配置.

NamedDomainObjectContainer

NamedDomainObjectContainer管理一组对象,其中每个元素都有一个与之关联的名称. 该容器负责创建和配置元素,并提供DSL,构建脚本可用于定义和配置元素. 它旨在容纳本身可配置的对象,例如一组自定义Gradle对象.

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

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

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

为了将类型与任何domainObjectContainer()方法一起使用,它必须公开名为" name"的属性作为该对象的唯一且恒定的名称. 该方法的domainObjectContainer(Class)变体通过调用带有字符串参数的类的构造函数来创建新实例,该字符串参数是对象的所需名称. 以这种方式创建的对象被视为自定义Gradle类型,因此可以利用本章中讨论的功能,例如服务注入或托管属性.

有关允许自定义实例化策略的domainObjectContainer()方法变体,请参见上面的链接.

例子7.管理对象集合
DownloadExtension.java
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.model.ObjectFactory;

import javax.inject.Inject;

public class DownloadExtension {
    // A container of `Resource` objects
    private final NamedDomainObjectContainer<Resource> resources;

    @Inject
    public DownloadExtension(ObjectFactory objectFactory) {
        // Use an injected ObjectFactory to create a container
        resources = objectFactory.domainObjectContainer(Resource.class);
    }

    public NamedDomainObjectContainer<Resource> getResources() {
        return resources;
    }
}

public class Resource {
    private final String name;
    private URI uri;
    private String userName;

    // Type must have a public constructor that takes the element name as a parameter
    public Resource(String name) {
        this.name = name;
    }

    // Type must have a 'name' property, which should be read-only
    public String getName() {
        return name;
    }

    public URI getUri() {
        return uri;
    }
    public void setUri(URI uri) {
        this.uri = uri;
    }

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}
DownloadExtension.kt
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.NamedDomainObjectContainer

open class DownloadExtension @Inject constructor(objectFactory: ObjectFactory) {
    // Use an injected ObjectFactory to create a container of `Resource` objects
    val resources = objectFactory.domainObjectContainer(Resource::class.java)
}

// Type must have a public constructor that takes the element name as a parameter
// Type must have a 'name' property, which should be read-only
open class Resource(val name: String) {
    var uri: URI? = null
    var userName: String? = null
}
DownloadExtension.groovy
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.model.ObjectFactory

import javax.inject.Inject

class DownloadExtension {
    // A container of `Resource` instances
    final NamedDomainObjectContainer<Resource> resources

    @Inject
    DownloadExtension(ObjectFactory objectFactory) {
        // Use an injected ObjectFactory to create a container
        resources = objectFactory.domainObjectContainer(Resource)
    }
}

class Resource {
    // Type must have a 'name' property, which should be read-only
    final String name
    URI uri
    String userName

    // Type must have a public constructor that takes the element name as a parameter
    Resource(String name) {
        this.name = name
    }
}

对于每个容器属性,Gradle会自动向Groovy和Kotlin DSL添加一个块,您可以使用该块来配置容器的内容:

例子8.配置块
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 {
        gradle {
            uri = uri('https://gradle.org')
        }
    }
}

DomainObjectSet

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

您可以使用ObjectFactory.domainObjectSet()方法创建实例.