共享构建服务允许任务共享状态或资源。例如,任务可能共享预计算值的缓存,或使用 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()
方法。这发生在最后使用构建服务的任务完成到构建结束之间。
以下是一个接受参数且可关闭的服务示例
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://:%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
注解时注册先前的服务
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
注解的属性消费先前的服务
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
任务属性使用的构建服务
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
注解时,所有这些都不需要。
以下是一个任务示例,它通过带有 @Internal
注解的属性消费先前的服务
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
之外的任何注解的服务。例如,目前无法将服务标记为任务的输入。
从配置操作中使用共享构建服务
通常,构建服务旨在由任务使用,并且由于它们通常表示一些可能创建成本高昂的状态,因此您应避免在配置时使用它们。但是,有时在配置时使用服务可能是有意义的。这是可能的;在提供者上调用 get()
。
将构建服务与 Worker API 结合使用
除了从任务中使用构建服务之外,您还可以从 Worker API 操作、工件转换或另一个构建服务中使用构建服务。为此,请将构建服务 Provider
作为消费操作或服务的参数传递,其方式与将其他参数传递给操作或服务的方式相同。
例如,要将 MyServiceType
服务传递给 Worker API 操作,您可以向操作的参数对象添加一个 Property<MyServiceType>
类型的属性,然后将注册服务时收到的 Provider<MyServiceType>
连接到此属性
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 的构建服务
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 服务上的方法开始接收事件
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 订阅构建服务以构建事件。 |