共享构建服务允许任务共享状态或资源。例如,任务可以共享预计算值的缓存,或使用 Web 服务或数据库实例。

构建服务是用于保存任务使用状态的对象。它提供了一种替代机制,用于挂接到 Gradle 构建并接收有关任务执行和操作完成的信息。

构建服务是配置可缓存的。

Gradle 管理服务的生命周期,仅在需要时创建服务实例,并在不再需要时清理它。Gradle 还可以协调对构建服务的访问,确保并发使用服务的任务数量不超过指定数目。

实现构建服务

要实现构建服务,请创建一个实现 BuildService 接口的抽象类。然后,在此类型上定义您希望任务使用的方法。

abstract class BaseCountingService implements BuildService<CountingParams>, AutoCloseable {

}

构建服务实现被视为 自定义 Gradle 类型,可以使用自定义 Gradle 类型可用的任何特性。

构建服务可以选择接收参数,Gradle 在创建服务实例时会将这些参数注入。要提供参数,您可以定义一个抽象类(或接口)来保存参数。参数类型必须实现(或扩展)BuildServiceParameters。服务实现可以使用 this.getParameters() 访问参数。参数类型也是一种 自定义 Gradle 类型

当构建服务不需要任何参数时,您可以使用 BuildServiceParameters.None 作为参数类型。

interface CountingParams extends BuildServiceParameters {
    Property<Integer> getInitial()
}

构建服务实现还可以选择实现 AutoCloseable 接口,在这种情况下,Gradle 在丢弃服务实例时会调用该服务实例的 close() 方法。这发生在最后一个使用构建服务的任务完成到构建结束之间的某个时间点。

这是一个接收参数且可关闭服务的示例

WebServer.java
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

import java.net.URI;
import java.net.URISyntaxException;

public abstract class WebServer implements BuildService<WebServer.Params>, AutoCloseable {

    // Some parameters for the web server
    interface Params extends BuildServiceParameters {
        Property<Integer> getPort();

        DirectoryProperty getResources();
    }

    private final URI uri;

    public WebServer() throws URISyntaxException {
        // Use the parameters
        int port = getParameters().getPort().get();
        uri = new URI(String.format("https://localhost:%d/", port));

        // Start the server ...

        System.out.println(String.format("Server is running at %s", uri));
    }

    // A public method for tasks to use
    public URI getUri() {
        return uri;
    }

    @Override
    public void close() {
        // Stop the server ...
    }
}

请注意,您不应实现 BuildService.getParameters() 方法,因为 Gradle 会提供此方法的实现。

构建服务实现必须是线程安全的,因为它可能会被多个任务并发使用。

注册构建服务并将其连接到任务

要创建构建服务,您需要使用 BuildServiceRegistry.registerIfAbsent() 方法注册服务实例。

注册服务并不会立即创建服务实例。服务实例是在任务首次使用该服务时按需创建的。如果在构建过程中没有任务使用该服务,则不会创建服务实例。

当前,构建服务的范围限定在构建层面,而不是项目层面,这些服务可以由所有项目的任务共享。您可以通过 Project.getGradle().getSharedServices() 访问共享构建服务的注册表。

注册构建服务以便通过 @ServiceReference 任务属性消费

这是一个插件示例,当消费该服务的任务属性使用 @ServiceReference 注解时,该插件会注册之前的服务。

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

如您所见,无需将 registerIfAbsent() 返回的构建服务提供者赋值给任务,该服务会自动注入到所有使用 @ServiceReference 注解的匹配属性中。

这是一个任务示例,它通过使用 @ServiceReference 注解的属性消费之前的服务。

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

注册的构建服务与服务引用属性的自动匹配是按类型(以及可选地按名称,对于声明其预期服务名称的属性)完成的。如果多个服务与请求的服务类型匹配(即为同一类型注册了多个服务,并且在 @ServiceReference 注解中没有提供服务名称),则您还需要手动将共享构建服务提供者赋值给任务属性。

请继续阅读,将其与消费该服务的任务属性使用 @Internal 注解的情况进行比较。

注册构建服务以便通过 @Internal 任务属性消费

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        Provider<WebServer> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            // Connect the service provider to the task
            task.getServer().set(serviceProvider);
            // Declare the association between the task and the service
            task.usesService(serviceProvider);
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

在这种情况下,插件注册服务并获得一个 Provider<WebService>。此 provider 可以连接到任务属性,以便将服务传递给任务。请注意,对于使用 @Internal 注解的任务属性,需要 (1) 明确地将注册时获得的 provider 赋值给任务属性,并且 (2) 您必须通过 Task.usesService 告诉 Gradle 该任务使用了该服务。而当消费该服务的任务属性使用 @ServiceReference 注解时,这些步骤都不需要。

这是一个任务示例,它通过使用 @Internal 注解的属性消费之前的服务。

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @Internal
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

请注意,目前不支持对服务使用除 @ServiceReference@Internal 之外的任何注解。例如,目前无法将服务标记为任务的输入。

在配置操作中使用共享构建服务

通常,构建服务旨在供任务使用,并且由于它们通常代表一些创建成本可能较高的状态,您应避免在配置阶段使用它们。但是,有时在配置阶段使用服务也是有意义的。这是可能的;只需在 provider 上调用 get() 方法。

将构建服务与 Worker API 结合使用

除了在任务中使用构建服务外,您还可以在 Worker API 操作制品转换或另一个构建服务中使用构建服务。为此,可以将构建服务 Provider 作为消费操作或服务的参数传递,就像您将其他参数传递给操作或服务一样。

例如,要将 MyServiceType 服务传递给 Worker API 操作,您可以在操作的参数对象中添加一个类型为 Property<MyServiceType> 的属性,然后将注册服务时获得的 Provider<MyServiceType> 连接到此属性。

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;

import javax.inject.Inject;
import java.net.URI;

public abstract class Download extends DefaultTask {

    public static abstract class DownloadWorkAction implements WorkAction<DownloadWorkAction.Parameters> {
        interface Parameters extends WorkParameters {
            // This property provides access to the service instance from the work action
            abstract Property<WebServer> getServer();
        }

        @Override
        public void execute() {
            // Use the server to download a file
            WebServer server = getParameters().getServer().get();
            URI uri = server.getUri().resolve("somefile.zip");
            System.out.println(String.format("Downloading %s", uri));
        }
    }

    @Inject
    abstract public WorkerExecutor getWorkerExecutor();

    // This property provides access to the service instance from the task
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @TaskAction
    public void download() {
        WorkQueue workQueue = getWorkerExecutor().noIsolation();
        workQueue.submit(DownloadWorkAction.class, parameter -> {
            parameter.getServer().set(getServer());
        });
    }
}

目前,无法将构建服务与使用 ClassLoader 或进程隔离模式的 Worker API 操作结合使用。

并发访问构建服务

注册服务时,您可以使用 BuildServiceSpec.getMaxParallelUsages() 返回的 Property 对象来约束并发执行。当此属性没有值(这是默认设置)时,Gradle 不会约束服务的访问。当此属性的值 > 0 时,Gradle 将允许不超过指定数量的任务并发使用该服务。

当消费任务属性使用 @Internal 注解时,为使约束生效,必须通过 Task.usesService 将构建服务注册到消费任务。注意:目前,Gradle 无法发现服务的间接使用(例如,如果任务直接使用的服务仅使用了另一个附加服务)。作为一种变通方法,可以通过以下方式向 Gradle 明确声明间接使用:向任务添加一个 @ServiceReference 属性并将仅间接使用的服务赋值给它(使其成为直接引用),或者调用 Task.usesService

接收任务执行信息

构建服务可用于在任务执行时接收事件。为此,请创建一个并注册实现 OperationCompletionListener 接口的构建服务。

TaskEventsService.java
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.tooling.events.FinishEvent;
import org.gradle.tooling.events.OperationCompletionListener;
import org.gradle.tooling.events.task.TaskFinishEvent;

public abstract class TaskEventsService implements BuildService<BuildServiceParameters.None>,
    OperationCompletionListener { (1)

    @Override
    public void onFinish(FinishEvent finishEvent) {
        if (finishEvent instanceof TaskFinishEvent) { (2)
            // Handle task finish event...
        }
    }
}
1 实现 OperationCompletionListener 接口和 BuildService 接口。
2 检查结束事件是否为 TaskFinishEvent

然后,在插件中,您可以使用 BuildEventsListenerRegistry 服务上的方法来开始接收事件。

TaskEventsPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.build.event.BuildEventsListenerRegistry;

import javax.inject.Inject;

public abstract class TaskEventsPlugin implements Plugin<Project> {
    @Inject
    public abstract BuildEventsListenerRegistry getEventsListenerRegistry(); (1)

    @Override
    public void apply(Project project) {
        Provider<TaskEventsService> serviceProvider =
            project.getGradle().getSharedServices().registerIfAbsent(
                "taskEvents", TaskEventsService.class, spec -> {}); (2)

        getEventsListenerRegistry().onTaskCompletion(serviceProvider); (3)
    }
}
1 使用 服务注入 来获取 BuildEventsListenerRegistry 的实例。
2 照常注册构建服务。
3 使用服务 Provider 订阅构建服务以获取构建事件。