有時,讓多個任務共用一些狀態或資源會很有用。例如,任務可能會共用預先計算值的快取,以便更快地完成工作。或者,任務可能會使用網路服務或資料庫執行個工作。

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://127.0.0.1:%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 動作,您可以在動作的參數物件中新增型別為 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 BuildService 介面之外實作 OperationCompletionListener 介面。
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 訂閱建置服務以建置事件。