適用於 Azure Function 的 Spring Cloud Function
1. 概述
在本教學中,我們將學習如何使用Spring Cloud Function(SCF)框架來開發可部署在Microsoft Azure Functions中的 Java 應用程式。我們將討論其關鍵概念,開發範例應用程序,將其部署在 Azure Functions 服務上,最後對其進行測試。
2. 關鍵概念
Azure Functions 服務提供了一個無伺服器環境,我們可以在其中部署應用程序,而無需擔心基礎設施管理。我們可以依照對應的SDK函式庫定義的框架,用Java、Python、C#等不同的程式語言來寫應用程式。這些應用程式可以透過源自 Azure 服務(例如 Blob 儲存、表格儲存、Cosmos DB 資料庫、事件橋等)的各種事件來呼叫。 Java Azure Function 函式庫提供了一個強大的基於註解的程式設計模型。它有助於將方法註冊到事件,從來源系統接收數據,然後更新目標系統。 SCF 框架提供了為 Azure Functions 和其他無伺服器雲端原生服務(例如 AWS Lambda、 Google Cloud Functions和Apache OpenWhisk)編寫的底層程式的抽象。由於SCF Azure 適配器,這一切都成為可能:
由於其統一的程式設計模型,它有助於相同程式碼跨不同平台的可移植性。此外,我們可以輕鬆地將 Spring 框架的依賴注入等主要功能應用到無伺服器應用程式中。通常,我們實作核心功能接口,例如
Function<I, O> 、 Consumer<I>和Supplier<O> ,並將它們註冊為 Spring beans。然後,這個 bean 會自動組裝到事件處理程序類別中,其中端點方法與@FunctionName註解一起套用。此外,SCF 還提供了一個FunctionCatlog bean,可以將其自動組裝到事件處理程序類別中。我們可以使用FunctionCatlaog#lookup(“<<bean name>>”)方法來擷取已實作的功能介面。 FunctionCatalog類別將其包裝在SimpleFunctionRegistry.FunctionInvocationWrapper類別中,該類別提供附加功能,例如函數組合和路由。我們將在接下來的部分中了解更多。
3. 先決條件
首先,我們需要一個有效的Azure 訂閱來部署 Azure Function 應用程式。 Java 應用程式的端點必須遵循 Azure Function 的程式設計模型,因此我們必須為其使用Maven 相依性:
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>
<version>3.1.0</version>
</dependency>
應用程式的程式碼準備好後,我們將需要Azure 函數 Maven 外掛程式將其部署在 Azure 中:
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>1.24.0</version>
</plugin>
Maven 工具有助於將應用程式打包到部署到 Azure Functions 服務所規定的標準結構中。 像往常一樣,該外掛程式可協助指定 Azure Function 的部署配置,例如appname 、 resourcegroup 、 appServicePlanName等。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
<version>4.1.3</version>
</dependency>
該程式庫在以 Java 編寫的 Azure Function 處理程序中啟用 SCF 和 Spring 相依性注入功能。這個處理程序指的是我們應用@FunctionName註解的 Java 方法,也是處理來自 Azure 服務(如 Blob 儲存體、Cosmos DB 事件橋接器等)或自訂應用程式的任何事件的入口點。應用程式 jar 的 Manifest 檔案必須將入口點指向使用@SpringBootApplication註解的 Spring Boot 類別。我們可以藉助maven-jar-plugin明確設定它:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.baeldung.functions.AzureSpringCloudFunctionApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
另一種方法是在pom.xml檔案中設定 start-class 屬性值,但這僅在我們將spring-boot-starter-parent定義為父級時才有效:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.11</version>
<relativePath/>
</parent>
最後,我們設定start-class屬性:
<properties>
<start-class>com.baeldung.functions.AzureSpringCloudFunctionApplication</start-class>
</properties>
此屬性可確保呼叫 Spring Boot 主類,初始化 Spring bean 並允許它們自動組裝到事件處理程序類別中。最後,Azure 期望應用程式有特定類型的打包,因此我們必須停用預設的 Spring Boot 打包並啟用 Spring Boot 精簡版面:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
</dependency>
</dependencies>
</plugin>
4.Java實現
讓我們考慮一個場景,其中 Azure Function 應用程式根據員工的居住城市計算其津貼。該應用程式透過 HTTP 接收員工 JSON 字串,並透過將津貼添加到薪資中來將其發回。
4.1.使用普通 Spring Bean 實現
首先,我們將定義用於開發此 Azure Function 應用程式的主要類別:
讓我們從定義
EmployeeSalaryFunction開始:
public class EmployeeSalaryFunction implements Function<Employee, Employee> {
@Override
public Employee apply(Employee employee) {
int allowance;
switch (employee.getCity()) {
case "Chicago" -> allowance = 5000;
case "California" -> allowance = 2000;
case "New York" -> allowance = 2500;
default -> allowance = 1000;
}
int finalSalary = employee.getSalary() + allowance;
employee.setSalary(finalSalary);
return employee;
}
}
EmployeeSalaryFunction類別實作介面java.util.function.Function 。 EmployeeSalaryFunction #apply apply()方法將基於城市的津貼添加到員工的基本工資中。要將此類載入為 Spring bean,我們將在ApplicationConfiguration類別中實例化它:
@Configuration
public class ApplicationConfiguration {
@Bean
public Function<Employee, Employee> employeeSalaryFunction() {
return new EmployeeSalaryFunction();
}
}
我們已將@Configuration註解應用於此類,讓 Spring 框架知道這是 bean 定義的來源。 @Bean方法employeeSalaryFunction()建立類型為EmployeeSalaryFunction的spring bean employeeSalaryFunction 。現在,讓我們使用@Autowired註解將這個employeeSalaryFunction bean 注入到EmployeeSalaryHandler類別中:
@Component
public class EmployeeSalaryHandler {
@Autowired
private Function<Employee, Employee> employeeSalaryFunction;
@FunctionName("employeeSalaryFunction")
public HttpResponseMessage calculateSalary(
@HttpTrigger(
name="http",
methods = HttpMethod.POST,
authLevel = AuthorizationLevel.ANONYMOUS)HttpRequestMessage<Optional<Employee>> employeeHttpRequestMessage,
ExecutionContext executionContext
) {
Employee employeeRequest = employeeHttpRequestMessage.getBody().get();
Employee employee = employeeSalaryFunction.apply(employeeRequest);
return employeeHttpRequestMessage.createResponseBuilder(HttpStatus.OK)
.body(employee)
.build();
}
}
Azure 事件處理程序函數主要依照 Java Azure Function SDK 程式設計模型來編寫。但是,它在類別層級使用 Spring 框架的@Component註釋,並在employeeSalaryFunction欄位上使用@Autowired註釋。按照慣例,確保自動組裝 bean 的名稱與@FunctionName註解中指定的名稱相符是一個很好的做法。同樣,我們可以擴展 Spring 框架對其他 Azure Function 觸發器的支持,例如@BlobTrigger 、 @QueueTrigger 、 @TimerTrigger等。
4.2.使用 SCF 實現
在我們必須動態檢索 Function bean 的場景中,明確自動組裝所有 Functions 並不是最佳解決方案。假設我們有多個實現來根據城市計算員工的最終工資:
我們定義了
NewYorkSalaryCalculatorFn 、 ChicagoSalaryCalculatorFn,和CaliforniaSalaryCalculatorFn等函數。這些根據員工居住城市計算員工的最終工資。讓我們來看看CaliforniaSalaryCalculatorFn類別:
public class CaliforniaSalaryCalculatorFn implements Function<Employee, Employee> {
@Override
public Employee apply(Employee employee) {
Integer finalSalary = employee.getSalary() + 3000;
employee.setSalary(finalSalary);
return employee;
}
}
此方法在員工的基本薪資上額外增加$3000津貼。計算其他城市員工薪資的功能或多或少相似。入口方法EmployeeSalaryHandler#calculateSalaryWithSCF()使用EmployeeSalaryFunctionWrapper#getCityBasedSalaryFunction()檢索適當的特定於城市的函數來計算員工的工資:
public class EmployeeSalaryFunctionWrapper {
private FunctionCatalog functionCatalog;
public EmployeeSalaryFunctionWrapper(FunctionCatalog functionCatalog) {
this.functionCatalog = functionCatalog;
}
public Function<Employee, Employee> getCityBasedSalaryFunction(Employee employee) {
Function<Employee, Employee> salaryCalculatorFunction;
switch (employee.getCity()) {
case "Chicago" -> salaryCalculatorFunction = functionCatalog.lookup("chicagoSalaryCalculatorFn");
case "California" -> salaryCalculatorFunction = functionCatalog.lookup("californiaSalaryCalculatorFn|defaultSalaryCalculatorFn");
case "New York" -> salaryCalculatorFunction = functionCatalog.lookup("newYorkSalaryCalculatorFn");
default -> salaryCalculatorFunction = functionCatalog.lookup("defaultSalaryCalculatorFn");
}
return salaryCalculatorFunction;
}
}
我們可以透過將FunctionCatalog物件傳遞給建構函式來實例化EmployeeSalaryFunctionWrapper 。然後我們透過呼叫EmployeeSalaryFunctionWrapper#getCityBasedSalaryFunction()來檢索確實的薪資計算器函數 bean。 FunctionCatalog#lookup(<<bean name>>) 方法幫助檢索工資計算器函數 bean。而且,函數bean是SimpleFunctionRegistry$FunctionInvocationWrapper的實例,支援函數組合和路由。例如, functionCatalog.lookup(“californiaSalaryCalculatorFn|defaultSalaryCalculatorFn”)將會傳回一個組合函數。此函數上的apply()方法相當於:
californiaSalaryCalculatorFn.andThen(defaultSalaryCalculatorFn).apply(employee)
這意味著來自加州的員工同時獲得州政府和額外的違約津貼。最後,我們來看看事件處理函數:
@Component
public class EmployeeSalaryHandler {
@Autowired
private FunctionCatalog functionCatalog;
@FunctionName("calculateSalaryWithSCF")
public HttpResponseMessage calculateSalaryWithSCF(
@HttpTrigger(
name="http",
methods = HttpMethod.POST,
authLevel = AuthorizationLevel.ANONYMOUS)HttpRequestMessage<Optional<Employee>> employeeHttpRequestMessage,
ExecutionContext executionContext
) {
Employee employeeRequest = employeeHttpRequestMessage.getBody().get();
executionContext.getLogger().info("Salary of " + employeeRequest.getName() + " is:" + employeeRequest.getSalary());
EmployeeSalaryFunctionWrapper employeeSalaryFunctionWrapper = new EmployeeSalaryFunctionWrapper(functionCatalog);
Function<Employee, Employee> cityBasedSalaryFunction = employeeSalaryFunctionWrapper.getCityBasedSalaryFunction(employeeRequest);
Employee employee = cityBasedSalaryFunction.apply(employeeRequest);
executionContext.getLogger().info("Final salary of " + employee.getName() + " is:" + employee.getSalary());
return employeeHttpRequestMessage.createResponseBuilder(HttpStatus.OK)
.body(employee)
.build();
}
}
與上一節討論的calcuateSalary()方法不同, calculateSalaryWithSCF()使用自動連接到類別的FunctionCatalog物件。
5. 部署並執行應用程式
我們將使用 Maven 在 Azure Functions 上編譯、打包和部署應用程式。讓我們從 IntelliJ 運行 Maven 目標:
部署成功後,功能將顯示在 Azure 入口網站上:
最後,從 Azure 入口網站取得它們的端點後,我們可以呼叫它們並檢查結果:
此外,可以在Azure門戶上確認函數呼叫:
六、結論
在本文中,我們學習如何使用 Spring Cloud Function 框架開發 Java Azure Function 應用程式。此框架允許使用基本的 Spring 依賴注入功能。此外, FunctionCatalog類別也提供有關組合和路由等函數的功能。雖然與低階 Java Azure Function 函式庫相比,該框架可能會增加一些開銷,但它提供了顯著的設計優勢。因此,只有在仔細評估應用程式的效能需求後才應採用。與往常一樣,本文中使用的程式碼可以在 GitHub 上找到。