前回は、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」の内容は以下の通り。
1 2 3 4 5 6 | <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を動かすために必要なライブラリを追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | <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の内容は以下の通り。
1 2 3 4 | { "version": "2.0", "functionTimeout": "00:10:00" } |
1 2 3 4 5 6 7 8 9 | { "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メソッドが呼び出されるようになっている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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」コマンドを実行する。