介绍
Spring Cloud Function 是一个使用函数实现业务逻辑的框架,允许使用相同的代码执行 Web 端点、流处理和任务,而不依赖于执行环境。
您可以在 AWS Lambda 或 Azure Functions 等无服务器环境中运行它。
关键是您可以在使用 Spring 的便捷特性的同时开发功能。
在本文中,我将尝试 Spring Cloud Functions × Azure Functions。
由于使用 Maven 的示例很多,我想使用 Gradle 创建一个项目。
创建项目
弹簧初始化创建一个 Spring Boot 项目模板。
Project : Gradle Project
Language : Java
Packaging: Jar
Java : 11 or 8 (17ではデプロイできなかった: 2022/11/02 時点)
その他は任意
设置文件
修改生成设置,以便可以使用 Azure Functions 的功能。
plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "com.microsoft.azure.azurefunctions" version "1.11.0" // 追加
id 'java'
}
group = 'azure.functions'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
compileJava.options.encoding = 'UTF-8' // 追加
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure:3.2.7' // 追加
implementation 'com.microsoft.azure.functions:azure-functions-java-library:2.1.0' // 追加
// ↓は任意
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
// ↓追加
jar {
enabled = true
// jarファイル名を指定しないと、"${rootProject.name}-${version}-plan.jar"みたいなファイル名になって
// 実行時に「jarが見つからない」と怒られたので、明示的に指定
archiveFileName = "${rootProject.name}-${version}.jar"
manifest {
// メインクラスを指定
attributes 'Main-Class' : 'azure.functions.springcloudapp.SpringCloudApplication'
}
}
// Azureへのデプロイ設定
azurefunctions {
resourceGroup = 'xxxxxxxxxxxxx' // リソースグループ名
appName = 'springcloudapp' // Functionアプリ名
region = 'eastus' // リージョン
appServicePlanName = 'xxxxxxxxxxxxx' // App Service Plan名
// ↑他にも色々リソースの指定ができます
runtime {
os = 'windows' // 'linux'にしたらなぜかデプロイできなかった
}
appSettings {
WEBSITE_RUN_FROM_PACKAGE = '1'
FUNCTIONS_EXTENSION_VERSION = '~4'
FUNCTIONS_WORKER_RUNTIME = 'java'
MAIN_CLASS = 'azure.functions.springcloudapp.SpringCloudApplication'
}
auth {
type = 'azure_cli'
}
// ローカルでデバッグするときは必要
localDebug = 'transport=dt_socket,server=y,suspend=n,address=5005'
deployment {
type = 'run_from_blob'
}
}
// ↑追加
将以下文件添加到项目根目录
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
}
}
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "java",
"MAIN_CLASS": "azure.functions.springcloudapp.SpringCloudApplication",
"AzureWebJobsDashboard": ""
}
}
函数创建
粗略地说,您需要创建 Handler 来处理每个触发器和 Function,这是实际的逻辑。
使用 Handler → Call Function 接收请求。
处理程序创建
创建一个处理程序类来处理触发器。
我在这里使用 HttpTrigger。
// FunctionInvokerというクラスを継承する
public class CreateTodoHandler extends FunctionInvoker<TodoDto, TodoDto> {
@FunctionName("saveTodo") // デプロイ時の関数アプリ名
public HttpResponseMessage saveTodo(
// @HttpTriggerにHTTPの受付情報を設定する
@HttpTrigger(
name = "req",
methods = { HttpMethod.POST },
route = "todos",
authLevel = AuthorizationLevel.FUNCTION
) HttpRequestMessage<TodoDto> request,
ExecutionContext context
) {
final TodoDto payload = request.getBody();
if (payload == null) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST).build();
}
// handleRequestメソッドを呼び出すと別で定義したFunctionが呼び出されて結果が返る
// @FunctionNameで指定した名前でDIコンテナ内のFunctionを検索する
final TodoDto todoCreated = handleRequest(payload, context);
return request
.createResponseBuilder(HttpStatus.CREATED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header(
HttpHeaders.LOCATION,
UriComponentsBuilder
.fromUri(request.getUri())
.path("/{id}")
.buildAndExpand(todoCreated.getId())
.toString()
)
.body(todoCreated)
.build();
}
}
函数创建
实现实际的逻辑部分。
有两种写法。 (我个人更喜欢第一个)
-
用
@Bean
定义一个返回函数类型的方法 -
用
@Component
创建Funtion接口的实现类
*除了功能,供应商和消费者也是可能的TodoFunction.java(@Bean 版本)@Component @RequiredArgsConstructor public class TodoFunction { // DIも使える private final TodoService todoService; // Handlerの@FunctionNameで指定した名前と同じ名前でDIコンテナに登録する // デフォルトではメソッド名で登録されるので、メソッド名を合わせておけば@Beanで明示的に名前指定しなくてもOK @Bean("saveTodo") public Function<TodoDto, TodoDto> saveTodo() { return (payload) -> { return todoService.saveTodo(payload); }; } // 複数Functionを定義する場合はメソッドを追加する }
TodoFunction.java(函数实现类版本)// Handlerの@FunctionNameで指定した名前と同じ名前でDIコンテナに登録する // デフォルトではクラス名(先頭は小文字)で登録される // ※この場合だとクラス名が「SaveTodo」だったら@Componentで明示的に名前指定しなくてもOK @Component("saveTodo") @RequiredArgsConstructor public class TodoFunction implements Function<TodoDto, TodoDto> { private final TodoService todoService; @Override public TodoDto apply(TodoDto payload) { return todoService.saveTodo(payload); } } // 複数Functionを定義する場合はクラスを追加する
本地调试
在项目的根目录下执行以下命令。
./gradlew azureFunctionsRun
部署到 Azure
在项目的根目录下执行以下命令。
./gradlew azureFunctionsDeploy
奖金
访问 ExecutionContext
您可以在 Handler 的触发方法中接收
ExecutionContext
。
我认为这将主要用于日志记录。context.getLogger().info("hogehoge");
在Function端使用这个上下文有点麻烦,但是需要使用
Message
。
这有点不方便,尽管它似乎很有可能登录到 Function 端。鲷鱼 c 地面温度。爪哇// Functionの入力をMessage(org.springframework.messaging.Message)にする @Bean public Function<Message<TodoDto>, TodoDto> saveTodo() { return message -> { // MessageのHeadersに"executionContext"というキーでExecutionContextがセットされてくる ExecutionContext context = ExecutionContext.class.cast(message.getHeaders().get("executionContext")); context.getLogger().info("Invoke saveTodo"); return todoService.saveTodo(message.getPayload()); }; }
每个Function都写这个比较麻烦,所以我创建了一个接口,将Function接口封装起来,使其通用。
天蓝色 c 地面温度。爪哇public interface AzureFunction<I, O> extends Function<Message<I>, O> { public static final String CONTEXT_HEADER_KEY = "executionContext"; @Override default O apply(Message<I> message) { ExecutionContext context = ExecutionContext.class.cast( message.getHeaders().get((CONTEXT_HEADER_KEY)) ); return apply(message.getPayload(), context); } /** handleRequestの引数とExecutionContextを受け取るメソッドを定義 */ O apply(I payload, ExecutionContext context); }
用法
鲷鱼 c 地面温度。爪哇@Bean public AzureFunction<TodoDto, TodoDto> saveTodo() { return (payload, context) -> { context.getLogger().info("Invoke saveTodo"); return todoService.saveTodo(payload); }; }
更容易使用......(?)
但是,如果函数的参数是Publisher(Mono,Flux),则无法很好地转换类型。
请让我知道是否有更聪明的方法。调用函数时的类型转换
当你用Handler的
handleRequest
方法调用Function时,它会根据Function的参数自动转换类型。
当我粗略检查时,它是这样的。输入类型
(handleRequest 的参数)转换类型
(函数论证)可兑换性 评论 T。 集合<T> ○ 转换为具有 1 个元素的集合 T。 东西<T> ○ T。 通量<T> ○ 用 1 个元素转换为通量 T。 消息<T> ○ 输入值可以用 Message::getPayload
获取集合<T> T。 ○ 函数针对Collection的数量执行 handleRequest
的返回值为 ArrayList<O1成为 >
但是,如果 O 是一个集合,它就会变成一个平面列表。通量<T> T。 ○ 对 Flux 的数量执行函数 handleRequest
的返回值与↑相同消息<T> 消息<T> × 消息<T> T。 ○ 传递了 Message::getPayload
的值HttpRequestMessage<T> HttpRequestMessage<T> × HttpRequestMessage<T> T。 ○ 传递了 HttpRequestMessage::getBody
的值handleRequest
返回HttpResponseMessage
HttpTrigger 以外的触发器
貌似HttpTrigger以外的触发器都可以正常使用了。
看起来您可以使用以下触发器:
(触发器本身是由 Azure 提供的,不是 Spring Cloud Function 提供的)- Blob 触发器
- 定时器触发器
- 卡夫卡触发器
- 队列触发器
- 预热触发器
- EventHub 触发器
- 事件网格触发器
- ServiceBus 主题触发器
- ServiceBusQueueTrigger
我尝试使用
TimerTrigger
和EventGridTrigger
。- Function1 使用 TimerTrigger 发出 EventGrid 事件
- Function2 接收 EventGrid 事件并请求 Function3 (API)
- Function3 接收 Http 请求并将数据注册到 Cosmos DB
创造一些你不太了解的东西。
Function1 定时器触发public class TimerHandler extends FunctionInvoker<String, EventGridEvent> { public static final String FUNCTION_NAME = "timer"; @FunctionName(FUNCTION_NAME) public void timer( // 1分毎に発火 @TimerTrigger(name = "timer", schedule = "* * * * *") String timerInfo, @EventGridOutput( name = "outputEvent", topicEndpointUri = "MyEventGridTopicUriSetting", topicKeySetting = "MyEventGridTopicKeySetting" ) OutputBinding<EventGridEvent> outputEvent, ExecutionContext context ) { // handleRequestの戻り値をoutputEventにセットしている handleOutput(timerInfo, outputEvent, context); } } @Component @RequiredArgsConstructor public class TimerFunction { @Bean public AzureFunction<String, EventGridEvent> timer() { return (payload, context) -> { context.getLogger().info("Invoke timer: " + payload); final EventGridEvent document = new EventGridEvent(); document.setId(UUID.randomUUID().toString()); document.setEventType("MyCustomEvent"); document.setEventTime(DateTimeUtil.formatDateTime(LocalDateTime.now())); document.setDataVersion("1.0"); document.setSubject("EventGridSample"); document.setData(new TodoDto(null, "Content", false, null)); return document; }; } }
Function2 事件网格触发器public class EventGridHandler extends FunctionInvoker<EventSchema, TodoDto> { public static final String FUNCTION_NAME = "eventGrid"; @FunctionName(FUNCTION_NAME) public void eventGrid( @EventGridTrigger(name = "event") EventSchema event, ExecutionContext context ) { final TodoDto output = handleRequest(event, context); context.getLogger().info(output.toString()); } } @Component @RequiredArgsConstructor public class EventGridFunction { private final RestTemplate restTemplate; @Value("${FUNCTION_ENDPOINT}") private String baseEndpoint; @Value("${FUNCTION_KEY}") private String functionKey; @Bean public AzureFunction<EventSchema, TodoDto> eventGrid() { return (event, context) -> { context.getLogger().info("Event: " + event); // Function3 API 呼び出し RequestEntity<TodoDto> request = RequestEntity .post(baseEndpoint + "/todos?code={code}", functionKey) .contentType(MediaType.APPLICATION_JSON) .body(event.getData()); ResponseEntity<TodoDto> response = restTemplate.exchange( request, TodoDto.class ); return response.getBody(); }; } }
酱
概括
通过使用 Spring Cloud Function,您将能够使用 Spring 函数开发 Functions。
就个人而言,我很高兴能够进行 DI。
也可以使用 HttpTrigger 以外的触发器,所以我想尝试各种事情。
(貌似官方库也在开发中,所以以后可能会默认使用DI。)参考
-
O
是函数的返回类型↩
-
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308632750.html