これまでは、HTTPリクエストによりAzure Functionsが動作するアプリケーションのみ作成してきたが、Timer Triggerによって、一定時間が来たタイミングでAzure Functionsが動作するアプリケーションも作成することができる。
今回は、Azure Functions上でTimerTriggerによって動作するJavaアプリケーション(Spring Boot上)を作成してみたので、共有する。
前提条件
下記記事のBlob Storageの作成が完了していること。
また、下記記事に従ってAzure Functionsのサンプルプログラムを作成済であること。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。

pom.xmlの内容は以下の通りで、Lombokを追加している以外は、一般的な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.DemoAzureFunction</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>
<!-- lombokを利用するための設定 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</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>3.1.2</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>
また、host.json、local.settings.jsonの内容は以下の通り。
{
"version": "2.0",
"functionTimeout": "00:10:00"
}{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=azureblobpurinit;AccountKey=55facd(以下Azure Storageのアカウントキーを指定)",
"FUNCTIONS_WORKER_RUNTIME": "java",
"MAIN_CLASS":"com.example.DemoAzureFunction",
"AzureWebJobsDashboard": ""
}
}なお、local.settings.jsonの「AzureWebJobsStorage」は、HTTP Trigger以外では指定が必要になるため、Azure Storageのアカウント名・アカウントキーを指定している。アカウントキーは、Azure Storageの以下の画面で確認できるようになっている。

また、TimerTriggerのサービスクラス、Paramクラス、Resultクラスの内容は以下の通りで、サービスクラスが呼び出されたタイミングで、ログ出力するようになっている。なお、Azure上でFunctionsのログが出力できるよう、ログ出力時に、Paramクラスに追加したログ出力クラスを利用している。
package com.example.service;
import org.springframework.stereotype.Service;
import com.example.model.TimerTriggerParam;
import com.example.model.TimerTriggerResult;
@Service
public class TimerTriggerService {
/**
* タイマートリガーのテストを行うサービス.
* @param param TimerTrigger呼出用Param
* @return タイマートリガーのテストを行うサービスクラスの呼出結果
*/
public TimerTriggerResult timerTriggerTest(TimerTriggerParam param) {
// タイマートリガーのテストを行うサービスが呼び出されたことをログ出力する
param.getLogger().info("TimerTriggerService timerTriggerTest triggered: "
+ param.getTimerInfo());
// タイマートリガーのテストを行うサービスクラスの呼出結果を返却する
TimerTriggerResult result = new TimerTriggerResult();
result.setResult("success");
return result;
}
}package com.example.model;
import lombok.Data;
import java.util.logging.Logger;
@Data
public class TimerTriggerParam {
/** TimerTrigger情報 */
private String timerInfo;
/** ログ出力クラス */
private Logger logger;
}package com.example.model;
import lombok.Data;
@Data
public class TimerTriggerResult {
/** 結果情報 */
private String result;
}
さらに、メインクラスの内容は以下の通りで、TimerTriggerのサービスクラスのファンクション定義を追加している。
package com.example;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.example.model.TimerTriggerParam;
import com.example.model.TimerTriggerResult;
import com.example.service.TimerTriggerService;
@SpringBootApplication
public class DemoAzureFunction {
/** タイマートリガーのテストを行うサービスクラスのオブジェクト */
@Autowired
private TimerTriggerService timerTriggerService;
public static void main(String[] args) throws Exception {
SpringApplication.run(DemoAzureFunction.class, args);
}
/**
* タイマートリガーのテストを行い結果を返却する関数
* @return タイマートリガーのテストを行うサービスクラスの呼出結果
*/
@Bean
public Function<TimerTriggerParam, TimerTriggerResult> timerTriggerTest(){
return timerTriggerParam
-> timerTriggerService.timerTriggerTest(timerTriggerParam);
}
}また、TimerTriggerイベントが発生したタイミングで呼ばれるハンドラークラスの内容は以下の通りで、TimerTriggerのサービスクラスを呼び出している。
package com.example;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import com.example.model.TimerTriggerParam;
import com.example.model.TimerTriggerResult;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.TimerTrigger;
public class TimerTriggerTestHandler
extends FunctionInvoker<TimerTriggerParam, TimerTriggerResult>{
/**
* TimerTriggerによって、DemoAzureFunctionクラスのtimerTriggerTestメソッドを呼び出す.
* @param timerInfo TimerTriggerイベント情報
* @param context コンテキストオブジェクト
*/
// 「schedule = "0 */1 * * * *"」で、1分毎にTimerTriggerイベントが発生するようになっている
@FunctionName("timerTriggerTest")
public void timerTriggerTest(@TimerTrigger(name = "timerTriggerTest"
, schedule = "0 */1 * * * *") String timerInfo,
ExecutionContext context) {
context.getLogger().info("TimerTriggerTestHandler timerTriggerTest triggered: "
+ timerInfo);
TimerTriggerParam param = new TimerTriggerParam();
param.setTimerInfo(timerInfo);
param.setLogger(context.getLogger());
handleRequest(param, context);
}
}なお、上記クラスでは、1分毎にTimerTriggerイベントが発生するようになっている。TimerTriggerイベントの発生タイミングの記載方法については、以下のサイトの「NCRONTAB式」を参照のこと。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=java#ncrontab-expressions
サンプルプログラムの実行結果(ローカル)
先ほど作成したアプリケーションを、ローカル環境で実行する方法と実行結果は、以下の通り。
1) コマンドプロンプトを起動し、作成したアプリケーションのpom.xmlの存在するディレクトリに移動する。

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

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


なお、上記画像では、コピーしている箇所を一部省略している。
4) 「mvn azure-functions:run」コマンドを実行し、Azure Functiosを実行する。

5) 1分後以内に、以下の赤枠の、ハンドラークラスとサービスクラスのログが出力されることが確認できる。

6) さらに1分後に、以下の赤枠の、ハンドラークラスとサービスクラスのログが出力されることが確認できる。

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

サンプルプログラムの実行結果(Azure上)
Azure FunctionsへのSpring Bootを利用したJavaアプリケーションのデプロイ方法と実行結果は、以下の通り。なお、「az login」コマンドによるログインは既に実施済とする。
1) 「mvn azure-functions:deploy」コマンドを実行し、jarファイルをAzure Functionsにデプロイする。

また、一定時間経過後にログ確認を行うため、Azure Portalにログイン後、以下の操作を行う。
2) Azure Functionsの概要を表示した状態で「関数」メニューを押下する。

6) ログに、以下の赤枠の、ハンドラークラスとサービスクラスのログが出力されることが確認できる。

要点まとめ
- Azure Functions上では、Timer Triggerによって、一定時間が来たタイミングでAzure Functionsが動作するアプリケーションも作成することができる。
- Timer Triggerアノテーションのschedule属性で、TimerTriggerイベントの発生タイミングを指定できる。








