前回は、Azure Potal上でAzure Functionsを作成してみたが、今回は、前回作成したAzure FunctionsにSpring Bootを利用したJavaアプリケーションを配置してみたので、その手順を共有する。
前提条件
下記記事に従ってAzure上でFunctionsを作成済であること。
また、下記記事に従って、STSをダウンロード済であること。
さらに、下記サイトの内容に従って、Apache Mavenをダウンロード済であること。
https://qiita.com/Junichi_M_/items/20daee936cd0c03c3115
また、下記サイトの内容に従って、Azure CLIをインストール済であること。
https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli-windows?tabs=azure-cli
さらに、下記サイトの「Azure Functions Core Tools のインストール」が完了していること。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash
やってみたこと
- Mavenプロジェクトの作成
- Spring Bootを利用したJavaアプリケーションの作成
- Spring Bootを利用したJavaアプリケーションのローカル環境での実行
- Azure FunctionsへのSpring Bootを利用したJavaアプリケーションのデプロイ
Mavenプロジェクトの作成
Azure Functions上で動作する、Spring Bootを利用したJavaアプリケーションを作成するには、まずは空のMavenプロジェクトを作成する。その手順は以下の通り。
1) STSを起動し、ファイルメニューの「新規」から「プロジェクト」を選択する。

2)「Mavenプロジェクト」を選択し、「次へ」ボタンを押下する。

3)「シンプルなプロジェクトの作成」にチェックを入れ、「次へ」ボタンを押下する。

4) グループID、アーティファクトIDを指定し、「完了」ボタンを押下する。

5) しばらく待つと空のMavenプロジェクトの作成が完了し、以下のようなプログラム構成になる。

また、作成後の「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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoAzureFunc</artifactId> <version>0.0.1-SNAPSHOT</version> </project>
Spring Bootを利用したJavaアプリケーションの作成
作成した空のMavenプロジェクトに、Azure Functionsにデプロイ(配置)するための、Spring Bootを利用したJavaアプリケーションを作成する。
作成後のプログラム構成は以下の通り。なお、赤枠は追加・変更したプログラムである。

なお、サンプルプログラムの内容は、以下のサイトを参照している。
https://github.com/Azure-Samples/hello-spring-function-azure
pom.xmlの内容は以下の通りで、Spring Boot関連とAzure Functionsを動かすために必要なライブラリを追加している。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demoAzureFunc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Hello Spring Function on Azure</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>
<azure.functions.maven.plugin.version>1.9.0</azure.functions.maven.plugin.version>
<!-- customize those properties. The functionAppName should be unique across Azure -->
<functionResourceGroup>azureAppDemo</functionResourceGroup>
<functionAppName>azureFuncDemoApp</functionAppName>
<functionAppServicePlan>ASP-azureAppDemo-8679</functionAppServicePlan>
<functionPricingTier>B1</functionPricingTier>
<functionAppRegion>japaneast</functionAppRegion>
<stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
<start-class>com.example.HelloFunction</start-class>
<spring.boot.wrapper.version>1.0.25.RELEASE</spring.boot.wrapper.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-web</artifactId>
<scope>provided</scope>
</dependency>
<!-- Spring Boot Webの設定 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>2.0.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<configuration>
<resourceGroup>${functionResourceGroup}</resourceGroup>
<appName>${functionAppName}</appName>
<appServicePlanName>${functionAppServicePlan}</appServicePlanName>
<region>${functionAppRegion}</region>
<pricingTier>${functionPricingTier}</pricingTier>
<runtime>
<os>Linux</os>
<javaVersion>8</javaVersion>
</runtime>
<appSettings>
<!-- Run Azure Function from package file by default -->
<property>
<name>WEBSITE_RUN_FROM_PACKAGE</name>
<value>1</value>
</property>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~3</value>
</property>
<property>
<name>FUNCTIONS_WORKER_RUNTIME</name>
<value>java</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>
${project.build.directory}/azure-functions/${functionAppName}
</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/azure
</directory>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${stagingDirectory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!--Remove obj folder generated by .NET SDK in maven clean-->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<filesets>
<fileset>
<directory>obj</directory>
</fileset>
</filesets>
</configuration>
</plugin>
<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>${spring.boot.wrapper.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>なお、下記サイトの情報によると、spring-cloud-function-dependenciesのバージョンが3.0以上だとClassCastExceptionが発生する場合があるので、spring-cloud-function-dependenciesのバージョンを2.0.1.RELEASEに変更している。
https://spring.io/blog/2018/09/25/spring-cloud-function-2-0-and-azure-functions
また、そうするとObjectMapperクラスが参照できなくなるため、spring-boot-starter-webをpom.xmlに追加している。
<2021年4月13日 追記>
spring-cloud-function-dependenciesのバージョンは、2021年3月16日にリリースしたバージョン3.1.2を利用すると、1つのAzure Functions内に複数のファンクションを含む場合の不具合が解消できている。
その場合、Handlerクラスの継承するクラスを「AzureSpringBootRequestHandler」クラスから「FunctionInvoker」クラスに変更する。
spring-cloud-function-dependenciesの3.1.2を利用した実装サンプルは、以下の記事を参照のこと。
host.json、local.settings.jsonの内容は以下の通り。
{
"version": "2.0",
"functionTimeout": "00:10:00"
}{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "java",
"MAIN_CLASS":"com.example.HelloFunction",
"AzureWebJobsDashboard": ""
}
}なお、host.json、local.settings.jsonの設定内容は以下のサイトを参照のこと。
●host.json
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json
●local.settings.json
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash#local-settings-file
また、Javaの各クラスの内容は以下の通りで、Azure FunctionsのAPIを呼び出したタイミングで、HelloHandlerクラスのexecuteメソッドが呼び出されるようになっている。
package com.example.model;
public class Greeting {
public Greeting() {
}
public Greeting(String message) {
this.message = message;
}
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}package com.example.model;
public class User {
public User() {
}
public User(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.example;
import com.example.model.Greeting;
import com.example.model.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.function.Function;
@SpringBootApplication
public class HelloFunction {
public static void main(String[] args) throws Exception {
SpringApplication.run(HelloFunction.class, args);
}
/**
* Userオブジェクトを引数に、Greetingオブジェクトを返却する関数
* @return 生成した関数
*/
@Bean
public Function<User, Greeting> hello() {
return user -> new Greeting("Hello, " + user.getName());
}
}package com.example;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler;
import com.example.model.Greeting;
import com.example.model.User;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
public class HelloHandler extends AzureSpringBootRequestHandler<User, Greeting> {
/**
* HTTP要求に応じて、HelloFunctionクラスのhelloメソッドを呼び出し、
* その戻り値をボディに設定したレスポンスを返す
* @param request リクエストオブジェクト
* @param context コンテキストオブジェクト
* @return レスポンスオブジェクト
*/
@FunctionName("hello")
public HttpResponseMessage execute(@HttpTrigger(name = "request"
, methods = { HttpMethod.GET, HttpMethod.POST }
, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
// リクエストオブジェクトからUserオブジェクトを取得する
User user = request.getBody().filter((u -> u.getName() != null))
.orElseGet(() -> new User(
request.getQueryParameters().getOrDefault(
"name", "<no name supplied> please provide a name as "
+ "either a query string parameter or in a POST body")));
context.getLogger().info("Greeting user name: " + user.getName());
// handleRequestメソッド内でHelloFunctionクラスのhelloメソッドを呼び出し、
// その戻り値をボディに設定したレスポンスを、JSON形式で返す
return request.createResponseBuilder(HttpStatus.OK)
.body(handleRequest(user, context))
.header("Content-Type", "text/json").build();
}
}
Spring Bootを利用したJavaアプリケーションのローカル環境での実行
先ほど作成したアプリケーションを、ローカル環境で実行する方法は以下の通り。
1) コマンドプロンプトを起動し、作成したアプリケーションのpom.xmlの存在するディレクトリに移動する。

2) 「mvn clean」コマンドを実行し、デプロイされたjarファイルが含まれるtarget ディレクトリ内をクリアする。

3) 「mvn package」コマンドを実行し、デプロイ用のjarファイルを作成する。


なお、上記画像では、コピーしている箇所を一部省略している。
また、「mvn package」コマンドの実行が完了すると、以下のように「demoAzureFunc-0.0.1-SNAPSHOT.jar」が作成されていることが確認できる。

4) 「mvn azure-functions:run」コマンドを実行し、Azure Functiosを実行する。

5) WEBブラウザ上で「http://localhost:7071/api/hello?name=Azure」とアクセスすると、以下のように、JSON形式で「”message”: “Hello, (nameで指定したパラメータ値)”」が表示されることが確認できる。

6) Azure Functionsを終了するには、「Ctrl+C」コマンドで「バッチジョブを終了しますか(Y/N)?」というメッセージが表示後、「Y」を選択しエンターキーを押下する。

Azure FunctionsへのSpring Bootを利用したJavaアプリケーションのデプロイ
Azure FunctionsへのSpring Bootを利用したJavaアプリケーションのデプロイ手順は、以下の通り。
1) 「az login」コマンドを実行し、Azure Portalにログインするアカウントでログインする。

下記ダイアログが表示されるため、ログインアカウントを選択する。

ログインに成功すると以下の画面に遷移する。

また、コンソールには以下のログイン情報が表示される。

2) 「mvn azure-functions:deploy」コマンドを実行し、jarファイルをAzure Functionsにデプロイする。

3) デプロイが完了したら、画面から確認する。デプロイ対象のAzure Functions概要の、URLの右「クリップボードにコピー」を押下し、URLをコピーした後で、「(コピーしたURL)/api/hello?name=Azure」にアクセスすると、以下のように、デプロイしたアプリケーションのAPI呼出結果が画面が表示される。


要点まとめ
- Azure Functionsにデプロイするための、Spring Bootを利用したJavaアプリケーションを作成するには、サンプルプログラムを流用する。
- Azure Functionsにデプロイするためのjarファイルを作成するには、「mvn clean」コマンドを実行後、「mvn package」コマンドを実行する。
- 「mvn azure-functions:run」コマンドを実行すると、ローカル環境でAzure Functionsの動作検証ができる。
- Azure Functionsに作成したjarファイルをデプロイするには、「mvn azure-functions:deploy」コマンドを実行する。





