函数计算服务使用Java编程,需要定义一个Java函数作为入口。本文介绍Java事件函数的结构和特点。

背景信息

函数计算支持Java8运行环境。Java语言和Python、Node.js这类脚本型语言不同,该语言需要编译后才能在JVM虚拟机中运行,因此Java语言具有以下限制:

  • 不支持上传代码:仅支持上传已经开发完成、编译打包后的ZIP包或JAR包。函数计算不提供Java的编译能力。
  • 不支持在线编辑:由于不支持上传代码,所以不支持在线编辑代码,仅能看到通过上传 JAR 包通过 OSS 上传两种方法提交代码。

事件函数接口

您在使用Java编程时,必须要实现函数计算提供的接口类,对于事件入口函数目前有两个预定义接口可以选择。这两个预定义接口分别是:

  • StreamRequestHandler

    以流的方式接受调用输入event和返回执行结果,您需要从输入流中读取调用函数时的输入,处理完成后把函数执行结果写入到输出流中来返回。

  • PojoRequestHandler

    通过泛型的方式,您可以自定义输入和输出的类型,但是输入和输出的类型必须是POJO类型。

StreamRequestHandler

一个最简单的事件函数定义如下所示。

package example;

import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.StreamRequestHandler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class HelloFC implements StreamRequestHandler {

    @Override
    public void handleRequest(
            InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        outputStream.write(new String("hello world").getBytes());
    }
}            
  • 包名和类名

    由于Java有包的概念,因此执行方法和其他语言有所不同,需要带有包信息。代码例子中对应的执行方法为example.HelloFC::mainHandler,此处example标识为Java package,HelloFC标识为类,mainHandler标识为类方法。

    包名和类名可以是任意的,但是需要与创建函数时的函数入口(handler)字段相对应。上述的例子包名是example,类名是HelloFC,那么创建函数时指定的handlerexample.HelloFC::handleRequesthandler的格式为{package}.{class}::{method}

  • 实现的接口

    您的代码中必须要实现函数计算预定义的接口。上述的代码示例中实现了StreamRequestHandler,其中的inputStream参数是调用函数时传入的数据,outputStream参数用于返回函数的执行结果。

  • context参数

    context参数中包含一些函数的运行时信息(例如requestId、临时AccessKey等),其类型是com.aliyun.fc.runtime.Context

  • 返回值

    实现StreamRequestHandler接口的函数通过outputStream参数返回执行结果。

  • 引入接口库
    其中用到的com.aliyun.fc.runtime这个包的依赖可以通过下文的pom.xml引用。
    <dependency>
        <groupId>com.aliyun.fc.runtime</groupId>
        <artifactId>fc-java-core</artifactId>
        <version>1.4.0</version>
    </dependency>           

    您可以通过Maven仓库可以获取fc-java-core最新的版本号。

在创建函数之前,您需要将代码和其依赖的fc-java-core打成JAR包。打包方式,请参见使用Java自定义模块
说明 示例代码包是示例中的hello world代码打包成的JAR包,您可以直接使用示例代码包进行测试。

您可以使用Serverless Devs的相关命令初始化、构建和部署项目。详细操作如下:

  1. 执行以下命令,初始化项目。
    s init start-fc-event-java8 -d start-fc-event-java8
    输出示例:
     Serverless Awesome: https://github.com/Serverless-Devs/Serverless-Devs/blob/master/docs/zh/awesome.md
    
     file decompression completed
    
         ____  _     _ ___  _ _     _        _____ ____
        /  _ \/ \   / \\  \/// \ /\/ \  /|  /    //   _\
        | / \|| |   | | \  / | | ||| |\ ||  |  __\|  /
        | |-||| |_/\| | / /  | \_/|| | \||  | |   |  \__
        \_/ \|\____/\_//_/   \____/\_/  \|  \_/   \____/
      please select credential alias default
    
        Welcome to the Aliyun FC start application
         This application requires to open these services:
             FC : https://fc.console.aliyun.com/
    
         * 额外说明:s.yaml中声明了actions:
            部署前执行:s build --use-docker --dockerfile ./code/Dockerfile
           如果不需要每次都构建项目,或者部署前不需要构建,或者已经手动构建了,可以注释掉这部分内容
         * 项目初始化完成,您可以直接进入项目目录下,并使用s deploy进行项目部署。
    
    
    
     Thanks for using Serverless-Devs
     You could [cd /test/start-fc-event-java8] and enjoy your serverless journey!
     If you need help for this example, you can use [s -h] after you enter folder.
     Document Star:https://github.com/Serverless-Devs/Serverless-Devs
  2. 执行以下命令,进入项目目录。
    cd start-fc-event-java8
  3. 执行以下命令,安装依赖。
    s build
    输出示例:
    [2021-12-19 12:07:05] [INFO] [S-CLI] - Start ...
    [2021-12-19 12:07:06] [INFO] [FC-BUILD] - Build artifact start...
    builder begin to build, runtime is: java8, sourceDir:  /test/start-fc-event-java8/
    running task: flow MavenTaskFlow
    running task: MavenCompileTask
    [INFO] Scanning for projects...
    [INFO]
    [INFO] -------------------< example:ServerlessToolProject >--------------------
    [INFO] Building ServerlessToolProject 1.0-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    ......
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 01:39 min
    [INFO] Finished at: 2021-12-19T20:10:10+08:00
    [INFO] ------------------------------------------------------------------------
    running task: CopyMavenArtifacts
    copy maven artifacts from /test/start-fc-event-java8/target/dependency
    [2021-12-19 12:10:10] [INFO] [FC-BUILD] - Build artifact successfully.
    
    Tips for next step
    ======================
    * Invoke Event Function: s local invoke
    * Invoke Http Function: s local start
    * Deploy Resources: s deploy
    End of method: build
  4. 执行以下命令,部署项目。
    s deploy
    输出示例:
    [2021-12-19 12:17:21] [INFO] [S-CLI] - Start ...
    [2021-12-19 12:17:21] [INFO] [S-CLI] - Start the pre-action
    [2021-12-19 12:17:21] [INFO] [S-CLI] - Action: s build --use-docker
    [2021-12-19 12:17:21] [INFO] [S-CLI] - Start ...
    [2021-12-19 12:17:22] [INFO] [FC-BUILD] - Build artifact start...
    [2021-12-19 12:17:22] [INFO] [FC-BUILD] - Use docker for building.
    [2021-12-19 12:17:22] [INFO] [FC-BUILD] - Build function using image: registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-java8:build-1.9.21
    [2021-12-19 12:17:22] [INFO] [FC-BUILD] - skip pulling image registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-java8:build-1.9.21...
    [2021-12-19 12:17:49] [INFO] [FC-BUILD] - Build artifact successfully.
    
    Tips for next step
    ======================
    * Invoke Event Function: s local invoke
    * Invoke Http Function: s local start
    * Deploy Resources: s deploy
    End of method: build
    [2021-12-19 12:17:49] [INFO] [S-CLI] - End the pre-action
    [2021-12-19 12:17:50] [INFO] [FC-DEPLOY] - Using region: cn-hangzhou
    [2021-12-19 12:17:50] [INFO] [FC-DEPLOY] - Using access alias: default
    [2021-12-19 12:17:50] [INFO] [FC-DEPLOY] - Using accessKeyID: LTAI4G4cwJkK4Rza6xd9****
    [2021-12-19 12:17:50] [INFO] [FC-DEPLOY] - Using accessKeySecret: eCc0GxSpzfq1DVspnqqd6nmYNN****
     Using fc deploy type: sdk, If you want to deploy with pulumi, you can [s cli fc-default set deploy-type pulumi] to switch.
    [2021-12-19 12:17:50] [INFO] [FC-DEPLOY] - Checking Service hello-world-service exists
    [2021-12-19 12:17:51] [INFO] [FC-DEPLOY] - Checking Function event-java8 exists
    [2021-12-19 12:17:51] [INFO] [FC-DEPLOY] - Fc detects that you have run build command for function: event-java8.
    [2021-12-19 12:17:51] [INFO] [FC-DEPLOY] - Using codeUri: /test/start-fc-event-java8/.s/build/artifacts/hello-world-service/event-java8
    
    
    Detail:
    
    added: {}
    deleted: {}
    updated:
      LD_LIBRARY_PATH: >-
        /code/.s/root/usr/local/lib:/code/.s/root/usr/lib:/code/.s/root/usr/lib/x86_64-linux-gnu:/code/.s/root/usr/lib64:/code/.s/root/lib:/code/.s/root/lib/x86_64-linux-gnu:/code/.s/root/python/lib/python2.7/site-packages:/code/.s/root/python/lib/python3.6/site-packages:/code:/code/lib:/usr/local/lib
      PATH: >-
        /code/.s/root/usr/local/bin:/code/.s/root/usr/local/sbin:/code/.s/root/usr/bin:/code/.s/root/usr/sbin:/code/.s/root/sbin:/code/.s/root/bin:/code:/code/node_modules/.bin:/code/.s/python/bin:/code/.s/node_modules/.bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin
      NODE_PATH: /code/node_modules:/usr/local/lib/node_modules
      PYTHONUSERBASE: /code/.s/python
    
     Fc want to add/append some content to your origin environment variables for finding dependencies generated by build comman
    d.
    Do you agree with the behavior? yes
     Make service hello-world-service success.
     Make function hello-world-service/event-java8 success.
    [2021-12-19 12:17:56] [INFO] [FC-DEPLOY] - Checking Service hello-world-service exists
    [2021-12-19 12:17:56] [INFO] [FC-DEPLOY] - Checking Function event-java8 exists
    
    ......
    
    helloworld:
      region:   cn-hangzhou
      service:
        name: hello-world-service
      function:
        name:       event-java8
        runtime:    java8
        handler:    example.App::handleRequest
        memorySize: 128
        timeout:    60

登录函数计算控制台,您就可以查看函数的状态并调用函数了。

PojoRequestHandler

一个最简单的处理函数定义如下所示。SimpleRequest的对象是需支持可JSON序列化的对象,例如POJO。

// HelloFC.java
package example;

import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.PojoRequestHandler;

public class HelloFC implements PojoRequestHandler<SimpleRequest, SimpleResponse> {

    @Override
    public SimpleResponse handleRequest(SimpleRequest request, Context context) {
        String message = "Hello, " + request.getFirstName() + " " + request.getLastName();
        return new SimpleResponse(message);
    }
}            
// SimpleRequest.java
package example;

public class SimpleRequest {
    String firstName;
    String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public SimpleRequest() {}
    public SimpleRequest(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}            
// SimpleResponse.java
package example;

public class SimpleResponse {
    String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public SimpleResponse() {}
    public SimpleResponse(String message) {
        this.message = message;
    }
}            

准备调用的输入文件如下。

{
  "firstName": "FC",
  "lastName": "aliyun"
}            

使用context

context是函数计算在运行时生成的一个对象,包含一些运行时的信息,您在代码中可以使用这些信息。context的具体实现,请参见fc-java-libs,其定义如下所示。

package com.aliyun.fc.runtime;

public interface Context {

    public String getRequestId();

    public Credentials getExecutionCredentials();

    public FunctionParam getFunctionParam();

    public FunctionComputeLogger getLogger();

    public Service getService();
}           

您可以看到context中包含了以下信息。

信息类型 信息类型说明
RequestId 本次调用请求的唯一ID。您可以记录下该ID,当出现问题时方便查询。
Function 当前调用的函数的一些基本信息,例如函数名、函数入口、函数内存和超时时间。
Credentials 函数计算服务通过扮演您提供服务角色的获得的一组临时密钥,其有效时间是5分钟。您可以在代码中使用Credentials去访问相应的服务例如OSS,这就避免了您把自己的AccessKey信息编码在函数代码里。详细信息,请参见服务角色
Logger 函数计算封装过的logger。
Service 当前调用的服务的一些基本信息。

下文的代码演示了如何使用临时密钥向OSS中上传一个文件。

package example;

import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.Credentials;
import com.aliyun.fc.runtime.StreamRequestHandler;
import com.aliyun.oss.OSSClient;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class HelloFC implements StreamRequestHandler {

    @Override
    public void handleRequest(
            InputStream inputStream, OutputStream outputStream, Context context) throws IOException {

        String endpoint = "oss-cn-shanghai.aliyuncs.com";
        String bucketName = "my-bucket";

        Credentials creds = context.getExecutionCredentials();
        OSSClient client = new OSSClient(
                endpoint, creds.getAccessKeyId(), creds.getAccessKeySecret(), creds.getSecurityToken());
        client.putObject(bucketName, "my-object", new ByteArrayInputStream(new String("hello").getBytes()));
        outputStream.write(new String("done").getBytes());
    }
}