有时,多个任务共享某些状态或资源很有用。例如,任务可能会共享预先计算值的缓存,以便更快地完成工作。或者任务可能使用 Web 服务或数据库实例来完成其工作。

Gradle 允许您声明构建服务来表示此状态。构建服务只是一个保存任务使用状态的对象。 Gradle 负责服务生命周期,并且仅在需要时创建服务实例,并在不再需要时将其清除。 Gradle 还可以选择负责协调对构建服务的访问,以便不超过指定数量的任务可以同时使用该服务。

实施构建服务

要实现构建服务,请创建一个实现BuildService的抽象类。定义您希望任务使用的此类型的方法。构建服务实现被视为自定义 Gradle 类型,并且可以使用自定义 Gradle 类型可用的任何功能。

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

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

构建服务实现也可以选择实现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 将提供此方法的实现。

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

使用任务中的构建服务

要使用任务中的构建服务,您需要:

  1. 向类型 的任务添加属性Property<MyServiceType>

  2. @Internal使用或注释该属性@ServiceReference(自 8.0 起)。

  3. 将共享构建服务提供者分配给该属性(使用时可选@ServiceReference(<serviceName>))。

  4. 声明任务和服务之间的关联,以便 Gradle 可以正确遵守构建服务生命周期及其使用约束(在使用时也是可选的@ServiceReference)。

请注意,当前不支持将服务与任何其他注释一起使用。例如,当前无法将服务标记为任务的输入。

使用注释共享构建服务属性@Internal

当您使用 注释共享构建服务属性时@Internal,您还需要做两件事:

  1. 将使用BuildServiceRegistry.registerIfAbsent()注册服务时获得的构建服务提供者显式分配给该属性。

  2. 通过Task.usesService显式声明任务和服务之间的关联。

下面是一个任务示例,该任务通过带有 注释的属性来使用先前的服务@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

@ServiceReference注释是一个正在孵化的API,在未来的版本中可能会发生变化。

否则,当您使用 注释共享构建服务属性时@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));
    }
}

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

要创建构建服务,请使用 BuildServiceRegistry.registerIfAbsent ()方法注册服务实例。注册服务不会创建服务实例。当任务首次使用该服务时,这种情况会按需发生。如果构建期间没有任务使用该服务,则不会创建该服务实例。

目前,构建服务的范围仅限于构建,而不是项目,并且这些服务可供所有项目的任务共享。您可以通过访问共享构建服务的注册表Project.getGradle().getSharedServices()

以下是一个插件示例,当使用该服务的任务属性注释为 时,该插件会注册先前的服务@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>。该提供程序可以连接到任务属性以将服务传递给任务。请注意,对于使用 注释的任务属性@Internal,该任务属性需要 (1) 显式分配注册期间获取的提供程序,并且 (2) 您必须通过Task.usesService告诉 Gradle 该任务使用该服务。

与使用服务的任务属性注释为以下情况进行比较@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"));
        });
    }
}

正如您所看到的,不需要为任务分配构建服务提供者,也不需要显式声明任务使用该服务。

通过配置操作使用共享构建服务

通常,构建服务旨在由任务使用,因为它们通常表示创建成本可能很高的某种状态,并且您应该避免在配置时使用它们。然而,有时在配置时使用该服务是有意义的。这是可能的,只需致电get()提供商即可。

使用构建服务的其他方式

除了使用任务中的构建服务之外,您还可以使用工作 API 操作工件转换或其他构建服务中的构建服务。为此,请将构建服务Provider作为使用操作或服务的参数传递,就像将其他参数传递给操作或服务一样。

例如,要将MyServiceType服务传递给辅助 API 操作,您可以将 type 属性添加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 或进程隔离模式的辅助 API 操作结合使用。

并发访问服务

您可以在注册服务时使用从BuildServiceSpec.getMaxParallelUsages()Property返回的对象来限制并发执行。当此属性没有值时(默认情况下),Gradle 不会限制对服务的访问。当此属性的值 > 0 时,Gradle 将允许不超过指定数量的任务同时使用该服务。

当使用任务属性使用 进行注释时@Internal,为了使约束生效,构建服务必须通过Task.usesService(Provider<? extends BuildService<?>>)向使用任务注册 。如果消费属性用 进行注释,则不需要这样做@ServiceReference

接收有关任务执行的信息

构建服务可用于在执行任务时接收事件。为此,创建并注册一个实现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
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订阅构建服务来构建事件。