马克·费舍尔、戴夫·赛尔、奥列格·朱拉考斯基、安舒尔·梅赫拉、丹·多布林

4.0.5


介绍

Spring Cloud Function 是一个具有以下高级目标的项目:

  • 通过函数推动业务逻辑的实现。

  • 将业务逻辑的开发生命周期与任何特定的运行时目标解耦,以便相同的代码可以作为 Web 端点、流处理器或任务运行。

  • 支持跨无服务器提供商的统一编程模型,以及独立运行(本地或在 PaaS 中)的能力。

  • 在无服务器提供程序上启用 Spring Boot 功能(自动配置、依赖项注入、指标)。

它抽象了所有传输细节和基础设施,使开发人员能够保留所有熟悉的工具和流程,并专注于业务逻辑。

这是一个完整的、可执行的、可测试的 Spring Boot 应用程序(实现简单的字符串操作):

@SpringBootApplication
public class Application {

  @Bean
  public Function<String, String> uppercase() {
    return value -> value.toUpperCase();
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

它只是一个 Spring Boot 应用程序,因此可以在本地和 CI 构建中构建、运行和测试它,就像任何其他 Spring Boot 应用程序一样。来自Project Reactor 的Reactive Streams 。Function_ 该功能可以通过 HTTP 或消息传递来访问。java.utilFlux Publisher

Spring Cloud Function 具有以下特点:

  • 编程风格的选择——反应式、命令式或混合式。

  • 函数组合和适配(例如,组合命令式函数和响应式函数)。

  • 支持具有多个输入和输出的反应式函数,允许函数处理合并、连接和其他复杂的流操作。

  • 输入和输出的透明类型转换。

  • 打包用于部署的函数,特定于目标平台(例如,Project Riff、AWS Lambda 等)

  • 将功能作为 HTTP 端点等向外界公开的适配器。

  • 使用隔离的类加载器部署包含此类应用程序上下文的 JAR 文件,以便可以将它们打包在单个 JVM 中。

  • 适用于AWS LambdaAzureGoogle Cloud Functions以及可能其他“无服务器”服务提供商的适配器。

Spring Cloud 在非限制性 Apache 2.0 许可证下发布。如果您想为文档的这一部分做出贡献或者发现错误,请在github项目中查找源代码和问题跟踪器。

入门

从命令行构建(并“安装”示例):

$ ./mvnw clean install

(如果您喜欢 YOLO,请添加-DskipTests。)

运行示例之一,例如

$ java -jar spring-cloud-function-samples/function-sample/target/*.jar

这将运行应用程序并通过 HTTP 公开其函数,因此您可以将字符串转换为大写,如下所示:

$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d Hello
HELLO

您可以Flux<String>通过用换行符分隔多个字符串 (a ) 来转换它们

$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d 'Hello
> World'
HELLOWORLD

(您可以QJ在终端中使用在类似的文字字符串中插入新行。)

编程模型

函数目录和灵活的函数签名

Spring Cloud Function 的主要功能之一是适应和支持用户定义函数的一系列类型签名,同时提供一致的执行模型。这就是为什么所有用户定义的函数都被转换为规范表示的原因FunctionCatalog

虽然用户通常根本不必关心这些FunctionCatalog,但了解用户代码中支持哪些类型的函数很有用。

同样重要的是要了解 Spring Cloud Function 为Project Reactor提供的反应式 API 提供一流的支持,允许反应式基元(例如 和 )用作用户定义函数中的类型,Mono从而在为函数实现选择编程模型时提供更大的灵活性。Flux反应式编程模型还可以为使用命令式编程风格很难甚至不可能实现的功能提供功能支持。有关这方面的更多信息,请阅读“函数数量”部分。

Java 8 函数支持

Spring Cloud Function 包含并构建在 Java 定义的 3 个核心函数式接口之上,自 Java 8 起我们就可以使用它们。

  • 供应商<O>

  • 函数<I,O>

  • 消费者<I>

为了避免不断提及SupplierFunction我们Consumer将在本手册的其余部分中酌情将它们称为功能性 bean。

简而言之,应用程序上下文中的任何功能性 bean 都将延迟注册到FunctionCatalog. 这意味着它可以受益于本参考手册中描述的所有附加功能。

在最简单的应用程序中,您所需要做的就是声明@BeantypeSupplier或在应用程序配置中。然后您可以根据名称访问和查找特定函数。FunctionConsumerFunctionCatalog

例如:

@Bean
public Function<String, String> uppercase() {
    return value -> value.toUpperCase();
}

. . .

FunctionCatalog catalog = applicationContext.getBean(FunctionCatalog.class);
Function uppercase =  catalog.lookup(“uppercase”);

重要的是要理解,鉴于这uppercase是一个 bean,您当然可以直接从 中获取它ApplicationContext,但您将获得的只是您声明的 bean,没有 SCF 提供的任何额外功能。当您通过 查找函数时FunctionCatalog,您将收到的实例将使用本手册中描述的附加功能(即类型转换、组合等)进行包装(插装)。此外,重要的是要了解典型用户不会直接使用 Spring Cloud Function。相反,典型的用户实现 Java 的Function/Supplier/Consumer想法是在不同的执行上下文中使用它,而不需要额外的工作。例如,相同的 java 函数可以通过 Spring Cloud Function 提供的适配器以及使用 Spring Cloud Function 作为核心编程模型的其他框架(例如Spring Cloud Stream)表示为REST 端点流消息处理程序AWS Lambda等。摘要 Spring Cloud Function 为 java 函数提供了可在各种执行上下文中使用的附加功能。

函数定义

虽然前面的示例向您展示了如何以编程方式在 FunctionCatalog 中查找函数,但在 Spring Cloud Function 被另一个框架(例如 Spring Cloud Stream)用作编程模型的典型集成案例中,您可以通过属性声明要使用哪些函数spring.cloud.function.definition。了解在发现FunctionCatalog. 例如,如果 中 只有一个 Function bean ApplicationContext,则spring.cloud.function.definition通常不需要该属性,因为 中 中的单个函数FunctionCatalog可以通过空名称或任何名称进行查找。例如,假设这uppercase是目录中唯一的函数,则可以将其查找为catalog.lookup(null), catalog.lookup(“”)catalog.lookup(“foo”) 也就是说,对于您使用 Spring Cloud Stream 等框架的情况,使用spring.cloud.function.definition它是最佳实践,建议始终使用spring.cloud.function.definition属性。

例如,

spring.cloud.function.definition=uppercase

过滤不符合条件的函数

典型的应用程序上下文可能包括有效的 java 函数的 beans,但不打算成为注册的候选者FunctionCatalog。此类 Bean 可以是来自其他项目的自动配置,也可以是任何其他有资格成为 Java 函数的 Bean。该框架提供了对已知 bean 的默认过滤,这些 bean 不应成为函数目录注册的候选者。您还可以通过使用属性提供以逗号分隔的 bean 定义名称列表来向此列表添加其他 bean spring.cloud.function.ineligible-definitions

例如,

spring.cloud.function.ineligible-definitions=foo,bar

供应商

供应商可以是被动的Supplier<Flux<T>> 也可以是命令性Supplier<T>。从调用的角度来看,这对于此类供应商的实现者应该没有什么影响。然而,当在框架(例如Spring Cloud Stream)中使用时,供应商(尤其是响应式供应商)通常用于表示流的来源,因此它们被调用一次以获得消费者可以订阅的流(例如 Flux)。换句话说,这些供应商相当于无限的流。然而,相同的反应式供应商也可以表示有限流(例如,轮询的 JDBC 数据的结果集)。在这些情况下,此类反应性供应商必须连接到底层框架的某种轮询机制。

为了帮助实现这一点,Spring Cloud Function 提供了一个标记注释, org.springframework.cloud.function.context.PollableBean以表明此类供应商生成有限流并且可能需要再次轮询。也就是说,重要的是要了解 Spring Cloud Function 本身没有为此注释提供任何行为。

此外,PollableBean注释还公开了一个splittable属性,以表明生成的流需要拆分(请参阅Splitter EIP

这是示例:

@PollableBean(splittable = true)
public Supplier<Flux<String>> someSupplier() {
	return () -> {
		String v1 = String.valueOf(System.nanoTime());
		String v2 = String.valueOf(System.nanoTime());
		String v3 = String.valueOf(System.nanoTime());
		return Flux.just(v1, v2, v3);
	};
}

功能

函数也可以以命令式或响应式方式编写,但与供应商和消费者不同,实现者无需特殊考虑,只需理解在Spring Cloud Stream等框架中使用时,响应式函数仅被调用一次以传递引用到流(Flux 或 Mono)并且每个事件调用一次命令式。

消费者

Consumer 有点特殊,因为它有一个void返回类型,这意味着至少可能是阻塞的。您很可能不需要编写Consumer<Flux<?>>,但如果您确实需要这样做,请记住订阅输入通量。

功能组成

函数组合是一种允许将多个函数组合成一个的功能。核心支持基于自 Java 8 起提供的Function.andThen(..) 支持的函数组合功能。但是除此之外,我们还提供了一些附加功能。

声明式函数组合

此功能允许您在提供属性时使用|(管道)或(逗号)分隔符以声明方式提供组合指令。,spring.cloud.function.definition

例如

--spring.cloud.function.definition=uppercase|reverse

uppercase在这里,我们有效地提供了单个函数的定义,该函数本身是 function和 function的组合reverse事实上,这就是属性名称是定义而不是名称的原因之一,因为函数的定义可以是多个命名函数的组合。正如前面提到的,您可以使用,管道代替管道(例如…​definition=uppercase,reverse)。

组合非函数

Spring Cloud Function 还支持使用ConsumerFunctionFunction组合Supplier Consumer。这里重要的是理解这些定义的最终结果。将供应商与功能组合仍然会产生供应商,而将供应商与消费者组合将有效地呈现可运行状态。遵循相同的逻辑,将 Function 与 Consumer 组合将产生 Consumer。

当然,你不能组合不可组合的东西,例如消费者和功能,消费者和供应商等。

功能路由和过滤

自 2.2 版本起,Spring Cloud Function 提供了路由功能,允许您调用单个函数,该函数充当您希望调用的实际函数的路由器。此功能在某些 FAAS 环境中非常有用,在这些环境中,维护多个函数的配置可能很麻烦或暴露更多功能超过一个功能是不可能的。

RoutingFunctionFunctionCatalog中 以名称注册functionRouter。为了简单性和一致性,您还可以参考RoutingFunction.FUNCTION_NAME常量。

该函数具有以下签名:

public class RoutingFunction implements Function<Object, Object> {
. . .
}

路由指令可以通过多种方式传送。我们支持通过消息头、系统属性以及可插入策略提供指令。让我们看看一些细节

消息路由回调

MessageRoutingCallback是一种帮助确定路由函数定义名称的策略。

public interface MessageRoutingCallback {
	FunctionRoutingResult routingResult(Message<?> message);
	. . .
}

您需要做的就是实现并将其注册为一个由RoutingFunction. 例如:

@Bean
public MessageRoutingCallback customRouter() {
	return new MessageRoutingCallback() {
		@Override
		public FunctionRoutingResult routingResult(Message<?> message) {
			return new FunctionRoutingResult((String) message.getHeaders().get("func_name"));
		}
	};
}

在前面的示例中,您可以看到一个非常简单的实现,MessageRoutingCallback它从传入消息的消息头中确定函数定义 ,并返回包含要调用的函数定义的func_name实例。FunctionRoutingResult

消息头

如果输入参数的类型为,您可以通过设置或消息头Message<?>之一来传达路由指令 。顾名思义,该属性依赖于 Spring 表达式语言 (SpEL)。对于更多静态情况,您可以使用标头,它允许您提供单个函数的名称(例如,)或组合指令(例如,)。对于更动态的情况,您可以使用标头并提供应解析为函数定义的 SpEL 表达式(如上所述)。spring.cloud.function.definitionspring.cloud.function.routing-expressionspring.cloud.function.routing-expressionspring.cloud.function.definition…​definition=foo…​definition=foo|bar|bazspring.cloud.function.routing-expression

SpEL 求值上下文的根对象是实际的输入参数,因此在 的情况下,Message<?>您可以构造可以访问 和 的表达式payloadheaders例如,spring.cloud.function.routing-expression=headers.function_name)。
SpEL 允许用户提供要执行的 Java 代码的字符串表示形式。鉴于 可以spring.cloud.function.routing-expression通过消息标头提供,意味着设置此类表达式的能力可能会暴露给最终用户(即,使用 Web 模块时的 HTTP 标头),这可能会导致一些问题(例如,恶意代码)。SimpleEvaluationContext为了管理这一点,通过消息头传入的所有表达式将仅针对功能有限的表达式进行评估,并且设计为仅评估上下文对象(在我们的例子中为消息)。另一方面,所有通过属性或系统变量设置的表达式都根据 进行计算StandardEvaluationContext,这使得 Java 语言具有充分的灵活性。虽然通过系统/应用程序属性或环境变量设置表达式通常被认为是安全的,因为在正常情况下它不会暴露给最终用户,但在某些情况下,可见性以及更新系统、应用程序和环境变量的能力确实会暴露通过由某些 Spring 项目或第三方提供的 Spring Boot Actuator 端点或最终用户的自定义实现提供给最终用户。必须使用行业标准 Web 安全实践来保护此类端点。Spring Cloud Function 不公开任何此类端点。

在特定的执行环境/模型中,适配器负责 spring.cloud.function.definition和/或spring.cloud.function.routing-expression通过消息头进行转换和通信。例如,当使用spring-cloud-function-web时,您可以提供spring.cloud.function.definitionHTTP 标头,框架会将其以及其他 HTTP 标头作为消息标头进行传播。

应用程序属性

spring.cloud.function.definition 路由指令也可以通过或作为应用程序属性来传送spring.cloud.function.routing-expression。上一节中描述的规则也适用于此处。唯一的区别是您将这些指令作为应用程序属性提供(例如,--spring.cloud.function.definition=foo)。

重要的是要理解提供spring.cloud.function.definitionspring.cloud.function.routing-expression作为消息头仅适用于命令式函数(例如,Function<Foo, Bar>)。也就是说,我们只能使用命令式函数来路由每条消息。使用反应函数,我们无法 按消息路由。因此,您只能将路由指令作为应用程序属性提供。这都是关于工作单元的。在命令式函数中,工作单元是消息,因此我们可以基于这样的工作单元进行路由。对于反应式函数,工作单元是整个流,因此我们将仅根据通过应用程序属性提供的指令进行操作并路由整个流。

路由指令的优先级顺序

鉴于我们有多种提供路由指令的机制,因此在同时使用多种机制的情况下了解冲突解决的优先级非常重要,因此顺序如下:

  1. MessageRoutingCallback(如果函数是命令式的,则无论是否定义了其他内容,都会接管)

  2. 消息标头(如果功能是必需的且未MessageRoutingCallback提供)

  3. 应用程序属性(任何功能)

无法路由的消息

如果目录中的路由功能不可用,您将收到一条异常消息。

在某些情况下,您不需要这种行为,并且您可能希望有一些可以处理此类消息的“包罗万象”类型的函数。为了实现这一目标,框架提供了org.springframework.cloud.function.context.DefaultMessageRoutingHandler策略。您需要做的就是将其注册为 bean。它的默认实现将简单地记录消息不可路由的事实,但将允许消息流继续进行而不会出现异常,从而有效地删除不可路由的消息。如果您想要更复杂的东西,您需要做的就是提供您自己的该策略的实现并将其注册为 bean。

@Bean
public DefaultMessageRoutingHandler defaultRoutingHandler() {
	return new DefaultMessageRoutingHandler() {
		@Override
		public void accept(Message<?> message) {
			// do something really cool
		}
	};
}

功能过滤

过滤是一种只有两条路径的路由类型——“走”或“丢弃”。就函数而言,这意味着您只想在某些条件返回“true”时调用某个函数,否则您想丢弃输入。然而,当谈到丢弃输入时,它在应用程序上下文中的含义有多种解释。例如,您可能想要记录它,或者您可能想要维护丢弃消息的计数器。您可能也什么都不想做。由于这些不同的路径,我们不提供如何处理丢弃消息的通用配置选项。相反,我们只是建议定义一个简单的 Consumer 来表示“丢弃”路径:

@Bean
public Consumer<?> devNull() {
   // log, count or whatever
}

现在,您可以让实际上只有两条路径的路由表达式有效地成为过滤器。例如:

--spring.cloud.function.routing-expression=headers.contentType.toString().equals('text/plain') ? 'echo' : 'devNull'

每条不符合“echo”函数标准的消息都将被转到“devNull”,您可以简单地对其执行任何操作。该签名Consumer<?>还将确保不会尝试进行类型转换,从而几乎没有执行开销。

当处理反应性输入(例如,Publisher)时,路由指令只能通过函数属性提供。这是由于反应函数的性质所致,反应函数仅被调用一次以传递发布者,其余部分由反应器处理,因此我们无法访问和/或依赖通过单个值(例如消息)通信的路由指令。

多个路由器

默认情况下,框架将始终具有如前面部分所述配置的单个路由功能。然而,有时您可能需要多个路由功能。在这种情况下,除了现有的 bean 实例之外,您还可以创建您自己的RoutingFunctionbean 实例,只要您为其指定一个除 之外的名称即可functionRouter

您可以将spring.cloud.function.routing-expressionspring.cloud.function.definition作为映射中的键/值对传递给 RoutinFunction。

这是一个简单的例子

@Configuration
protected static class MultipleRouterConfiguration {

	@Bean
	RoutingFunction mySpecialRouter(FunctionCatalog functionCatalog, BeanFactory beanFactory, @Nullable MessageRoutingCallback routingCallback) {
		Map<String, String> propertiesMap = new HashMap<>();
		propertiesMap.put(FunctionProperties.PREFIX + ".routing-expression", "'reverse'");
		return new RoutingFunction(functionCatalog, propertiesMap, new BeanFactoryResolver(beanFactory), routingCallback);
	}

	@Bean
	public Function<String, String> reverse() {
		return v -> new StringBuilder(v).reverse().toString();
	}

	@Bean
	public Function<String, String> uppercase() {
		return String::toUpperCase;
	}
}

以及演示其工作原理的测试

`

@Test
public void testMultipleRouters() {
	System.setProperty(FunctionProperties.PREFIX + ".routing-expression", "'uppercase'");
	FunctionCatalog functionCatalog = this.configureCatalog(MultipleRouterConfiguration.class);
	Function function = functionCatalog.lookup(RoutingFunction.FUNCTION_NAME);
	assertThat(function).isNotNull();
	Message<String> message = MessageBuilder.withPayload("hello").build();
	assertThat(function.apply(message)).isEqualTo("HELLO");

	function = functionCatalog.lookup("mySpecialRouter");
	assertThat(function).isNotNull();
	message = MessageBuilder.withPayload("hello").build();
	assertThat(function.apply(message)).isEqualTo("olleh");
}

输入/输出丰富

很多时候,您需要修改或优化传入或传出的消息,并保持代码中没有非功能性问题。您不想在业务逻辑内执行此操作。

您始终可以通过函数组合来完成它。这种方法有几个好处:

  • 它允许您将这种非功能性关注点隔离到一个单独的函数中,您可以将其与业务函数组合为函数定义。

  • 它为您提供了完全的自由(和危险),让您可以在传入消息到达实际业务功能之前修改哪些内容。

@Bean
public Function<Message<?>, Message<?>> enrich() {
    return message -> MessageBuilder.fromMessage(message).setHeader("foo", "bar").build();
}

@Bean
public Function<Message<?>, Message<?>> myBusinessFunction() {
    // do whatever
}

然后通过提供以下函数定义来编写您的函数enrich|myBusinessFunction

虽然所描述的方法是最灵活的,但它也是最复杂的,因为它需要您编写一些代码,使其成为一个 bean 或手动将其注册为函数,然后才能将其与业务函数组合,正如您可以从前面的例子。

但是,如果您尝试进行的修改(丰富)像前面的示例中那样微不足道,该怎么办?是否有更简单、更动态、可配置的机制来完成同样的任务?

从版本 3.1.3 开始,该框架允许您提供 SpEL 表达式来丰富进入函数的输入和函数输出的各个消息头。让我们以其中一个测试为例。

@Test
public void testMixedInputOutputHeaderMapping() throws Exception {
	try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
			SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run(
					"--logging.level.org.springframework.cloud.function=DEBUG",
					"--spring.main.lazy-initialization=true",
					"--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut1='hello1'",
					"--spring.cloud.function.configuration.split.output-header-mapping-expression.keyOut2=headers.contentType",
					"--spring.cloud.function.configuration.split.input-header-mapping-expression.key1=headers.path.split('/')[0]",
					"--spring.cloud.function.configuration.split.input-header-mapping-expression.key2=headers.path.split('/')[1]",
					"--spring.cloud.function.configuration.split.input-header-mapping-expression.key3=headers.path")) {

		FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
		FunctionInvocationWrapper function = functionCatalog.lookup("split");
		Message<byte[]> result = (Message<byte[]>) function.apply(MessageBuilder.withPayload("helo")
				.setHeader(MessageHeaders.CONTENT_TYPE, "application/json")
				.setHeader("path", "foo/bar/baz").build());
		assertThat(result.getHeaders().containsKey("keyOut1")).isTrue();
		assertThat(result.getHeaders().get("keyOut1")).isEqualTo("hello1");
		assertThat(result.getHeaders().containsKey("keyOut2")).isTrue();
		assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json");
	}
}

在这里您可以看到一个名为的属性input-header-mapping-expressionoutput-header-mapping-expression其前面是函数名称(即split),后面是要设置的消息头键的名称以及 SpEL 表达式的值。第一个表达式(针对“keyOut1”)是用单引号括起来的文字 SpEL 表达式,有效地将“keyOut1”设置为 value hello1。设置keyOut2为现有“contentType”标头的值。

您还可以在输入标头映射中观察到一些有趣的功能,其中我们实际上拆分了现有标头“路径”的值,根据索引将 key1 和 key2 的单独值设置为拆分元素的值。

如果由于某种原因所提供的表达式求值失败,则函数的执行将继续进行,就好像什么也没有发生一样。但是,您将在日志中看到警告消息,通知您相关信息
o.s.c.f.context.catalog.InputEnricher    : Failed while evaluating expression "hello1"  on incoming message. . .

如果您正在处理具有多个输入的函数(下一节),您可以在之后立即使用索引input-header-mapping-expression

--spring.cloud.function.configuration.echo.input-header-mapping-expression[0].key1=‘hello1'
--spring.cloud.function.configuration.echo.input-header-mapping-expression[1].key2='hello2'

函数数量

有时需要对数据流进行分类和组织。例如,考虑处理包含“订单”和“发票”的无组织数据的经典大数据用例,并且您希望每个数据都进入单独的数据存储。这就是函数数量(具有多个输入和输出的函数)支持发挥作用的地方。

让我们看一下此类函数的示例(完整的实现细节可 在此处获得),

@Bean
public Function<Flux<Integer>, Tuple2<Flux<String>, Flux<String>>> organise() {
	return flux -> ...;
}

鉴于 Project Reactor 是 SCF 的核心依赖项,我们正在使用它的 Tuple 库。元组通过向我们传达基数类型信息,为我们提供了独特的优势。两者在 SCSt 背景下都极其重要。基数让我们知道需要创建多少个输入和输出绑定并将其绑定到函数的相应输入和输出。了解类型信息可确保正确的类型转换。

此外,这也是绑定名称命名约定的“索引”部分发挥作用的地方,因为在此函数中,两个输出绑定名称是organise-out-0organise-out-1

重要提示:目前,函数数量支持以Function<TupleN<Flux<?>…​>, TupleN<Flux<?>…​>>复杂事件处理为中心的反应函数 ( ),其中事件汇合的评估和计算通常需要查看事件流而不是单个事件。

输入标头传播

在典型的场景中,输入消息头不会传播到输出,这是理所当然的,因为函数的输出可能是其他需要它自己的消息头集的东西的输入。然而,有时这种传播可能是必要的,因此 Spring Cloud Function 提供了多种机制来实现这一点。

首先,您始终可以手动复制标题。例如,如果您有一个带有接受Message并返回签名的函数Message(即Function<Message, Message>),您可以简单地、有选择地自行复制标头。请记住,如果您的函数返回 Message,除了正确转换其有效负载之外,框架不会对其执行任何操作。然而,这种方法可能有点乏味,特别是当您只想复制所有标头时。为了帮助解决此类情况,我们提供了一个简单的属性,允许您在要传播输入标头的函数上设置布尔标志。该物业是copy-input-headers.

例如,假设您有以下配置:

@EnableAutoConfiguration
@Configuration
protected static class InputHeaderPropagationConfiguration {

	@Bean
	public Function<String, String> uppercase() {
		return x -> x.toUpperCase();
	}
}

如您所知,您仍然可以通过向其发送消息来调用此函数(框架将负责类型转换和有效负载提取)

通过简单地设置spring.cloud.function.configuration.uppercase.copy-input-headerstrue,以下断言也将成立

Function<Message<String>, Message<byte[]>> uppercase = catalog.lookup("uppercase", "application/json");
Message<byte[]> result = uppercase.apply(MessageBuilder.withPayload("bob").setHeader("foo", "bar").build());
assertThat(result.getHeaders()).containsKey("foo");

类型转换(Content-Type协商)

内容类型协商是 Spring Cloud Function 的核心功能之一,因为它不仅允许将传入数据转换为函数签名声明的类型,而且可以在函数组合期间进行相同的转换,从而使其他方式不可组合(按类型) 可组合函数。

为了更好地理解内容类型协商背后的机制和必要性,我们使用以下函数作为示例来查看一个非常简单的用例:

@Bean
public Function<Person, String> personFunction {..}

前面示例中显示的函数需要一个Person对象作为参数,并生成 String 类型作为输出。如果使用 type 调用此类函数Person,则一切正常。但通常函数扮演传入数据处理程序的角色,这些数据通常以原始格式出现,例如 等。byte[]为了JSON String使框架成功地将传入数据作为参数传递给该函数,它必须以某种方式进行转换将传入数据转换为Person类型。

Spring Cloud Function 依赖于 Spring 的两种本机机制来实现这一点。

  1. MessageConverter - 将传入的消息数据转换为函数声明的类型。

  2. ConversionService - 将传入的非消息数据转换为函数声明的类型。

这意味着根据原始数据的类型(消息或非消息),Spring Cloud Function 将应用一种或其他机制。

对于大多数情况,当处理作为其他请求(例如 HTTP、消息传递等)的一部分调用的函数时,框架依赖于MessageConverters,因为此类请求已经转换为 Spring Message。换句话说,框架定位并应用适当的MessageConverter. 为了实现这一点,框架需要用户的一些指令。这些指令之一已由函数本身的签名(Person 类型)提供。因此,从理论上讲,这应该(并且在某些情况下)足够了。然而,对于大多数用例,为了选择合适的MessageConverter,框架需要额外的信息。缺少的部分是contentType标题。

此类标头通常作为消息的一部分,由最初创建此类消息的相应适配器注入。例如,HTTP POST 请求会将其内容类型 HTTP 标头复制到contentType消息的标头。

对于此类标头不存在的情况,框架依赖于默认内容类型application/json.

内容类型与参数类型

如前所述,为了让框架选择适当的MessageConverter,它需要参数类型和(可选)内容类型信息。选择适当参数的逻辑MessageConverter位于参数解析器中,该解析器在调用用户定义函数之前触发(此时框架已知实际参数类型)。如果参数类型与当前有效负载的类型不匹配,框架将委托给预配置的堆栈MessageConverters以查看其中是否有任何一个可以转换有效负载。

和参数类型的组合contentType是框架确定消息是否可以通过定位适当的MessageConverter. 如果没有MessageConverter找到合适的,则会抛出异常,您可以通过添加自定义来处理该异常MessageConverter (请参阅 参考资料User-defined Message Converters)。

不要期望Message仅基于contentType. 请记住, 是contentType目标类型的补充。这是一个提示,MessageConverter可能会考虑也可能不会考虑。

消息转换器

MessageConverters定义两个方法:

Object fromMessage(Message<?> message, Class<?> targetClass);

Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);

了解这些方法的契约及其用法非常重要,特别是在 Spring Cloud Stream 的上下文中。

fromMessage方法将传入类型转换Message为参数类型。其负载Message可以是任何类型,是否MessageConverter支持多种类型取决于其实际实现。

提供的消息转换器

如前所述,该框架已经提供了一个堆栈MessageConverters来处理最常见的用例。以下列表MessageConverters按优先顺序描述了所提供的(MessageConverter使用第一个有效的):

  1. JsonMessageConverter:在使用Jackson(默认)或 Gson 库的情况下,支持将 POJO 的有效负载转换Message为 POJO 。该消息转换器还知道参数(例如,application/json;type=foo.bar.Person)。这对于开发函数时可能不知道类型的情况很有用,因此函数签名可能看起来像or或。换句话说,对于类型转换,我们通常从函数签名派生类型。拥有 mime-type 参数可以让您以更动态的方式传达类型。contentTypeapplication/jsontypeFunction<?, ?>FunctionFunction<Object, Object>

  2. ByteArrayMessageConverter:支持在is的情况下将Messagefrom的有效负载转换为。它本质上是一种传递,主要是为了向后兼容而存在。byte[]byte[]contentTypeapplication/octet-stream

  3. StringMessageConverter:支持任何类型到Stringwhen contentTypeis的转换text/plain

当没有找到合适的转换器时,框架会抛出异常。发生这种情况时,您应该检查您的代码和配置,并确保您没有遗漏任何内容(即,确保您contentType通过使用绑定或标头提供了 a)。但是,最有可能的是,您发现了一些不常见的情况(例如自定义contentType),并且当前提供的堆栈MessageConverters 不知道如何转换。如果是这种情况,您可以添加自定义MessageConverter. 请参阅用户定义的消息转换器

用户定义的消息转换器

Spring Cloud Function 公开了一种定义和注册附加MessageConverters. 要使用它,请实现org.springframework.messaging.converter.MessageConverter并将其配置为@Bean. 然后将其附加到现有的“MessageConverter”堆栈中。

重要的是要了解自定义MessageConverter实现被添加到现有堆栈的头部。因此,自定义MessageConverter实现优先于现有的实现,这使您可以覆盖现有转换器以及添加到现有转换器。

以下示例演示如何创建消息转换器 bean 以支持名为 的新内容类型application/bar

@SpringBootApplication
public static class SinkApplication {

    ...

    @Bean
    public MessageConverter customMessageConverter() {
        return new MyCustomMessageConverter();
    }
}

public class MyCustomMessageConverter extends AbstractMessageConverter {

    public MyCustomMessageConverter() {
        super(new MimeType("application", "bar"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return (Bar.class.equals(clazz));
    }

    @Override
    protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
        Object payload = message.getPayload();
        return (payload instanceof Bar ? payload : new Bar((byte[]) payload));
    }
}

JSON 选项注意事项

在 Spring Cloud Function 中,我们支持 Jackson 和 Gson 机制来处理 JSON。为了您的利益,将其抽象化,org.springframework.cloud.function.json.JsonMapper它本身知道两种机制,并将使用您选择的一种或遵循默认规则。默认规则如下:

  • 类路径上的库就是要使用的机制。因此,如果您必须com.fasterxml.jackson.*使用类路径,则将使用 Jackson,如果您必须使用com.google.code.gson,则将使用 Gson。

  • 如果两者都有,则 Gson 将是默认值,或者您可以spring.cloud.function.preferred-json-mapper使用以下两个值之一设置属性:gsonjackson

也就是说,类型转换通常对开发人员来说是透明的,但是考虑到它org.springframework.cloud.function.json.JsonMapper也注册为 bean,如果需要,您可以轻松地将其注入到代码中。

Kotlin Lambda 支持

我们还提供对 Kotlin lambda 的支持(自 v2.0 起)。考虑以下:

@Bean
open fun kotlinSupplier(): () -> String {
    return  { "Hello from Kotlin" }
}

@Bean
open fun kotlinFunction(): (String) -> String {
    return  { it.toUpperCase() }
}

@Bean
open fun kotlinConsumer(): (String) -> Unit {
    return  { println(it) }
}

上面表示配置为 Spring beans 的 Kotlin lambda。每个签名都映射到 Java 中的 SupplierFunction和等价物Consumer,因此框架支持/识别签名。虽然 Kotlin 到 Java 映射的机制超出了本文档的范围,但重要的是要了解“Java 8 函数支持”部分中概述的相同签名转换规则也适用于此处。

要启用 Kotlin 支持,您所需要做的就是在类路径上添加 Kotlin SDK 库,这将触发适当的自动配置和支持类。

功能组件扫描

Spring Cloud Function 将在调用的包中扫描Function,Consumer和的实现(如果存在)。使用此功能,您可以编写不依赖于 Spring 的函数 - 甚至不需要注释。如果你想使用不同的包,你可以设置。您还可以使用完全关闭扫描。Supplierfunctions@Componentspring.cloud.function.scan.packagesspring.cloud.function.scan.enabled=false

独立网络应用程序

函数可以自动导出为 HTTP 端点。

spring-cloud-function-web模块具有自动配置功能,当它包含在 Spring Boot Web 应用程序(具有 MVC 支持)中时,该配置就会激活。spring-cloud-starter-function-web如果您只想获得简单的入门体验,还可以收集所有可选依赖项。

激活 Web 配置后,您的应用程序将有一个 MVC 端点(默认位于“/”上,但可使用 进行配置 spring.cloud.function.web.path),可用于访问应用程序上下文中的函数,其中函数名称成为 URL 路径的一部分。支持的内容类型是纯文本和 JSON。

重要的是要理解,虽然 SCF 提供了将功能 bean 导出为 REST 端点的能力,但它并不能替代 Spring MVC/WebFlux 等。它主要是为了适应无状态无服务器模式,在这种模式中,您只想通过以下方式公开一些无状态功能: HTTP。
方法 小路 要求 回复 地位

GET

/{供应商}

-

来自指定供应商的物品

200 好

POST

/{消费者}

JSON 对象或文本

镜像输入并将请求正文推送给消费者

202 已接受

PUT

/{消费者}

JSON 对象或文本

镜像输入并将请求正文推送给消费者

202 已接受

DELETE

/{消费者}

JSON 对象或文本

-

204 没有内容

POST

/{功能}

JSON 对象或文本

应用命名函数的结果

200 好

PUT

/{功能}

JSON 对象或文本

应用命名函数的结果

200 好

GET

/{功能}/{项目}

-

将项目转换为对象并返回应用函数的结果

200 好

如上表所示,端点的行为取决于方法以及传入请求数据的类型。当传入数据是单值,并且目标函数被声明为明显单值(即不返回集合或Flux)时,响应也将包含单值。对于多值响应,客户端可以通过发送 来请求服务器发送的事件流Accept: text/event-stream

使用输入和输出声明的函数和使用者Message<?>会将请求标头视为消息标头,并且输出消息标头将转换为 HTTP 标头。如果没有或为空,则消息的有效负载将为一个或空字符串。bodybody

当 POST 文本时,响应格式可能与 Spring Boot 2.0 及更早版本不同,具体取决于内容协商(提供内容类型并接受标头以获得最佳结果)。

请参阅测试功能应用程序以查看有关如何测试此类应用程序的详细信息和示例。

HTTP 请求参数

正如您从上表中注意到的,您可以将参数作为路径变量(即/{function}/{item})传递给函数。例如,http://localhost:8080/uppercase/foo将导致调用uppercase其输入参数为 的函数foo

虽然这是推荐的方法并且适合大多数用例,但有时您必须处理 HTTP 请求参数(例如,http://localhost:8080/uppercase/foo?name=Bill)框架将通过将 HTTP 请求参数存储在标头中来处理类似于 HTTP 标头的 HTTP 请求Message参数在标头键下http_request_param ,其值是Map请求参数,因此为了访问它们,您的函数输入签名应该接受Message类型(例如,Function<Message<String>, String>)。为了方便我们提供HeaderUtils.HTTP_REQUEST_PARAM常数。

功能映射规则

如果目录中只有一个函数(消费者等),则路径中的名称是可选的。换句话说,只要您只有uppercase目录中的功能 curl -H "Content-Type: text/plain" localhost:8080/uppercase -d hellocurl -H "Content-Type: text/plain" localhost:8080/ -d hello调用是相同的。

复合函数可以使用管道或逗号来分隔函数名称(管道在 URL 路径中是合法的,但在命令行上键入有点尴尬)。例如, curl -H "Content-Type: text/plain" localhost:8080/uppercase,reverse -d hello.

对于目录中有多个函数的情况,每个函数都将被导出并映射,函数名称是路径的一部分(例如,localhost:8080/uppercase)。spring.cloud.function.definition在这种情况下,您仍然可以通过提供属性将特定函数或函数组合映射到根路径

例如,

--spring.cloud.function.definition=foo|bar

上面的属性将组合 'foo' 和 'bar' 函数,并将组合函数映射到“/”路径。

相同的属性也适用于无法通过 URL 解析函数的情况。例如,您的 URL 可能是localhost:8080/uppercase,但没有任何uppercase功能。不过还有函数foobar. 因此,在这种情况下localhost:8080/uppercase将解决foo|bar。这对于使用 URL 来传达某些信息的情况尤其有用,因为将使用uri实际 URL 的值调用消息标头,使用户能够使用它进行评估和计算。

功能 过滤规则

在目录中有多个函数的情况下,可能只需要导出某些函数或函数组合。在这种情况下,您可以使用您打算导出的相同spring.cloud.function.definition属性列表函数,以 分隔;。请注意,在这种情况下,不会将任何内容映射到根路径,并且不会导出未列出的函数(包括组合)

例如,

--spring.cloud.function.definition=foo;bar

这只会导出函数foo和函数bar,而不管目录中有多少函数可用(例如,localhost:8080/foo)。

--spring.cloud.function.definition=foo|bar;baz

这只会导出函数组合foo|bar和函数baz,无论目录中有多少函数可用(例如,localhost:8080/foo,bar)。

使用 Spring Cloud Function 进行 CRUD REST

到目前为止,您应该清楚函数是作为 REST 端点导出的,并且可以使用各种 HTTP 方法进行调用。换句话说,单个函数可以通过 GET、POST、PUT 等触发。

然而,它并不总是可取的,而且肯定不符合 CRUD 概念。虽然 SCF 不支持也无意支持 Spring Web 堆栈的所有功能,但该框架确实提供了对 CRUD 映射的支持,其中单个函数可以映射到特定的 HTTP 方法。它是通过 spring.cloud.function.http.<method-name> 属性完成的。

例如,

spring.cloud.function.http.GET=uppercase;reverse;foo|bar
spring.cloud.function.http.POST=reverse
spring.cloud.function.http.DELETE=deleteById

正如您所看到的,这里我们使用与spring.cloud.function.definition属性“;”相同的规则将函数映射到各种 HTTP 方法。允许我们定义多个函数和“|” 表示函数组合。

独立流媒体应用程序

要从代理(例如 RabbitMQ 或 Kafka)发送或接收消息,您可以利用spring-cloud-stream项目及其与 Spring Cloud Function 的集成。请参阅Spring Cloud Stream参考手册的Spring Cloud Function部分以获取更多详细信息和示例。

部署打包功能

Spring Cloud Function 提供了一个“部署器”库,允许您使用隔离的类加载器启动 jar 文件(或展开的存档或一组 jar 文件)并公开其中定义的函数。这是一个非常强大的工具,例如,它允许您使函数适应一系列不同的输入输出适配器,而无需更改目标 jar 文件。无服务器平台通常内置此类功能,因此您可以将其视为此类平台中函数调用程序的构建块(实际上,Riff Java 函数调用程序使用此库)。

标准入口点是添加spring-cloud-function-deployer到类路径中,部署程序启动并查找一些配置来告诉它在哪里可以找到函数 jar。

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-function-deployer</artifactId>
	<version>${spring.cloud.function.version}</version>
</dependency>

用户至少必须提供spring.cloud.function.location包含函数的存档的 URL 或资源位置。它可以选择使用maven:前缀通过依赖项查找来定位工件(请参阅 参考资料FunctionProperties 以获得完整的详细信息)。Spring Boot 应用程序是从 jar 文件引导的,使用 来MANIFEST.MF定位启动类,这样标准的 Spring Boot fat jar 就可以很好地工作。如果目标 jar 可以成功启动,则结果是在主应用程序的FunctionCatalog. 注册的函数可以通过主应用程序中的代码应用,即使它是在隔离的类加载器中创建的(默认情况下)。

下面是部署包含“大写”函数并调用它的 JAR 的示例。

@SpringBootApplication
public class DeployFunctionDemo {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DeployFunctionDemo.class,
				"--spring.cloud.function.location=..../target/uppercase-0.0.1-SNAPSHOT.jar",
				"--spring.cloud.function.definition=uppercase");

		FunctionCatalog catalog = context.getBean(FunctionCatalog.class);
		Function<String, String> function = catalog.lookup("uppercase");
		System.out.println(function.apply("hello"));
	}
}

下面是使用 Maven URI 的示例(取自 中的测试之一FunctionDeployerTests):

@SpringBootApplication
public class DeployFunctionDemo {

	public static void main(String[] args) {
		String[] args = new String[] {
				"--spring.cloud.function.location=maven://oz.demo:demo-uppercase:0.0.1-SNAPSHOT",
				"--spring.cloud.function.function-class=oz.demo.uppercase.MyFunction" };

		ApplicationContext context = SpringApplication.run(DeployerApplication.class, args);
		FunctionCatalog catalog = context.getBean(FunctionCatalog.class);
		Function<String, String> function = catalog.lookup("myFunction");

		assertThat(function.apply("bob")).isEqualTo("BOB");
	}
}

请记住,Maven 资源(例如本地和远程存储库、用户、密码等)是使用默认 MavenProperties 解析的,它有效地使用本地默认值,并且适用于大多数情况。MavenProperties但是,如果您需要自定义,您可以简单地提供一个可以设置其他属性的类型的 bean (请参见下面的示例)。

@Bean
public MavenProperties mavenProperties() {
	MavenProperties properties = new MavenProperties();
	properties.setLocalRepository("target/it/");
	return properties;
}

支持的封装场景

目前 Spring Cloud Function 支持多种打包场景,为您在部署函数时提供最大的灵活性。

简单的JAR

此打包选项意味着不依赖于与 Spring 相关的任何内容。例如; 考虑这样的 JAR 包含以下类:

package function.example;
. . .
public class UpperCaseFunction implements Function<String, String> {
	@Override
	public String apply(String value) {
		return value.toUpperCase();
	}
}

您需要做的就是在部署此类包时指定location和属性:function-class

--spring.cloud.function.location=target/it/simplestjar/target/simplestjar-1.0.0.RELEASE.jar
--spring.cloud.function.function-class=function.example.UpperCaseFunction

在某些情况下,您可能希望将多个函数打包在一起。对于这种情况,您可以使用 spring.cloud.function.function-class属性列出多个类,并用 分隔它们;

例如,

--spring.cloud.function.function-class=function.example.UpperCaseFunction;function.example.ReverseFunction

在这里,我们确定了两个要部署的函数,现在我们可以通过名称(例如catalog.lookup("reverseFunction");)在函数目录中访问它们。

有关更多详细信息,请参考此处提供的完整示例。您还可以在FunctionDeployerTests中找到相应的测试。

  • 元件扫描*

从版本 3.1.4 开始,您可以通过功能组件扫描中描述的组件扫描功能来简化您的配置。如果将函数类放在名为 的包中functions,则可以省略spring.cloud.function.function-class属性,因为框架将自动发现函数类并将它们加载到函数目录中。请记住进行函数查找时要遵循的命名约定。例如,函数类将在 名称下functions.UpperCaseFunction可用。FunctionCatalogupperCaseFunction

Spring 启动 JAR

此打包选项意味着存在对 Spring Boot 的依赖关系,并且 JAR 是作为 Spring Boot JAR 生成的。也就是说,考虑到部署的 JAR 在隔离的类加载器中运行,与实际部署者使用的 Spring Boot 版本不会有任何版本冲突。例如; 考虑这样的 JAR 包含以下类(如果 Spring/Spring Boot 位于类路径上,则可能有一些额外的 Spring 依赖项):

package function.example;
. . .
public class UpperCaseFunction implements Function<String, String> {
	@Override
	public String apply(String value) {
		return value.toUpperCase();
	}
}

和以前一样,您需要做的就是在部署此类包时指定location和属性:function-class

--spring.cloud.function.location=target/it/simplestjar/target/simplestjar-1.0.0.RELEASE.jar
--spring.cloud.function.function-class=function.example.UpperCaseFunction

有关更多详细信息,请参考此处提供的完整示例。您还可以在FunctionDeployerTests中找到相应的测试。

Spring引导应用程序

此打包选项意味着您的 JAR 是完整的独立 Spring Boot 应用程序,具有托管 Spring bean 的功能。与之前一样,有一个明显的假设,即存在对 Spring Boot 的依赖,并且 JAR 是作为 Spring Boot JAR 生成的。也就是说,考虑到部署的 JAR 在隔离的类加载器中运行,与实际部署者使用的 Spring Boot 版本不会有任何版本冲突。例如; 考虑这样的 JAR 包含以下类:

package function.example;
. . .
@SpringBootApplication
public class SimpleFunctionAppApplication {

	public static void main(String[] args) {
		SpringApplication.run(SimpleFunctionAppApplication.class, args);
	}

	@Bean
	public Function<String, String> uppercase() {
		return value -> value.toUpperCase();
	}
}

鉴于我们正在有效地处理另一个 Spring 应用程序上下文,并且函数是 spring 管理的 bean,除了属性之外,location我们还指定definitionproperty 而不是function-class.

--spring.cloud.function.location=target/it/bootapp/target/bootapp-1.0.0.RELEASE-exec.jar
--spring.cloud.function.definition=uppercase

有关更多详细信息,请参考此处提供的完整示例。您还可以在FunctionDeployerTests中找到相应的测试。

此特定部署选项的类路径上可能有也可能没有 Spring Cloud Function。从部署者的角度来看,这并不重要。

功能性 Bean 定义

Spring Cloud Function 支持“函数式”风格的 bean 声明,适用于需要快速启动的小型应用程序。bean 声明的函数式风格是 Spring Framework 5.0 的一个特性,在 5.1 中得到了显着增强。

功能性 Bean 定义与传统 Bean 定义的比较

这是一个普通的 Spring Cloud Function 应用程序,具有熟悉的@Configuration声明@Bean风格:

@SpringBootApplication
public class DemoApplication {

  @Bean
  public Function<String, String> uppercase() {
    return value -> value.toUpperCase();
  }

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

现在对于功能 bean:用户应用程序代码可以重新转换为“功能”形式,如下所示:

@SpringBootConfiguration
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {

  public static void main(String[] args) {
    FunctionalSpringApplication.run(DemoApplication.class, args);
  }

  public Function<String, String> uppercase() {
    return value -> value.toUpperCase();
  }

  @Override
  public void initialize(GenericApplicationContext context) {
    context.registerBean("demo", FunctionRegistration.class,
        () -> new FunctionRegistration<>(uppercase())
            .type(FunctionTypeUtils.functionType(String.class, String.class)));
  }

}

主要区别是:

  • 主要类是一个ApplicationContextInitializer.

  • 这些@Bean方法已转换为调用context.registerBean()

  • @SpringBootApplication替换为 , @SpringBootConfiguration表示我们未启用 Spring Boot 自动配置,但仍将该类标记为“入口点”。

  • 来自SpringApplicationSpring Boot 已替换为 FunctionalSpringApplication来自 Spring Cloud Function(它是一个子类)。

您在 Spring Cloud Function 应用程序中注册的业务逻辑 bean 类型为FunctionRegistration。这是一个包装器,其中包含函数以及有关输入和输出类型的信息。在@Bean 应用程序的形式中,可以反射性地导出信息,但在功能 bean 注册中,除非我们使用FunctionRegistration.

使用ApplicationContextInitializerand的另一种方法FunctionRegistration是让应用程序本身实现Function(orConsumerSupplier)。示例(相当于上面):

@SpringBootConfiguration
public class DemoApplication implements Function<String, String> {

  public static void main(String[] args) {
    FunctionalSpringApplication.run(DemoApplication.class, args);
  }

  @Override
  public String apply(String value) {
    return value.toUpperCase();
  }

}

如果您添加一个单独的、独立的类型类并使用该方法的替代形式Function注册它,它也会起作用。最主要的是,泛型类型信息可以在运行时通过类声明获得。SpringApplicationrun()

假设你有

@Component
public class CustomFunction implements Function<Flux<Foo>, Flux<Bar>> {
	@Override
	public Flux<Bar> apply(Flux<Foo> flux) {
		return flux.map(foo -> new Bar("This is a Bar object from Foo value: " + foo.getValue()));
	}

}

您可以这样注册:

@Override
public void initialize(GenericApplicationContext context) {
		context.registerBean("function", FunctionRegistration.class,
				() -> new FunctionRegistration<>(new CustomFunction()).type(CustomFunction.class));
}

功能性 Bean 声明的局限性

与整个 Spring Boot 相比,大多数 Spring Cloud Function 应用程序的范围相对较小,因此我们能够轻松地使其适应这些功能 bean 定义。如果您超出了该限制范围,您可以通过切换回@Bean样式配置或使用混合方法来扩展您的 Spring Cloud Function 应用程序。例如,如果您想利用 Spring Boot 自动配置与外部数据存储集成,则需要使用@EnableAutoConfiguration. 如果您愿意,您的函数仍然可以使用功能声明来定义(即“混合”样式),但在这种情况下,您将需要显式关闭“完整功能模式”,以便spring.functional.enabled=falseSpring Boot 可以收回控制权。

功能可视化与控制

Spring Cloud Function 支持FunctionCatalog通过 Actuator 端点以及编程方式实现可用功能的可视化。

程序化方式

要以编程方式查看应用程序上下文中可用的功能,您只需访问FunctionCatalog. 在那里您可以找到获取目录大小、查找函数以及列出所有可用函数的名称的方法。

例如,

FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
int size = functionCatalog.size(); // will tell you how many functions available in catalog
Set<String> names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog
. . .
执行器

由于执行器和 Web 是可选的,因此您必须首先添加 Web 依赖项之一,并手动添加执行器依赖项。下面的例子展示了如何添加Web框架的依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

以下示例展示了如何添加 WebFlux 框架的依赖项:

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

您可以按如下方式添加 Actuator 依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

您还必须functions通过设置以下属性来启用执行器端点:--management.endpoints.web.exposure.include=functions

访问以下URL可查看FunctionCatalog中的函数: http://<host>:<port>/actuator/functions

例如,

curl http://localhost:8080/actuator/functions

你的输出应该是这样的:

{"charCounter":
	{"type":"FUNCTION","input-type":"string","output-type":"integer"},
 "logger":
 	{"type":"CONSUMER","input-type":"string"},
 "functionRouter":
 	{"type":"FUNCTION","input-type":"object","output-type":"object"},
 "words":
 	{"type":"SUPPLIER","output-type":"string"}. . .

测试功能应用程序

Spring Cloud Function 还具有一些 Spring Boot 用户非常熟悉的集成测试实用程序。

假设这是您的应用程序:

@SpringBootApplication
public class SampleFunctionApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleFunctionApplication.class, args);
    }

    @Bean
    public Function<String, String> uppercase() {
        return v -> v.toUpperCase();
    }
}

以下是包装此应用程序的 HTTP 服务器的集成测试:

@SpringBootTest(classes = SampleFunctionApplication.class,
            webEnvironment = WebEnvironment.RANDOM_PORT)
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

或者当使用函数 bean 定义样式时:

@FunctionalSpringBootTest
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

此测试与您为@Bean同一应用程序的版本编写的测试几乎相同 - 唯一的区别是@FunctionalSpringBootTest注释,而不是常规的@SpringBootTest. 所有其他部分(例如 )@Autowired TestRestTemplate都是标准 Spring Boot 功能。

为了帮助正确的依赖关系,这里摘录了 POM

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    . . . .
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-function-web</artifactId>
        <version>4.0.5</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

或者您可以仅使用FunctionCatalog. 例如:

@FunctionalSpringBootTest
public class FunctionalTests {

	@Autowired
	private FunctionCatalog catalog;

	@Test
	public void words() {
		Function<String, String> function = catalog.lookup(Function.class,
				"uppercase");
		assertThat(function.apply("hello")).isEqualTo("HELLO");
	}

}

无服务器平台适配器

除了能够作为独立进程运行之外,Spring Cloud Function 应用程序还可以适应运行现有的无服务器平台之一。该项目中有适用于 AWS LambdaAzure的适配器。Oracle Fn 平台有自己的 Spring Cloud Function 适配器。Riff支持 Java 函数,其 Java Function Invoker本身就是 Spring Cloud Function jar 的适配器。

AWS Lambda

AWS适配器采用 Spring Cloud Function 应用程序并将其转换为可以在 AWS Lambda 中运行形式。

如何开始使用 AWS Lambda 的详细信息超出了本文档的范围,因此期望用户对 AWS 和 AWS Lambda 有一定的了解,并希望了解 Spring 提供了哪些附加价值。

入门

Spring Cloud Function 框架的目标之一是提供必要的基础设施元素,使简单的函数应用程序 能够在特定环境中以某种方式进行交互。一个简单的函数应用程序(在上下文或Spring中)是一个包含Supplier、Function或Consumer类型的bean的应用程序。因此,对于 AWS,这意味着一个简单的函数 bean 应该以某种方式在 AWS Lambda 环境中被识别和执行。

让我们看一下例子:

@SpringBootApplication
public class FunctionConfiguration {

	public static void main(String[] args) {
		SpringApplication.run(FunctionConfiguration.class, args);
	}

	@Bean
	public Function<String, String> uppercase() {
		return value -> value.toUpperCase();
	}
}

它显示了一个完整的 Spring Boot 应用程序,其中定义了一个函数 bean。有趣的是,表面上这只是另一个启动应用程序,但在 AWS Adapter 的上下文中,它也是一个完全有效的 AWS Lambda 应用程序。不需要其他代码或配置。您所需要做的就是打包并部署它,所以让我们看看如何做到这一点。

为了使事情变得更简单,我们提供了一个可供构建和部署的示例项目,您可以 在此处访问它。

您只需执行./mvnw clean package即可生成 JAR 文件。所有必需的 Maven 插件均已设置为生成适当的 AWS 可部署 JAR 文件。(您可以在JAR 布局注释中阅读有关 JAR 布局的更多详细信息)。

然后,您必须将 JAR 文件(通过 AWS 仪表板或 AWS CLI)上传到 AWS。

当询问处理程序时,您指定org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest哪个是通用请求处理程序。

AWS部署

就这些。使用一些示例数据保存并执行该函数,对于该函数,该数据预计是一个字符串,该函数将大写并返回。

虽然org.springframework.cloud.function.adapter.aws.FunctionInvoker这是一个通用的 AWS实现,旨在将您与 AWS Lambda API 的具体细节完全隔离,但在某些情况下,您可能需要指定要使用RequestHandler哪个特定的 AWS 。RequestHandler下一节将向您解释如何实现这一目标。

AWS 请求处理程序

该适配器有几个可供您使用的通用请求处理程序。最通用的是(我们在入门部分中使用的)是org.springframework.cloud.function.adapter.aws.FunctionInvokerAWS 的RequestStreamHandler. 用户不需要执行任何其他操作,只需在部署函数时将其指定为 AWS 仪表板上的“处理程序”即可。它将处理大多数情况,包括 Kinesis、流媒体等。

如果您的应用程序具有不止一种@Bean类型Function等,那么您可以通过配置属性或环境变量来选择要使用的一种spring.cloud.function.definition 。这些功能是从 Spring Cloud 中提取的FunctionCatalog。如果您未指定,spring.cloud.function.definition 框架将尝试按照搜索顺序查找默认值,其中首先搜索 thenFunctionConsumerfinally Supplier)。

AWS功能路由

Spring Cloud Function 的核心功能之一是路由 ——一种根据用户提供的路由指令将一个特殊函数委托给其他函数的能力。

在 AWS Lambda 环境中,此功能提供了一项额外优势,因为它允许您将单个函数(路由函数)绑定为 AWS Lambda,从而为 API 网关绑定单个 HTTP 端点。因此,最终您只管理一项功能和一个端点,同时受益于可以成为应用程序一部分的许多功能。

提供的示例中提供了更多详细信息,但很少有值得一提的一般事项。

每当您的应用程序中存在多个函数时,就会默认启用路由功能,因为org.springframework.cloud.function.adapter.aws.FunctionInvoker 无法确定将哪个函数绑定为 AWS Lambda,因此默认为RoutingFunction. 这意味着您需要做的就是提供可以使用多种机制执行的路由指令 (有关更多详细信息,请参阅示例)。

另请注意,由于 AWS 不允许.在环境变量名称中使用点和/或连字符`-`,因此您可以受益于启动支持,只需用下划线替换点,用驼峰式大小写替换点。例如,spring.cloud.function.definition成为spring_cloud_function_definitionspring.cloud.function.routing-expression成为spring_cloud_function_routingExpression

具有自定义运行时的 AWS 函数路由

使用[自定义运行时]函数路由时的工作方式相同。您只需指定functionRouter为 AWS 处理程序,就像使用函数名称作为处理程序一样。

JAR 布局注意事项

您在 Lambda 中运行时不需要 Spring Cloud Function Web 或 Stream 适配器,因此您可能需要在创建发送到 AWS 的 JAR 之前排除这些适配器。Lambda 应用程序必须进行着色,但 Spring Boot 独立应用程序则不需要,因此您可以使用 2 个单独的 jar 运行同一个应用程序(根据示例)。示例应用程序创建 2 个 jar 文件,一个包含aws 用于在 Lambda 中部署的分类器,另一个包含spring-cloud-function-web 运行时包含的可执行(瘦)jar 。Spring Cloud Function 将尝试使用该属性从 JAR 文件清单中为您找到一个“主类” Start-Class(如果您使用启动器父级,则 Spring Boot 工具将为您添加该属性)。Start-Class如果清单中没有,您可以MAIN_CLASS在将函数部署到 AWS 时使用环境变量或系统属性。

如果您不使用功能 bean 定义,而是依赖于 Spring Boot 的自动配置,并且不依赖于spring-boot-starter-parent,则必须将其他转换器配置为 maven-shade-plugin 执行的一部分。

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-shade-plugin</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<version>2.7.4</version>
		</dependency>
	</dependencies>
	<executions>
		<execution>
			<goals>
			     <goal>shade</goal>
			</goals>
			<configuration>
				<createDependencyReducedPom>false</createDependencyReducedPom>
				<shadedArtifactAttached>true</shadedArtifactAttached>
				<shadedClassifierName>aws</shadedClassifierName>
				<transformers>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.handlers</resource>
					</transformer>
					<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
						<resource>META-INF/spring.factories</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.schemas</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.components</resource>
					</transformer>
				</transformers>
			</configuration>
		</execution>
	</executions>
</plugin>

构建文件设置

为了在 AWS Lambda 上运行 Spring Cloud Function 应用程序,您可以利用云平台提供商提供的 Maven 或 Gradle 插件。

梅文

为了使用 Maven 的适配器插件,请将插件依赖项添加到您的pom.xml 文件中:

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-aws</artifactId>
	</dependency>
</dependencies>

正如JAR 布局注释中所指出的,您将需要一个阴影 jar 才能将其上传到 AWS Lambda。您可以使用Maven Shade 插件来实现此目的。设置示例可以在上面找到。

您可以使用 Spring Boot Maven 插件来生成瘦 jar

<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>
			<version>${wrapper.version}</version>
		</dependency>
	</dependencies>
</plugin>

您可以在此处pom.xml找到使用 Maven 将 Spring Cloud Function 应用程序部署到 AWS Lambda 的完整示例文件。

摇篮

为了使用 Gradle 的适配器插件,请将依赖项添加到您的build.gradle文件中:

dependencies {
	compile("org.springframework.cloud:spring-cloud-function-adapter-aws:${version}")
}

正如JAR 布局注释中所指出的,您将需要一个阴影 jar 才能将其上传到 AWS Lambda。您可以使用Gradle Shadow 插件来实现:

buildscript {
	dependencies {
		classpath "com.github.jengelman.gradle.plugins:shadow:${shadowPluginVersion}"
	}
}
apply plugin: 'com.github.johnrengelman.shadow'

assemble.dependsOn = [shadowJar]

import com.github.jengelman.gradle.plugins.shadow.transformers.*

shadowJar {
	classifier = 'aws'
	dependencies {
		exclude(
			dependency("org.springframework.cloud:spring-cloud-function-web:${springCloudFunctionVersion}"))
	}
	// Required for Spring
	mergeServiceFiles()
	append 'META-INF/spring.handlers'
	append 'META-INF/spring.schemas'
	append 'META-INF/spring.tooling'
	append 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
	append 'META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports'
	transform(PropertiesFileTransformer) {
		paths = ['META-INF/spring.factories']
		mergeStrategy = "append"
	}
}

您可以使用 Spring Boot Gradle 插件和 Spring Boot Thin Gradle 插件来生成Thin jar

buildscript {
	dependencies {
		classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${wrapperVersion}")
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}
apply plugin: 'org.springframework.boot'
apply plugin: 'org.springframework.boot.experimental.thin-launcher'
assemble.dependsOn = [thinJar]

您可以在此处build.gradle找到使用 Gradle 将 Spring Cloud Function 应用程序部署到 AWS Lambda 的完整示例文件。

上传

在下面构建示例spring-cloud-function-samples/function-sample-aws并将-awsjar 文件上传到 Lambda。处理程序可以是example.Handleror org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler(类的 FQN,而不是方法引用,尽管 Lambda 确实接受方法引用)。

./mvnw -U clean package

使用AWS命令行工具,它看起来像这样:

aws lambda create-function --function-name Uppercase --role arn:aws:iam::[USERID]:role/service-role/[ROLE] --zip-file fileb://function-sample-aws/target/function-sample-aws-2.0.0.BUILD-SNAPSHOT-aws.jar --handler org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler --description "Spring Cloud Function Adapter Example" --runtime java8 --region us-east-1 --timeout 30 --memory-size 1024 --publish

AWS 示例中函数的输入类型是 Foo,具有名为“value”的单个属性。所以你需要这个来测试它:

{
  "value": "test"
}
AWS 示例应用程序是以“功能”风格编写的(作为ApplicationContextInitializer)。这在 Lambda 中启动比传统样式要快得多@Bean,因此如果您不需要@Beans(或@EnableAutoConfiguration),这是一个不错的选择。热启动不受影响。

类型转换

Spring Cloud Function 将尝试透明地处理原始输入流和函数声明的类型之间的类型转换。

例如,如果您的函数签名是这样的,Function<Foo, Bar>我们将尝试将传入流事件转换为Foo.

如果事件类型未知或无法确定(例如Function<?, ?>),我们将尝试将传入流事件转换为通用事件Map

原始输入

有时您可能希望访问原始输入。在这种情况下,您所需要做的就是声明您的函数签名为accept InputStream。例如,Function<InputStream, ?>. 在这种情况下,我们不会尝试任何转换,而是将原始输入直接传递给函数。

微软Azure功能

Azure函数适配器,用于将Spring Cloud Function应用程序部署为本机 Azure Java 函数。

编程Azure Functions 模型广泛地依赖于 Java注释来定义函数的处理程序方法及其输入和输出类型。在编译时,带注释的类由提供的 Azure Maven/Gradle 插件处理,以生成必要的 Azure Function 绑定文件、配置和包工件。Azure 注释只是一种类型安全的方法,用于将 java 函数配置为被识别为 Azure 函数。

spring -cloud-function-adapter-azure扩展了基本编程模型以提供 Spring 和 Spring Cloud Function 支持。借助适配器,您可以使用依赖项注入构建 Spring Cloud Function 应用程序,然后将必要的服务自动连接到 Azure 处理程序方法中。

scf 天蓝色适配器
对于基于 Web 的函数应用程序,您可以将通用函数替换adapter-azure为专用的spring-cloud-function-adapter-azure-web。使用 Azure Web Adaptor,您可以将任何 Spring Web 应用程序部署为 Azure HttpTrigger 函数。该适配器隐藏了 Azure 注释的复杂性,并使用熟悉的Spring Web编程模型。有关更多信息,请参阅下面的Azure Web Adaptor部分。

.1. Azure 适配器

为 Azure Functions提供SpringSpring Cloud Function集成。

.1.1. 依赖关系

为了启用 Azure Function 集成,请将 azure 适配器依赖项添加到您的pom.xmlbuild.gradle 文件中:

梅文
<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-azure</artifactId>
	</dependency>
</dependencies>
摇篮
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure'
}
版本4.0.0+是必需的。将适配器放在类路径上会激活 Azure Java Worker 集成。

.1.2. 开发指南

使用@Component(或@Service) 注释将任何现有的 Azure Function 类(例如带有@FunctionName处理程序)转换为 Spring 组件。然后,您可以自动连接所需的依赖项(或Spring Cloud Function 组合的函数目录)并在 Azure 函数处理程序中使用这些依赖项。

@Component (1)
public class MyAzureFunction {

	// Plain Spring bean - not a Spring Cloud Functions!
	@Autowired private Function<String, String> uppercase; (2)

	// The FunctionCatalog leverages the Spring Cloud Function framework.
	@Autowired private FunctionCatalog functionCatalog; (2)

	@FunctionName("spring") (3)
	public String plainBean( (4)
			@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
			ExecutionContext context) {

		return this.uppercase.apply(request.getBody().get());
	}

	@FunctionName("scf") (3)
	public String springCloudFunction( (5)
			@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
			ExecutionContext context) {

		// Use SCF composition. Composed functions are not just spring beans but SCF such.
		Function composed = this.functionCatalog.lookup("reverse|uppercase"); (6)

		return (String) composed.apply(request.getBody().get());
	}
}
1 指示该类MyAzureFunction是一个“组件”,Spring 框架将其视为自动检测和类路径扫描的候选者。
2 自动连接(如下)中定义的uppercase和beans 。functionCatalogHttpTriggerDemoApplication
3 @FunctionName注释标识指定的Azure函数处理程序。当被触发器(例如@HttpTrigger)调用时,函数会处理该触发器和任何其他输入,以产生一个或多个输出。
4 方法plainBean处理程序映射到一个 Azure 函数,该函数使用自动连接的uppercasespring bean 来计算结果。它演示了如何在 Azure 处理程序中使用“普通”Spring 组件。
5 方法springCloudFunction处理程序映射到另一个 Azure 函数,该函数使用自动连接的FunctionCatalog实例来计算结果。
6 展示如何利用 Spring Cloud Function Function Catalog组合 API。
使用com.microsoft.azure.functions.annotation.*包 中包含的 Java 注释将输入和输出绑定到方法。

Azure 处理程序内部使用的业务逻辑的实现看起来像一个常见的 Spring 应用程序:

@SpringBootApplication (1)
public class HttpTriggerDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(HttpTriggerDemoApplication.class, args);
	}

	@Bean
	public Function<String, String> uppercase() { (2)
		return payload -> payload.toUpperCase();
	}

	@Bean
	public Function<String, String> reverse() { (2)
		return payload -> new StringBuilder(payload).reverse().toString();
	}
}
1 带注释的类@SpringBootApplication作主类配置Main-Class中所解释的。
2 自动连接并在 Azure 函数处理程序中使用的函数。
功能目录

Spring Cloud Function 支持一系列用户定义函数的类型签名,同时提供一致的执行模型。为此,它使用函数目录将所有用户定义的函数转换为规范表示。

Azure 适配器可以自动连接任何 Spring 组件,例如uppercase上面的组件。但它们被视为普通的 Java 类实例,而不是规范的 Spring Cloud Functions!

要利用 Spring Cloud Function 并访问规范函数表示,您需要自动连接FunctionCatalog并在处理程序中使用它,就像上面的处理程序functionCatalog实例一样。springCloudFunction()

访问 Azure ExecutionContext

有时需要访问 Azure 运行时以com.microsoft.azure.functions.ExecutionContext. 例如,此类需求之一是日志记录,因此它可以出现在 Azure 控制台中。

为此,AzureFunctionUtil.enhanceInputIfNecessary允许您添加 的实例ExecutionContext作为消息标头,以便您可以通过executionContext密钥检索它。

@FunctionName("myfunction")
public String execute(
	@HttpTrigger(name = "req", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
	ExecutionContext context) {

	Message message =
		(Message) AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context); (1)

	return this.uppercase.apply(message);
}
1 利用该实用程序使用标头键AzureFunctionUtil内联as 消息标头。contextAzureFunctionUtil.EXECUTION_CONTEXT

现在您可以从消息头中检索 ExecutionContext:

@Bean
public Function<Message<String>, String> uppercase(JsonMapper mapper) {
	return message -> {
		String value = message.getPayload();
		ExecutionContext context =
			(ExecutionContext) message.getHeaders().get(AzureFunctionUtil.EXECUTION_CONTEXT); (1)
		. . .
	}
}
1 从标头中检索 ExecutionContext 实例。

.1.3. 配置

要在 Microsoft Azure 上运行函数应用程序,您必须提供必要的配置,例如function.jsonhost.json,并遵守强制 打包格式

通常,Azure Maven(或 Gradle)插件用于从带注释的类生成必要的配置并生成所需的包格式。

Azure打包格式与默认的 Spring Boot 打包(例如 )不兼容uber jar。下面的禁用Spring Boot 插件部分解释了如何处理这个问题。
Azure Maven/Gradle 插件

Azure 提供MavenGradle插件来处理带注释的类、生成必要的配置并生成预期的包布局。插件用于设置平台、运行时和应用程序设置属性,如下所示:

梅文
<plugin>
	<groupId>com.microsoft.azure</groupId>
	<artifactId>azure-functions-maven-plugin</artifactId>
	<version>1.22.0 or higher</version>

	<configuration>
		<appName>YOUR-AZURE-FUNCTION-APP-NAME</appName>
		<resourceGroup>YOUR-AZURE-FUNCTION-RESOURCE-GROUP</resourceGroup>
		<region>YOUR-AZURE-FUNCTION-APP-REGION</region>
		<appServicePlanName>YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME</appServicePlanName>
		<pricingTier>YOUR-AZURE-FUNCTION-PRICING-TIER</pricingTier>

		<hostJson>${project.basedir}/src/main/resources/host.json</hostJson>

		<runtime>
			<os>linux</os>
			<javaVersion>11</javaVersion>
		</runtime>

		<appSettings>
			<property>
				<name>FUNCTIONS_EXTENSION_VERSION</name>
				<value>~4</value>
			</property>
		</appSettings>
	</configuration>
	<executions>
		<execution>
			<id>package-functions</id>
			<goals>
				<goal>package</goal>
			</goals>
		</execution>
	</executions>
</plugin>
摇篮
plugins {
    id "com.microsoft.azure.azurefunctions" version "1.11.0"
	// ...
}

apply plugin: "com.microsoft.azure.azurefunctions"

azurefunctions {
	appName = 'YOUR-AZURE-FUNCTION-APP-NAME'
    resourceGroup = 'YOUR-AZURE-FUNCTION-RESOURCE-GROUP'
    region = 'YOUR-AZURE-FUNCTION-APP-REGION'
    appServicePlanName = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME'
    pricingTier = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME'

    runtime {
      os = 'linux'
      javaVersion = '11'
    }

    auth {
      type = 'azure_cli'
    }

    appSettings {
      FUNCTIONS_EXTENSION_VERSION = '~4'
    }
	// Uncomment to enable local debug
    // localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
}

有关运行时配置的更多信息:Java 版本部署操作系统

禁用 Spring Boot 插件

预计,Azure Functions 在 Azure 执行运行时内运行,而不是在 SpringBoot 运行时内运行!此外,Azure 需要由 Azure Maven/Gradle 插件生成的特定打包格式,该格式与默认的 Spring Boot 打包不兼容。

您必须禁用 SpringBoot Maven/Gradle 插件或使用Spring Boot Thin Launcher,如以下 Maven 代码段所示:

<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>
主级配置

指定Main-Class/Start-Class指向您的 Spring 应用程序入口点,例如上例中的HttpTriggerDemoApplication类。

您可以使用 Mavenstart-class属性或设置Main-Class以下属性MANIFEST/META-INFO

梅文
<properties>
	<start-class>YOUR APP MAIN CLASS</start-class>
	...
</properties>
摇篮
jar {
    manifest {
        attributes(
            "Main-Class": "YOUR-APP-MAIN-CLASS"
        )
    }
}
或者,您可以使用MAIN_CLASS环境变量显式设置类名。对于本地运行,请将MAIN_CLASS变量添加到文件中,对于 Azure 门户部署,请在应用程序设置local.settings.json中设置变量。
如果MAIN_CLASS未设置该变量,Azure 适配器会MANIFEST/META-INFO从类路径上找到的 jar 中查找属性,并选择第一个Main-Class:带有 a@SpringBootApplication@SpringBootConfiguration注释的属性。
元数据配置

您可以使用共享的host.json文件来配置函数应用。

{
	"version": "2.0",
	"extensionBundle": {
		"id": "Microsoft.Azure.Functions.ExtensionBundle",
		"version": "[4.*, 5.0.0)"
	}
}

host.json 元数据文件包含影响函数应用实例中所有函数的配置选项。

如果该文件不在项目顶部文件夹中,您需要相应地配置您的插件(如hostJsonmaven 属性)。

.1.4. 样品

以下是您可以探索的各种 Spring Cloud Function Azure 适配器示例的列表:

.2. Azure Web 适配器

adapter-azure对于纯粹的、基于 Web 的函数应用程序,您可以使用专门的spring-cloud-function-adapter-azure-web替换通用函数。Azure Web Adaptor 可以在内部使用 HttpTrigger 将任何 Spring Web 应用程序部署为本机 Azure 函数。它隐藏了 Azure 注释的复杂性,而是依赖于熟悉的Spring Web编程模型。

要启用 Azure Web Adaptor,请将适配器依赖项添加到您的pom.xmlbuild.gradle文件中:

梅文
<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-azure-web</artifactId>
	</dependency>
</dependencies>
摇篮
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure-web'
}

相同的配置使用说明也适用于Azure Web Adapter

.2.1. 样品

有关更多信息,请浏览以下 Azure Web Adaptor 示例:

.3. 用法

构建和部署应用程序类型的通用Azure Adapter说明Azure Web Adapter

.3.1. 建造

梅文
./mvnw -U clean package
摇篮
./gradlew azureFunctionsPackage

.3.2. 本地运行

要在本地运行Azure Functions并部署到实时 Azure 环境,您需要Azure Functions Core Tools与 Azure CLI 一起安装(请参阅此处)。对于某些配置,您还需要Azurite 模拟器。

然后运行示例:

梅文
./mvnw azure-functions:run
摇篮
./gradlew azureFunctionsRun

.3.3. 在 Azure 上运行

确保您已登录 Azure 帐户。

az login

并部署

梅文
./mvnw azure-functions:deploy
摇篮
./gradlew azureFunctionsDeploy

.3.4. 本地调试

在调试模式下运行该函数。

梅文
./mvnw azure-functions:run -DenableDebug
摇篮
// If you want to debug your functions, please add the following line
// to the azurefunctions section of your build.gradle.
azurefunctions {
  ...
  localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
}

或者,JAVA_OPTS您的价值local.settings.json如下:

{
	"IsEncrypted": false,
	"Values": {
		...
		"FUNCTIONS_WORKER_RUNTIME": "java",
		"JAVA_OPTS": "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005"
	}
}

这是远程调试配置的片段VSCode

{
	"version": "0.2.0",
	"configurations": [
		{
			"type": "java",
			"name": "Attach to Remote Program",
			"request": "attach",
			"hostName": "localhost",
			"port": "5005"
		},
	]
}

.4. FunctionInvoker(已弃用)

旧的FunctionInvoker编程模型已被弃用,并且今后将不再受支持。

有关函数集成方法的其他文档和示例,请遵循azure-sample README 和代码。

谷歌云功能

Google Cloud Functions 适配器使 Spring Cloud Function 应用能够在Google Cloud Functions无服务器平台上运行。您可以使用开源Google Functions Framework for Java在本地运行该函数,也可以在 GCP 上运行该函数。

项目依赖关系

首先将依赖项添加spring-cloud-function-adapter-gcp到您的项目中。

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-gcp</artifactId>
	</dependency>

	...
</dependencies>

另外,添加spring-boot-maven-plugin将构建要部署的功能的 JAR。

请注意,我们还引用spring-cloud-function-adapter-gcpspring-boot-maven-plugin. 这是必要的,因为它会修改插件以将您的函数打包为正确的 JAR 格式,以便在 Google Cloud Functions 上部署。
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<outputDirectory>target/deploy</outputDirectory>
	</configuration>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-function-adapter-gcp</artifactId>
		</dependency>
	</dependencies>
</plugin>

最后,添加作为 Google Functions Framework for Java 的一部分提供的 Maven 插件。这允许您通过 本地测试您的功能mvn function:run

函数目标应始终设置为org.springframework.cloud.function.adapter.gcp.GcfJarLauncher;这是一个适配器类,充当来自 Google Cloud Functions 平台的 Spring Cloud Function 的入口点。
<plugin>
	<groupId>com.google.cloud.functions</groupId>
	<artifactId>function-maven-plugin</artifactId>
	<version>0.9.1</version>
	<configuration>
		<functionTarget>org.springframework.cloud.function.adapter.gcp.GcfJarLauncher</functionTarget>
		<port>8080</port>
	</configuration>
</plugin>

完整的工作示例可以在Spring Cloud Functions GCP 示例pom.xml中找到。

HTTP 函数

Google Cloud Functions 支持部署HTTP Functions,这些函数是由 HTTP 请求调用的函数。以下部分描述了将 Spring Cloud Function 部署为 HTTP Function 的说明。

入门

让我们从一个简单的 Spring Cloud Function 示例开始:

@SpringBootApplication
public class CloudFunctionMain {

	public static void main(String[] args) {
		SpringApplication.run(CloudFunctionMain.class, args);
	}

	@Bean
	public Function<String, String> uppercase() {
		return value -> value.toUpperCase();
	}
}

在 中指定您的配置主类resources/META-INF/MANIFEST.MF

Main-Class: com.example.CloudFunctionMain

然后在本地运行该函数。function-maven-plugin这是由项目依赖项部分中描述的Google Cloud Functions 提供的。

mvn function:run

调用HTTP函数:

curl http://localhost:8080/ -d "hello"
部署到 GCP

首先打包您的应用程序。

mvn package

如果您添加了上面定义的自定义插件,您应该在目录spring-boot-maven-plugin中看到生成的 JAR 。target/deploy此 JAR 的格式正确,可以部署到 Google Cloud Functions。

接下来,确保您已安装Cloud SDK CLI

从项目基目录运行以下命令进行部署。

gcloud functions deploy function-sample-gcp-http \
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
--runtime java11 \
--trigger-http \
--source target/deploy \
--memory 512MB

调用HTTP函数:

curl https://REGION-PROJECT_ID.cloudfunctions.net/function-sample-gcp-http -d "hello"

设置自定义 HTTP 状态代码:

Functions can specify a custom HTTP response code by setting the `FunctionInvoker.HTTP_STATUS_CODE` header.
@Bean
public Function<String, Message<String>> function() {

	String payload = "hello";

	Message<String> message = MessageBuilder.withPayload(payload).setHeader(FunctionInvoker.HTTP_STATUS_CODE, 404).build();

	return input -> message;
};

后台功能

Google Cloud Functions 还支持部署后台函数,这些函数可间接调用以响应事件,例如Cloud Pub/Sub主题上的消息、 Cloud Storage 存储桶中的更改或Firebase事件。

它还spring-cloud-function-adapter-gcp允许将功能部署为后台功能。

以下部分描述了编写 Cloud Pub/Sub 主题背景函数的过程。然而,有许多不同的事件类型可以触发后台函数执行,这里不讨论;这些在后台函数触发器文档中进行了描述。

入门

让我们从一个简单的 Spring Cloud Function 开始,它将作为 GCF 后台函数运行:

@SpringBootApplication
public class BackgroundFunctionMain {

	public static void main(String[] args) {
		SpringApplication.run(BackgroundFunctionMain.class, args);
	}

	@Bean
	public Consumer<PubSubMessage> pubSubFunction() {
		return message -> System.out.println("The Pub/Sub message data: " + message.getData());
	}
}

此外,PubSubMessage使用以下定义在项目中创建类。此类表示Pub/Sub 事件结构,该结构在 Pub/Sub 主题事件上传递给您的函数。

public class PubSubMessage {

	private String data;

	private Map<String, String> attributes;

	private String messageId;

	private String publishTime;

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public Map<String, String> getAttributes() {
		return attributes;
	}

	public void setAttributes(Map<String, String> attributes) {
		this.attributes = attributes;
	}

	public String getMessageId() {
		return messageId;
	}

	public void setMessageId(String messageId) {
		this.messageId = messageId;
	}

	public String getPublishTime() {
		return publishTime;
	}

	public void setPublishTime(String publishTime) {
		this.publishTime = publishTime;
	}

}

在 中指定您的配置主类resources/META-INF/MANIFEST.MF

Main-Class: com.example.BackgroundFunctionMain

然后在本地运行该函数。function-maven-plugin这是由项目依赖项部分中描述的Google Cloud Functions 提供的。

mvn function:run

调用HTTP函数:

curl localhost:8080 -H "Content-Type: application/json" -d '{"data":"hello"}'

通过查看日志验证该函数是否被调用。

部署到 GCP

为了将后台功能部署到 GCP,首先打包您的应用程序。

mvn package

如果您添加了上面定义的自定义插件,您应该在目录spring-boot-maven-plugin中看到生成的 JAR 。target/deploy此 JAR 的格式正确,可以部署到 Google Cloud Functions。

接下来,确保您已安装Cloud SDK CLI

从项目基目录运行以下命令进行部署。

gcloud functions deploy function-sample-gcp-background \
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
--runtime java11 \
--trigger-topic my-functions-topic \
--source target/deploy \
--memory 512MB

现在,每次将消息发布到 指定的主题时,Google Cloud Function 都会调用该函数--trigger-topic

有关测试和验证后台函数的演练,请参阅运行GCF 后台函数示例的说明。

示例功能

项目提供了以下示例函数供参考:

  • function -sample-gcp-http是一个 HTTP 函数,您可以在本地测试并尝试部署。

  • function -sample-gcp-background显示了由发布到指定 Pub/Sub 主题的消息触发的后台函数示例。