これまでこのブログで取り上げてきたサンプルプログラム内では、Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信で、JSON文字列のString型で通信を行ってきたが、オブジェクトのままで通信を行うこともできる。
今回は、Azure App ServiceからAzure Functionsを呼び出す際のAPI通信で、オブジェクトのままで通信を行ってみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラム(共通クラス)の内容
作成したサンプルプログラム(共通クラス)の構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、JsonSerializeアノテーションを利用するためのjackson-databindを追加している。
<!-- JsonSerializeアノテーションを利用するための設定 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency>
また、ファイルダウンロードリストの戻り値に利用するFileDataクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、byte配列をByte配列に変更している。
package com.example.model;
import lombok.Data;
@Data
public class FileData {
/** ID */
private int id;
/** ファイル名 */
private String fileName;
/** ファイルパス */
private String filePath;
/** ファイルデータ */
private Byte[] fileData;
}さらに、ファイルアップロード時に利用するFileUploadParamクラスの内容は以下の通りで、ファイル名とByte配列をもつファイルデータを設定するようにしている。
package com.example.model;
import lombok.Data;
@Data
public class FileUploadParam {
/** ファイル名 */
private String fileName;
/** ファイルデータ */
private Byte[] fileData;
}また、ファイルダウンロードリストを取得する際のGetFileListParamクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、@JsonSerializeアノテーションを付与している。
package com.example.model;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
@Data
@JsonSerialize
public class GetFileListParam {
}なお、@JsonSerializeアノテーションを付与しないと、「com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.example.model.GetFileListParam and no properties discovered to create BeanSerializer」というエラーが出るため、オブジェクトをJSON文字列に変換するための@JsonSerializeアノテーションが必要になる。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureCommon
作成したサンプルプログラム(App Service側)の内容
作成したサンプルプログラム(App Service側)の構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。
<!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
また、コントローラクラスの内容は以下の通りで、HttpEntityクラスにParamクラスのオブジェクトを設定し、RestTemplateを用いたAPI通信の戻り値をResultクラスに変更している。また、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。
package com.example.demo;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.thymeleaf.util.StringUtils;
import com.example.model.FileData;
import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;
@Controller
public class DemoController {
/** RestTemplateオブジェクト */
@Autowired
private RestTemplate restTemplate;
/** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */
@Value("${demoAzureFunc.urlBase}")
private String demoAzureFuncBase;
/**
* ファイルリストを取得し、メイン画面を初期表示する.
* @param model Modelオブジェクト
* @param session HttpSessionオブジェクト
* @return メイン画面
*/
@GetMapping("/")
public String index(Model model, HttpSession session) {
// Azure FunctionsのgetFileList関数を呼び出すためのヘッダー情報を設定する
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// Azure FunctionsのgetFileList関数を呼び出すための引数を設定する
HttpEntity<GetFileListParam> entity
= new HttpEntity<>(new GetFileListParam(), headers);
// Azure FunctionsのgetFileList関数を呼び出す
ResponseEntity<GetFileListResult> response = restTemplate.exchange(
demoAzureFuncBase + "getFileList", HttpMethod.POST
, new HttpEntity<>(entity), GetFileListResult.class);
GetFileListResult getFileListResult = response.getBody();
model.addAttribute("fileDataList", getFileListResult.getFileDataList());
session.setAttribute("fileDataList", getFileListResult.getFileDataList());
model.addAttribute("message"
, "アップロードするファイルを指定し、アップロードボタンを押下してください。");
return "main";
}
/**
* ファイルダウンロード処理.
* @param id ID
* @param response HttpServletResponse
* @param session HttpSessionオブジェクト
* @return 画面遷移先(nullを返す)
*/
@RequestMapping("/download")
public String download(@RequestParam("id") String id
, HttpServletResponse response, HttpSession session) {
// セッションからファイルリストを取得する
@SuppressWarnings("unchecked")
ArrayList<FileData> fileDataList
= (ArrayList<FileData>) session.getAttribute("fileDataList");
FileData fileData = fileDataList.get(Integer.parseInt(id) - 1);
// ファイルダウンロードの設定を実施
// ファイルの種類は指定しない
response.setContentType("application/octet-stream");
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "");
response.setHeader("Content-Disposition", "attachment;filename=\""
+ getFileNameEncoded(fileData.getFileName()) + "\"");
// ダウンロードファイルへ出力
try (OutputStream out = response.getOutputStream()) {
out.write(ArrayUtils.toPrimitive(fileData.getFileData()));
out.flush();
} catch (Exception e) {
System.err.println(e);
}
// 画面遷移先はnullを指定
return null;
}
/**
* ファイルデータをAzure Blob Storageに登録する.
* @param uploadFile アップロードファイル
* @param model Modelオブジェクト
* @param redirectAttributes リダイレクト先に渡すパラメータ
* @return メイン画面
*/
@PostMapping("/upload")
public String add(@RequestParam("upload_file") MultipartFile uploadFile
, Model model, RedirectAttributes redirectAttributes) {
// ファイルが未指定の場合はエラーとする
if (uploadFile == null || StringUtils.isEmptyOrWhitespace(
uploadFile.getOriginalFilename())) {
redirectAttributes.addFlashAttribute("errMessage"
, "ファイルを指定してください。");
return "redirect:/";
}
// Azure FunctionsのfileUpload関数を呼び出すためのヘッダー情報を設定する
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// Azure FunctionsのfileUpload関数を呼び出すための引数を設定する
FileUploadParam fileUploadParam = new FileUploadParam();
try {
fileUploadParam.setFileName(uploadFile.getOriginalFilename());
fileUploadParam.setFileData(ArrayUtils.toObject(
IOUtils.toByteArray(uploadFile.getInputStream())));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
HttpEntity<FileUploadParam> entity
= new HttpEntity<>(fileUploadParam, headers);
// Azure FunctionsのfileUpload関数を呼び出す
ResponseEntity<FileUploadResult> response = restTemplate.exchange(
demoAzureFuncBase + "fileUpload", HttpMethod.POST
, entity, FileUploadResult.class);
FileUploadResult fileUploadResult = response.getBody();
// メイン画面へ遷移
model.addAttribute("message", fileUploadResult.getMessage());
return "redirect:/";
}
/**
* ファイル名をUTF-8でエンコードする.
* @param filePath ファイル名
* @return エンコード後のファイル名
*/
private String getFileNameEncoded(String fileName) {
String fileNameAft = "";
if (!StringUtils.isEmptyOrWhitespace(fileName)) {
try {
// ファイル名をUTF-8でエンコードして返却
fileNameAft = URLEncoder.encode(fileName, "UTF-8");
} catch (Exception e) {
System.err.println(e);
return "";
}
}
return fileNameAft;
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureApp
作成したサンプルプログラム(Azure Functions側)の内容
作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。
<!-- ArrayUtilsクラスを含むcommons-lang3の設定 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
また、ハンドラークラスの内容はそれぞれ以下の通りで、@HttpTriggerアノテーションでParamクラスのHttpRequestMessageを受け取るようにしている。
package com.example;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.http.MediaType;
import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
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 FileUploadHandler
extends FunctionInvoker<FileUploadParam, FileUploadResult> {
/**
* HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
* その戻り値をボディに設定したレスポンスを返す.
* @param request リクエストオブジェクト
* @param context コンテキストオブジェクト
* @return レスポンスオブジェクト
*/
@FunctionName("fileUpload")
public HttpResponseMessage execute(
@HttpTrigger(name = "request"
, methods = HttpMethod.POST
, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<FileUploadParam>> request,
ExecutionContext context) {
// リクエストオブジェクトからFileUploadParamオブジェクトを取得する
FileUploadParam fileUploadParam = request.getBody().get();
// handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
// その戻り値をボディに設定したレスポンスを、JSON形式で返す
return request.createResponseBuilder(HttpStatus.OK)
.body(handleRequest(fileUploadParam, context))
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
}
}package com.example;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.http.MediaType;
import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;
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 GetFileListHandler
extends FunctionInvoker<GetFileListParam, GetFileListResult> {
/**
* HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
* その戻り値をボディに設定したレスポンスを返す.
* @param request リクエストオブジェクト
* @param context コンテキストオブジェクト
* @return レスポンスオブジェクト
*/
@FunctionName("getFileList")
public HttpResponseMessage execute(
@HttpTrigger(name = "request"
, methods = HttpMethod.POST
, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<GetFileListParam>> request,
ExecutionContext context) {
// リクエストオブジェクトからFileUploadParamオブジェクトを取得する
GetFileListParam getFileListParam = request.getBody().get();
// handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
// その戻り値をボディに設定したレスポンスを、JSON形式で返す
return request.createResponseBuilder(HttpStatus.OK)
.body(handleRequest(getFileListParam, context))
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
}
}さらに、サービスクラスの内容はそれぞれ以下の通りで、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。
package com.example.service;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.blob.BlobOutputStream;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
@Service
public class FileUploadService {
/** Azure Storageのアカウント名 */
@Value("${azure.storage.accountName}")
private String storageAccountName;
/** Azure Storageへのアクセスキー */
@Value("${azure.storage.accessKey}")
private String storageAccessKey;
/** Azure StorageのBlobコンテナー名 */
@Value("${azure.storage.containerName}")
private String storageContainerName;
/**
* ファイルアップロード処理を行うサービス.
* @param fileUploadParam ファイルアップロード用Param
* @return ファイルアップロードサービスクラスの呼出結果
*/
public FileUploadResult fileUpload(FileUploadParam fileUploadParam) {
// ファイルアップロード処理
try {
// Blobストレージへの接続文字列
String storageConnectionString = "DefaultEndpointsProtocol=https;"
+ "AccountName=" + storageAccountName + ";"
+ "AccountKey=" + storageAccessKey + ";";
// ストレージアカウントオブジェクトを取得
CloudStorageAccount storageAccount
= CloudStorageAccount.parse(storageConnectionString);
// Blobクライアントオブジェクトを取得
CloudBlobClient blobClient = storageAccount.createCloudBlobClient();
// Blob内のコンテナーを取得
CloudBlobContainer container
= blobClient.getContainerReference(storageContainerName);
// Blob内のコンテナーにデータを書き込む
CloudBlockBlob blob = container.getBlockBlobReference(
fileUploadParam.getFileName());
BlobOutputStream output = blob.openOutputStream();
output.write(ArrayUtils.toPrimitive(fileUploadParam.getFileData()));
output.close();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
FileUploadResult result = new FileUploadResult();
result.setMessage("ファイルアップロードが完了しました。");
return result;
}
}package com.example.service;
import java.io.InputStream;
import java.util.ArrayList;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.example.model.FileData;
import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.ListBlobItem;
@Service
public class GetFileListService {
/** Azure Storageのアカウント名 */
@Value("${azure.storage.accountName}")
private String storageAccountName;
/** Azure Storageへのアクセスキー */
@Value("${azure.storage.accessKey}")
private String storageAccessKey;
/** Azure StorageのBlobコンテナー名 */
@Value("${azure.storage.containerName}")
private String storageContainerName;
/**
* ファイルリスト取得処理を行うサービス.
* @param getFileListParam ファイル取得用Param
* @return ファイルリスト取得サービスクラスの呼出結果
*/
public GetFileListResult getFileList(GetFileListParam getFileListParam) {
GetFileListResult result = new GetFileListResult();
// ファイルアップロード処理
try {
// Blobストレージへの接続文字列
String storageConnectionString = "DefaultEndpointsProtocol=https;"
+ "AccountName=" + storageAccountName + ";"
+ "AccountKey=" + storageAccessKey + ";";
// ストレージアカウントオブジェクトを取得
CloudStorageAccount storageAccount
= CloudStorageAccount.parse(storageConnectionString);
// Blobクライアントオブジェクトを取得
CloudBlobClient blobClient = storageAccount.createCloudBlobClient();
// Blob内のコンテナーを取得
CloudBlobContainer container
= blobClient.getContainerReference(storageContainerName);
// Blob情報を取得し、戻り値に設定する
ArrayList<FileData> fileDataList = new ArrayList<>();
int index = 1;
for (ListBlobItem blobItem : container.listBlobs()) {
// ファイル名・ファイルパスを取得
String filePath = blobItem.getUri().toString();
String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
// Blobデータを読み込み
CloudBlockBlob blob = container.getBlockBlobReference(fileName);
InputStream input = blob.openInputStream();
// ファイルデータを設定
FileData fileData = new FileData();
fileData.setId(index);
fileData.setFileName(fileName);
fileData.setFilePath(filePath);
fileData.setFileData(ArrayUtils.toObject(IOUtils.toByteArray(input)));
fileDataList.add(fileData);
index++;
}
result.setFileDataList(fileDataList);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return result;
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureFunc
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の各記事の「作成したサンプルプログラムの実行結果」と同じになる。
要点まとめ
- Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信は、オブジェクトのままで通信を行うこともできる。その際、byte配列の項目があればByte配列に変更し、空のParamオブジェクトがあれば@JsonSerializeアノテーションを付与する必要がある。
- byte配列⇔Byte配列の変換を行うには、Apache Commons Lang 3内のArrayUtilsクラスのメソッドを用いればよい。





