函数计算目前支持OpenJDK 8和OpenJDK 11运行环境。本文介绍Java运行环境的打印日志、错误处理和自定义模块。

打印日志

函数通过context.getLogger()打印的内容会被收集到创建服务时指定的Logstore中。

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 {

        context.getLogger().info("hello world");
        outputStream.write(new String("hello world").getBytes());
    }
}

执行上面的代码输出的日志内容如下所示。

message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad891**** [INFO] hello world        
您也可以在代码中使用以下代码,打印不同类型的日志信息:
  • context.getLogger().warn:打印WARN级别的日志信息。
  • context.getLogger().error:打印ERROR级别的日志信息。

错误处理

您的函数如果在执行过程中抛出异常,函数计算会捕获并返回异常信息。

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 {
        throw new IOException("oops");
    }
} 
执行以下命令,调用目标函数:
s invoke -f event.json
输出示例:
{
  "errorMessage" : "oops",
  "errorType" : "java.io.IOException",
  "errorCause" : "oops",
  "stackTrace" : [ "example.HelloFC.handleRequest(HelloFC.java:15)" ]
}
Error: Request id: 45dd8d90-6b78-cce3-087c-8bf4ebc6c9af. Error type: UnhandledInvocationError 

发生异常时,函数调用的响应的HTTP header中会包含X-Fc-Error-Type: UnhandledInvocationError。关于函数计算错误类型的更多信息,请参见错误处理

使用Java自定义模块

如果您需要使用自定义的模块,则需要在打JAR包时,将他们与代码一起打包。下文以OpenJDK 8为例演示如何通过Maven将OSS的包打包到Java项目中。

  1. 安装Java和Maven。
    关于Java的详细信息,请参见官网。关于Maven的详细信息,请参见Installing Apache Maven
  2. 创建一个Java项目,目录结构如下。
    test/src/main/java/example/App.java       
  3. App.java文件内输入以下内容。
    package example;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    import com.aliyun.fc.runtime.Context;
    import com.aliyun.fc.runtime.StreamRequestHandler;
    import com.aliyun.fc.runtime.FunctionInitializer;
    
    /**
     * Hello world!
     *
     */
    public class App implements StreamRequestHandler, FunctionInitializer {
    
        public void initialize(Context context) throws IOException {
        }
    
        public void handleRequest(
                InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
            outputStream.write(new String("hello world\n").getBytes());
        }
    }             
  4. 在项目文件夹根目录下创建pom.xml文件,在pom.xml中添加maven-assembly-plugin插件,内容如下。
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>example</groupId>
      <artifactId>Java-example</artifactId>
      <packaging>jar</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>Java-example</name>
    
      <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.test.skip>true</maven.test.skip>
      </properties>
    <build>
      <plugins>
          <plugin>
              <artifactId>maven-assembly-plugin</artifactId>
              <version>3.1.0</version>
              <configuration>
                  <descriptorRefs>
                      <descriptorRef>jar-with-dependencies</descriptorRef>
                  </descriptorRefs>
                  <appendAssemblyId>false</appendAssemblyId> <!-- this is used for not append id to the jar name -->
              </configuration>
              <executions>
                  <execution>
                      <id>make-assembly</id> <!-- this is used for inheritance merges -->
                      <phase>package</phase> <!-- bind to the packaging phase -->
                      <goals>
                          <goal>single</goal>
                      </goals>
                  </execution>
              </executions>
          </plugin>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
              </configuration>
          </plugin>
      </plugins>
      </build>
    </project>            
  5. 项目需要引用Maven Central的外部包,可以根据需要添加依赖,下文以OSS Java SDK为例,pom.xml文件示例内容如下。
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>example</groupId>
      <artifactId>Java-example</artifactId>
      <packaging>jar</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>Java-example</name>
    
      <dependencies>
        <dependency>
          <groupId>com.aliyun.fc.runtime</groupId>
          <artifactId>fc-java-core</artifactId>
          <version>1.3.0</version>
        </dependency>
        <dependency>
         <groupId>com.aliyun.oss</groupId>
         <artifactId>aliyun-sdk-oss</artifactId>
         <version>2.6.1</version>
       </dependency>
      </dependencies>
    
      <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.test.skip>true</maven.test.skip>
      </properties>
    <build>
      <plugins>
          <plugin>
              <artifactId>maven-assembly-plugin</artifactId>
              <version>3.1.0</version>
              <configuration>
                  <descriptorRefs>
                      <descriptorRef>jar-with-dependencies</descriptorRef>
                  </descriptorRefs>
                  <appendAssemblyId>false</appendAssemblyId> <!-- this is used for not append id to the jar name -->
              </configuration>
              <executions>
                  <execution>
                      <id>make-assembly</id> <!-- this is used for inheritance merges -->
                      <phase>package</phase> <!-- bind to the packaging phase -->
                      <goals>
                          <goal>single</goal>
                      </goals>
                  </execution>
              </executions>
          </plugin>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
              </configuration>
          </plugin>
      </plugins>
      </build>
    </project>             
  6. 在项目的根目录下执行mvn package命令打包,编译输出如下。
    mvn package
    输出示例:
    [INFO] Scanning for projects...
     ...  ....   ....
    [INFO] --------------------------< example:example >---------------------------
    [INFO] Building Java-example 1.0-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
     ...  ....   ....
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  11.681 s
    [INFO] Finished at: 2020-03-26T15:55:05+08:00
    [INFO] ------------------------------------------------------------------------          

    如果显示编译失败,请根据输出的编译错误信息调整代码。

    编译后的JAR包位于项目文件夹内的target目录内,并根据pom.xml内的artifactIdversion字段命名为Java-example、1.0-SNAPSHOT.jar。

  7. 函数计算控制台上传JAR包。
    1. 登录函数计算控制台
    2. 在左侧导航栏,单击服务及函数
    3. 在顶部菜单栏,选择地域。
    4. 服务列表页面,单击目标服务。
    5. 函数管理页面,单击目标函数名称。
    6. 函数代码页面,在上传代码的下拉列表下,选择上传 zip 包上传文件夹通过 OSS 上传方式上传打包好的JAR包。

您也可以使用maven-shade-plugin插件进行打包。 通常上述的打包能够满足大多数的使用场景。但是如果您想把某些没有被Maven管理的JAR包打入到最终的JAR包中,例如您在resources/lib下引入的其他非Maven仓库中的JAR包,此时可以使用maven-jar-pluginmaven-dependency-plugin插件将其打入最终的JAR包中。

使用Serverless Devs打包部署

如果您使用Serverless Devs部署应用,可以使用s build命令来构建。

  1. 在工程的根目录下创建s.yaml文件,内容示例如下。
    edition: 1.0.0
    name: transform_fun
    access: default
    vars:
      region: cn-shenzhen
    services:
      fc-JavaDemo-test:
        component: devsapp/fc
        props:
          region: ${vars.region}
          service:
            name: JavaDemo
            description: helloworld
            internetAccess: true
          function:
            name: test
            handler: example.App::handleRequest
            initializer: example.App::initialize
            runtime: java8
            codeUri: ./            
  2. 执行以下命令将工程打包。
    s build
    返回结果如下。
    [2021-12-10 03:27:34] [INFO] [S-CLI] - Start ...
    [2021-12-10 03:27:34] [INFO] [FC-BUILD] - Build artifact start...
    builder begin to build, runtime is: java8, sourceDir:  /test/demo
    running task: flow MavenTaskFlow
    running task: MavenCompileTask
    ......
    running task: CopyMavenArtifacts
    copy maven artifacts from /test/demo/target/dependency
    [2021-12-10 03:27:47] [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
  3. 执行以下命令部署函数。
    s deploy           
    返回结果如下。
    [2021-12-10 03:31:03] [INFO] [S-CLI] - Start ...
    [2021-12-10 03:31:04] [INFO] [FC-DEPLOY] - Using region: cn-shenzhen
    [2021-12-10 03:31:04] [INFO] [FC-DEPLOY] - Using access alias: default
    [2021-12-10 03:31:04] [INFO] [FC-DEPLOY] - Using accessKeyID: LTAI4G4cwJkK4Rza6xd9****
    [2021-12-10 03:31:04] [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-10 03:31:05] [INFO] [FC-DEPLOY] - Checking Service eee exists
    [2021-12-10 03:31:06] [INFO] [FC-DEPLOY] - Checking Function eee exists
    [2021-12-10 03:31:06] [INFO] [FC-DEPLOY] - Fc detects that you have run build command for function: eee.
    [2021-12-10 03:31:06] [INFO] [FC-DEPLOY] - Using codeUri: /test/demo/.s/build/artifacts/eee/eee
    
    
    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 command.
    Do you agree with the behavior? yes
     Make service eee success.
     Make function eee/eee success.
    [2021-12-10 03:31:09] [INFO] [FC-DEPLOY] - Checking Service eee exists
    [2021-12-10 03:31:09] [INFO] [FC-DEPLOY] - Checking Function eee exists
    
    Tips for next step
    ======================
    * Display information of the deployed resource: s info
    * Display metrics: s metrics
    * Display logs: s logs
    * Invoke remote function: s invoke
    * Remove Service: s remove service
    * Remove Function: s remove function
    * Remove Trigger: s remove trigger
    * Remove CustomDomain: s remove domain
    
    
    
    fc-eee-eee:
      region:   cn-shenzhen
      service:
        name: JavaDemo
      function:
        name:       test
        runtime:    java8
        handler:    example.App::handleRequest
        memorySize: 128
        timeout:    3

使用IntelliJ IDEA安装依赖并打包上传

  1. 本地创建Java项目,pom.xml文件内容如下。
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>example</groupId>
      <artifactId>Java-example</artifactId>
      <packaging>jar</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>Java-example</name>
      <dependencies>
        <dependency>
          <groupId>com.aliyun.fc.runtime</groupId>
          <artifactId>fc-java-core</artifactId>
          <version>1.3.0</version>
        </dependency>
        <dependency>
         <groupId>com.aliyun.oss</groupId>
         <artifactId>aliyun-sdk-oss</artifactId>
         <version>2.6.1</version>
       </dependency>
      </dependencies>
    </project>       
  2. 使用IDEA导出JAR包相关配置。
    1. 选择File > Project Structure
      export jar
    2. 在弹出的窗口中选择Artifacts > + > JAR > from modules with dependencies
      choose from modules with dependencies
    3. 在配置窗口中使用默认配置,单击OK
      ok
    4. 回到IDEA的主菜单,选择Build > Build Artifacts下的Build或者Rebuild即可生成JAR包。
      build
  3. 您可以在项目根目录下out/artifacts/example_jar目录中将导出的JAR包上传函数计算。您通过函数计算控制台函数代码页签,在上传代码的下拉列表下,选择上传 zip 包上传文件夹通过 OSS 上传的方式上传导出的JAR包。

执行结果

登录函数计算控制台,即可查看创建成功的服务、函数,且触发执行可以返回正确的结果。