函数计算目前支持Java OpenJDK 1.8.0(runtime=java8)运行环境。本文介绍了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-323ad89118e5 [INFO] hello world        

使用context.getLogger().warncontext.getLogger().error分别可以打印WARN、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");
    }
} 

调用时收到的响应如下所示。

>>> invk hello-java -f /tmp/a.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

更多信息,请参见错误类型

使用自定义模块

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

  1. 安装Java和Maven。
    您可以通过 官网下载安装合适您系统的JDK。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. 代码执行页面,选择OSS上传或者代码包上传的方式上传打包好的JAR包。

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

使用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包上传函数计算。 您通过函数计算控制台代码执行页面,选择OSS上传或者代码包上传方式上传导出的JAR包。

使用Funcraft工具打包部署

如果您使用Funcraft部署应用,可以使用fun build命令来构建。

  1. 在工程的根目录下创建template.yml文件,内容示例如下。
    ROSTemplateFormatVersion: '2015-09-01'
    Transform: 'Aliyun::Serverless-2018-04-03'
    Resources:
      JavaDemo:
        Type: 'Aliyun::Serverless::Service'
        Properties:
          Description: 'helloworld'
        test:
          Type: 'Aliyun::Serverless::Function'
          Properties:
            Handler: example.App::handleRequest
            Initializer: example.App::initialize
            Runtime: java8
            CodeUri: './'               
  2. 执行以下命令将工程打包。
    fun build             
    返回结果如下。
    using template: template.yml
    start building function dependencies without docker
    
    building JavaDemo/test
    running task flow MavenTaskFlow
    running task: MavenCompileTask
    running task: MavenCopyDependencies
    running task: CopyMavenArtifacts
    
    Build Success
    
    Built artifacts: .fun/build/artifacts
    Built template: .fun/build/artifacts/template.yml
    
    Tips for next step
    ======================
    * Invoke Event Function: fun local invoke
    * Invoke Http Function: fun local start
    * Deploy Resources: fun deploy  
  3. 执行以下命令部署函数。
    fun deploy -y              
    返回结果如下。
    using template: .fun/build/artifacts/template.yml
    using region: cn-qingdao
    using accountId: ***********3743
    using accessKeyId: ***********Ptgk
    using timeout: 60
    
    Collecting your services information, in order to caculate devlopment changes...
    
    Resources Changes(Beta version! Only FC resources changes will be displayed):
    
    ***********
    
    Waiting for service JavaDemo to be deployed...
            Waiting for function test to be deployed...
                    Waiting for packaging function test code...
                    The function test has been packaged. A total of 3 files were compressed and the final size was 330.35 KB
            function test deploy success
    service JavaDemo deploy success  

执行结果

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