前回は、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」コマンドを実行する。