使用Java的Azure函数
概述
听说Azure Functions已经正式支持Java,所以我想试一下。
基本上,按照以下的步骤进行,但是中途遇到了几次问题,所以我会把问题和解决办法记录下来。
使用Java和Maven创建第一个函数(预览版)。
环境
-
- windows10
- eclipse
首先安装各种软件/应用。
.NET Core (.NET 核心)
从以下链接中安装:
https://www.microsoft.com/net/learn/get-started/windows
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。
安装程序
请选择”Windows Installer (.msi)”。
https://nodejs.org/en/download/
确认
安装完毕后,路径已经添加进去了。
可以在命令提示符下进行确认。
> node -v
v8.9.4
蓝色函数核心工具
这是一个可以在本地运行和调试Azure Functions的工具。
由于已经安装了Node.js,所以我们可以使用npm进行安装。
>npm install -g azure-functions-core-tools@core
C:\Users\xxx\AppData\Roaming\npm\func -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js
C:\Users\xxx\AppData\Roaming\npm\azurefunctions -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js
C:\Users\xxx\AppData\Roaming\npm\azfun -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js
azure-functions-core-tools@2.0.1-beta.22 postinstall C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools
node lib/install.js
[==================] Downloading Azure Functions Cli
+ azure-functions-core-tools@2.0.1-beta.22
added 46 packages in 8.164s
Azure 命令行界面
根据其名称可知,Azure CLI是一款可以通过命令行进行各种操作的工具。请参考下面链接进行安装。
https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli-windows
创建项目
在命令提示符中切换到Eclipse的工作区,并执行以下命令。您也可以使用图形界面创建新项目。
mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype
因为会问一些列的 groupId等等,所以给它取一个喜欢的名字吧。
Define value for property 'groupId': jp.suzuq
Define value for property 'artifactId': azure.functions
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1
Define value for property 'package' jp.suzuq: : jp.suzuq.azure.functions
Define value for property 'appName' azure.functions-20180222185249620: :
Define value for property 'appRegion' westus: : jp
马上开始建造
在项目完成阶段,已经创建了一个简单的功能类。
首先,我们将尝试构建并部署它,以确认其运行情况。
mvn clean package
如果按照原样操作,将会在以下错误中导致构建失败。
在项目azure.functions上,执行目标com.microsoft.azure:azure-functions-maven-plugin:0.1.10:package (package-functions)失败:执行目标com.microsoft.azure:azure-functions-maven-plugin:0.1.10:package的package-functions失败:执行com.microsoft.azure:azure-functions-maven-plugin:0.1.10:package时缺少一个必需的类:com/microsoft/azure/serverless/functions/annotation/DocumentDBInput。
由于azure-functions-maven-plugin存在问题,所以我尝试将版本升级到最新的0.2.1,结果运行成功了。
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>0.2.1</version>
</plugin>
在本地进行测试。
在将其部署到Azure之前,先在本地进行测试。
mvn azure-functions:run
在控制台上显示了functions的标志…
[INFO] Starting running Azure Functions...
%%%%%%
%%%%%%
@ %%%%%% @
@@ %%%%%% @@
@@@ %%%%%%%%%%% @@@
@@ %%%%%%%%%% @@
@@ %%%% @@
@@ %%% @@
@@ %% @@
%%
%
当你看到下面的提示时,表示准备工作已经完成。
Http Functions:
hello: http://localhost:7071/api/hello
根据指示,我将尝试使用curl来执行以下操作。
如果没有curl,请使用其他可以发送HTTP请求的工具。
curl -w '\n' -d hoge http://localhost:7071/api/hello
在控制台上输出日志,可以知道它正在执行。
[2018/05/02 9:51:28] Function started (Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
[2018/05/02 9:51:28] Executing 'Functions.hello' (Reason='This function was programmatically called via the host APIs.', Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
[2018/05/02 9:51:28] Java HTTP trigger processed a request.
[2018/05/02 9:51:28] Function "hello" (ID: 40832889-9057-4664-885e-c84407660a4a) invoked by Java Worker
[2018/05/02 9:51:28] Function completed (Success, Id=0aed5288-e5ff-47e2-868b-8455d7731b70, Duration=5ms)
[2018/05/02 9:51:28] Executed 'Functions.hello' (Succeeded, Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
随着200响应,返回了执行结果。
Hello, hoge'
部署
终于要展开部署了。
登录Azure
我会使用Azure CLI登录。在电脑上打开PowerShell并输入”az login”命令。
> az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.
请按照指示访问https://microsoft.com/devicelogin,并在PowerShell控制台中输入显示的代码。
完成”az login”命令后,将显示已登录的Azure信息。
[
{
"cloudName": "AzureCloud",
"id": "xxxxxxxxxxxxxxxxxxxxxxx",
"isDefault": true,
"name": "Microsoft Azure",
"state": "Enabled",
"tenantId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"user": {
"name": "xxxx@xxxxxxxxxxxxxxxxxxxxxx",
"type": "user"
}
}
]
使用Maven进行部署
回到Eclipse,执行下面的Maven命令。
mvn azure-functions:deploy
出了个错误…
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: Status code 400, {"error":{"code":"LocationNotAvailableForResourceGroup","message":"The provided location 'jp' is not available for resource group. List of available regions is 'centralus,eastasia,southeastasia,eastus,eastus2,westus,westus2,northcentralus,southcentralus,westcentralus,northeurope,westeurope,japaneast,japanwest,brazilsouth,australiasoutheast,australiaeast,westindia,southindia,centralindia,canadacentral,canadaeast,uksouth,ukwest,koreacentral,koreasouth,francecentral'."}}
由于 region 提到了某个问题,尝试将 pom.xml 中的 region 设置从 “jp” 更改为 “japaneast”。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<functionAppName>azure.functions-9999999.azurewebsites.net</functionAppName>
<functionAppRegion>japaneast</functionAppRegion>
</properties>
这次出现了不同的错误。
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: The host name azure.functions-20180222185249620.azurewebsites.net is invalid. OnError while emitting onNext value: retrofit2.Response.class
尝试更改函数名称。
由于函数名称还会成为在Azure上部署的jar文件的名称,因此在这里需要重新构建一次。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<functionAppName>testFunction</functionAppName>
<functionAppRegion>japaneast</functionAppRegion>
</properties>
又出现错误。
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: {
[ERROR] "message": "An error has occurred.",
[ERROR] "exceptionMessage": "An error occurred when trying to create a controller of type 'DeploymentController'. Make sure that the controller has a parameterless public constructor.",
[ERROR] "exceptionType": "System.InvalidOperationException",
[ERROR] "stackTrace": " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
[ERROR] "innerException": {
[ERROR] "message": "An error has occurred.",
[ERROR] "exceptionMessage": "The user name or password is incorrect.\r\n",
[ERROR] "exceptionType": "System.IO.IOException",
[ERROR] "stackTrace": " at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)\r\n at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)\r\n at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)\r\n at System.IO.Directory.CreateDirectory(String path)\r\n at System.IO.Abstractions.DirectoryWrapper.CreateDirectory(String path)\r\n at Microsoft.Web.Deployment.WebApi.PathHelper.GetLogFilesFolderPath(IEnvironment environment)\r\n at Microsoft.Web.Deployment.WebApi.DeploymentController..ctor()\r\n at lambda_method(Closure )\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
[ERROR] }
[ERROR] }: OnError while emitting onNext value: retrofit2.Response.class
因为我不明白你说的是什么,所以我尝试重复了一次,奇怪的是,竟然成功了。
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 35.832 s
[INFO] Finished at: 2018-05-10T18:55:38+09:00
[INFO] Final Memory: 48M/551M
[INFO] ------------------------------------------------------------------------
从Azure门户中查看通过部署的Function。
从侧边栏的”Function App”选项中进行查看。

让我试一试
可以从右上方的图像中“获取函数的URL”中复制URL。
让我们尝试通过复制的URL使用curl发送请求。
curl -w '\n' https://functionName.azurewebsites.net/api/hello -d AzureFunctions
Hello, AzureFunctions
发生了一些变动!
创建一个原始的函数
先暫時使用預設準備好的功能,然後開始創建自己的原始版本。
剛才執行的是在工作區自動生成的這個程式。
參考這個,我們來創建自己的函數。
package jp.suzuq.azure.functions;
import java.util.*;
import com.microsoft.azure.serverless.functions.annotation.*;
import com.microsoft.azure.serverless.functions.*;
/**
* Azure Functions with HTTP Trigger.
*/
public class Function {
/**
* This function listens at endpoint "/api/hello". Two ways to invoke it using "curl" command in bash:
* 1. curl -d "HTTP Body" {your host}/api/hello
* 2. curl {your host}/api/hello?name=HTTP%20Query
*/
@FunctionName("hello")
public HttpResponseMessage<String> hello(
@HttpTrigger(name = "req", methods = {"get", "post"}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");
// Parse query parameter
String query = request.getQueryParameters().get("name");
String name = request.getBody().orElse(query);
if (name == null) {
return request.createResponse(400, "Please pass a name on the query string or in the request body");
} else {
return request.createResponse(200, "Hello, " + name);
}
}
}
制作定时器触发器
默认情况下,这是一个通过HTTP请求触发的函数,所以我将尝试创建一个通过定时器触发的函数。
package jp.suzuq.azure.functions;
import com.microsoft.azure.serverless.functions.ExecutionContext;
import com.microsoft.azure.serverless.functions.annotation.FunctionName;
import com.microsoft.azure.serverless.functions.annotation.TimerTrigger;
public class TimerFunction {
@FunctionName("timer")
public void timer(
@TimerTrigger(name = "timer", schedule = "0 */5 * * * *") String timerInfo,
final ExecutionContext context)
{
context.getLogger().info("timerInfo : " + timerInfo);
}
}
我們在方法的第一個參數上加上了@TimerTrigger註解。
然後,使用cron形式在「schedule」中填寫了計劃。這就是函數的執行計劃。
添加图书馆
因为我想要使用Java的各种库,所以我想要用Java创建Azure Functions。
我们内部的maven库中有很多方便的Java类,我平时在开发中经常使用,所以我希望在Azure Functions中也能用到它们。
我会将其添加到pom.xml并部署进行测试。
<dependency>
<groupId>jp.hoge</groupId>
<artifactId>HogeCommon</artifactId>
<version>1.20.0</version>
</dependency>
尝试运行时,不知何故函数却超时并中途强制结束了。
为什么会超时令人苦恼了很多,但最终发现添加到pom的库没有包含在编译完成的模块中。要么就是给我抛出ClassNotFoundException吧…
如果要查看已构建的模块,您可以查看eclipse工作区的target文件夹,或者您可以通过Azure门户上的Kudu下载已部署的内容。


根据错误的构建方式,参考下面的内容来修改了pom.xml文件。如何在Java Azure函数中添加依赖的JAR。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive />
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

虽然还想尝试很多事情,但暂时先到这里吧。
我对此有些想法
我很满意能在 Azure Functions 中使用内部图书馆。但是准备工作确实有些麻烦。
对于简单的函数来说,用 JavaScript 最简单。
对于像这次这样可以重用公司内部资产并且可以在本地进行调试的情况,Java 的确是最好的选择。
我想根据用途来做选择。
有用的网站参考
使用 Java 和 Maven 创建第一个函数(预览版)
在 Java 中设置触发器
安装并登录 Azure CLI(Windows 版)
如何在 Java Azure 函数中添加依赖 JAR