Microsoft LearnのJava on Azureを進めていくと結構最初の方でSpring WebアプリをマイクロサービスとしてAzure Spring Appにデプロイする手順が登場する。一通りやってみた感想として、知らないことが多すぎて1つ1つしっかり理解することにかなり時間を使った。私のような初心者にはかなりハードルの高いトレーニングモジュール。とにかく早くJava on Azureを消化したい。
Azure Spring Appには、月間50時間分のvCPU消費、100時間分のメモリ消費(GB)の無料枠が含まれる。
例えば、Azure Spring AppsのBASIC Tier(2vCPUと4GBメモリ)のインスタンスを1つ利用した場合、25時間分/月はチャージされない。
Microsoft Learnの内容はAzure Database for MySQLの単一サーバーを使った内容になっているが、2024/9/16に廃止予定とのことなので、ここでは自分なりに調べつつ、Azure Database for MySQLのフレキシブルサーバーを使った内容にする。
執筆時点のSpring Bootの最新バージョンは [3.2.2] だが、Azure Spring Appsが対応しているバージョンは [3.1.x] もしくは [3.0.x] だったので、ここでは執筆時点で3.1.xの最新バージョンである [3.1.8] を使用する。この情報は、私の確認した限りではMSのオンラインドキュメントでは明示されておらず、azコマンド実行時に対応しているバージョン番号が表示された。
以下、az コマンドで Spring Boot 3.2.2でビルドしたjarファイルをデプロイしようとした時に表示されたメッセージ。
Action:
Consider applying the following actions:
- Change Spring Boot version to one of the following versions [3.0.x, 3.1.x] .
You can find the latest Spring Boot versions here [<https://spring.io/projects/spring-boot#learn>].
ログイン中のセッション情報を確認して意図した作業環境であることを確認。もしくは意図した作業環境でログインする。
% az account show
{
"environmentName": "AzureCloud",
"homeTenantId": "xxx",
"id": "xxx",
"isDefault": true,
"managedByTenants": [],
"name": "サブスクリプション名",
"state": "Enabled",
"tenantId": "xxx",
"user": {
"name": "xxx@xxx.onmicrosoft.com",
"type": "user"
}
}
Azure CLIからAzure Spring AppにWEBアプリをデプロイする場合、Azコマンドにspring拡張機能をインストールする。
% az extension add -n spring -y
Default enabled including preview versions for extension installation now. Disabled in May 2024. Use '--allow-preview true' to enable it specifically if needed. Use '--allow-preview false' to install stable version only.
Azure CLIでコマンドを発行する場合、いくつかの環境変数を事前設定しておくと後々楽。
% AZ_RESOURCE_GROUP_NAME=azure-spring-workshop
% AZ_SPRING_CLOUD_NAME=azure-spring-workshop-20240206
もう一つ、Azure CLIではコマンド対象となるリソースグループやリソースを端末のデフォルトとして設定しておく事ができる。今回のAzure Spring Appであれば、以下のデフォルト値を設定しておくとこれまた後々楽。
% az configure --defaults location=japaneast group=${AZ_RESOURCE_GROUP_NAME}
端末に設定されている各種変数は「az config get」で一覧を取得できる。
% az config get
Command group 'config' is experimental and under development. Reference and support levels: <https://aka.ms/CLI_refstatus>
{
"cloud": [
{
"name": "name",
"source": "/Users/sato/.azure/config",
"value": "AzureCloud"
}
],
"core": [
{
"name": "first_run",
"source": "/Users/sato/.azure/config",
"value": "yes"
}
],
"defaults": [
{
"name": "group",
"source": "/Users/sato/.azure/config",
"value": "azure-spring-workshop"
},
{
"name": "spring",
"source": "/Users/sato/.azure/config",
"value": "azure-spring-workshop-20240206"
},
{
"name": "location",
"source": "/Users/sato/.azure/config",
"value": "japaneast"
}
]
}
環境情報として設定できる項目のリストは以下に定義されている。
Azure Spring Appを作成する場合、”Microsoft.AppPlatform”というプロバイダーが必要らしいが、Azure CLIで実行すると自動的に検出して有効にしてくれた。
以下のコマンドではskuしか指定していないが、自動的にLog Analytics とApplication Insights を有効にしてくれた。この場合、Log AnalyticsとApplication Insightsのリソース名はAzure Spring Appの名称と同じ名称になる。
% az spring create -n $AZ_SPRING_CLOUD_NAME --sku standard
Resource provider 'Microsoft.AppPlatform' used by this operation is not registered. We are registering for you.
Registration succeeded.
- Creating Service ..
Start configure Application Insights
Log Analytics workspace "azure-spring-workshop-20240206" was created for this Azure Spring Apps. You can visit <https://portal.azure.com/#resource/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/Microsoft.OperationalInsights/workspaces/azure-spring-workshop-20240206/overview> to view your Log Analytics workspace
Application Insights "azure-spring-workshop-20240206" was created for this Azure Spring Apps. You can visit <https://portal.azure.com/#resource/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/microsoft.insights/components/azure-spring-workshop-20240206/overview> to view your Application Insights component
{
"id": "/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/Microsoft.AppPlatform/Spring/azure-spring-workshop-20240206",
"location": "japaneast",
"name": "azure-spring-workshop-20240206",
"properties": {
"fqdn": "azure-spring-workshop-20240206.azuremicroservices.io",
"infraResourceGroup": null,
"maintenanceScheduleConfiguration": null,
"managedEnvironmentId": null,
"marketplaceResource": null,
"networkProfile": {
"appNetworkResourceGroup": null,
"appSubnetId": null,
"ingressConfig": null,
"outboundIPs": {
"publicIPs": [
"xxx.xxx.xxx.xxx",
"xxx.xxx.xxx.xxx"
]
},
"outboundType": "loadBalancer",
"requiredTraffics": null,
"serviceCidr": null,
"serviceRuntimeNetworkResourceGroup": null,
"serviceRuntimeSubnetId": null
},
"powerState": "Running",
"provisioningState": "Succeeded",
"serviceId": "xxx",
"version": 3,
"vnetAddons": null,
"zoneRedundant": false
},
"resourceGroup": "azure-spring-workshop",
"sku": {
"capacity": null,
"name": "S0",
"tier": "Standard"
},
"systemData": {
"createdAt": "2024-02-06T08:48:16.457609+00:00",
"createdBy": "xxx@xxx.onmicrosoft.com",
"createdByType": "User",
"lastModifiedAt": "2024-02-06T08:48:16.457609+00:00",
"lastModifiedBy": "xxx@xxx.onmicrosoft.com",
"lastModifiedByType": "User"
},
"tags": null,
"type": "Microsoft.AppPlatform/Spring"
}
作成したAzure Spring Appsに対して、Spring Boot版のTODOアプリをデプロイするためのアプリケーションインスタンスを作成する。
% az spring app create --name todo-service --service $AZ_SPRING_CLOUD_NAME --runtime-version java_17
This command usually takes minutes to run. Add '--verbose' parameter if needed.
[1/2] Creating app todo-service
[2/2] Creating default deployment with name "default"
App create succeeded
{
"id": "/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/Microsoft.AppPlatform/Spring/azure-spring-workshop-20240206/apps/todo-service",
"identity": null,
"location": "japaneast",
"name": "todo-service",
"properties": {
"activeDeployment": {
"id": "/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/Microsoft.AppPlatform/Spring/azure-spring-workshop-20240206/apps/todo-service/deployments/default",
"name": "default",
"properties": {
"active": true,
"deploymentSettings": {
"addonConfigs": null,
"apms": null,
"containerProbeSettings": null,
"environmentVariables": null,
"livenessProbe": {
"disableProbe": false,
"failureThreshold": 3,
"initialDelaySeconds": 300,
"periodSeconds": 10,
"probeAction": {
"type": "TCPSocketAction"
},
"successThreshold": 1,
"timeoutSeconds": 3
},
"readinessProbe": {
"disableProbe": false,
"failureThreshold": 3,
"initialDelaySeconds": 0,
"periodSeconds": 5,
"probeAction": {
"type": "TCPSocketAction"
},
"successThreshold": 1,
"timeoutSeconds": 3
},
"resourceRequests": {
"cpu": "1",
"memory": "1Gi"
},
"scale": null,
"startupProbe": null,
"terminationGracePeriodSeconds": 90
},
"instances": [
{
"discoveryStatus": "UNREGISTERED",
"name": "todo-service-default-12-55b858c54-vvfm5",
"reason": null,
"startTime": "2024-02-08T15:00:11Z",
"status": "Running",
"zone": null
}
],
"provisioningState": "Succeeded",
"source": {
"jvmOptions": null,
"relativePath": "<default>",
"runtimeVersion": "Java_17",
"type": "Jar",
"version": null
},
"status": "Running"
},
"resourceGroup": "azure-spring-workshop",
"sku": {
"capacity": 1,
"name": "S0",
"tier": "Standard"
},
"systemData": {
"createdAt": "2024-02-08T15:00:07.687094+00:00",
"createdBy": "xxx@xxx.onmicrosoft.com",
"createdByType": "User",
"lastModifiedAt": "2024-02-08T15:00:07.687094+00:00",
"lastModifiedBy": "xxx@xxx.onmicrosoft.com",
"lastModifiedByType": "User"
},
"type": "Microsoft.AppPlatform/Spring/apps/deployments"
},
"addonConfigs": {
"applicationConfigurationService": {},
"serviceRegistry": {}
},
"customPersistentDisks": null,
"enableEndToEndTls": false,
"fqdn": "azure-spring-workshop-20240206.azuremicroservices.io",
"httpsOnly": false,
"ingressSettings": {
"backendProtocol": "Default",
"clientAuth": null,
"readTimeoutInSeconds": 300,
"sendTimeoutInSeconds": 60,
"sessionAffinity": "None",
"sessionCookieMaxAge": 0
},
"loadedCertificates": null,
"persistentDisk": {
"mountPath": "/persistent",
"sizeInGb": 0,
"usedInGb": null
},
"provisioningState": "Succeeded",
"public": false,
"secrets": null,
"temporaryDisk": {
"mountPath": "/tmp",
"sizeInGb": 5
},
"testEndpointAuthState": null,
"testEndpointAuthStatus": "Enabled",
"url": null,
"vnetAddons": null,
"workloadProfileName": null
},
"resourceGroup": "azure-spring-workshop",
"systemData": {
"createdAt": "2024-02-08T15:00:01.849831+00:00",
"createdBy": "xxx@xxx.onmicrosoft.com",
"createdByType": "User",
"lastModifiedAt": "2024-02-08T15:00:01.849831+00:00",
"lastModifiedBy": "xxx@xxx.onmicrosoft.com",
"lastModifiedByType": "User"
},
"type": "Microsoft.AppPlatform/Spring/apps"
}
Springマイクロサービスからアクセスするデータベースサーバーとして、Azure Database for MySQLを作成する。
(Microsoft Learnでは単一サーバーを作成しているが、冒頭で触れた通り、単一サーバーは今後廃止予定なので、ここではフレキシブルサーバーを作成する)
デフォルトとして全AzureリソースからアクセスできるようにFirewallルールを設定されるが、途中で今使っている端末のIPも許可するかどうか聞かれる。なんて親切設計。MySQL単一サーバーの時はAzureリソース作成した後にデータベースインスタンスを作成していたが、フレキシブルサーバーの場合はデフォルトで1つのコマンドで作成してくれる。これも場面によっては親切設計。
% az mysql flexible-server create --name ${AZ_SPRING_CLOUD_NAME}-mysql-flex --database-name todos --admin-user todosadmin --admin-password xxx
Checking the existence of the resource group 'azure-spring-workshop'...
Resource group 'azure-spring-workshop' exists ? : True
Detected current client IP : xxx.xxx.xxx.xxx
Do you want to enable access to client xxx.xxx.xxx.xxx (y/n) (y/n): y
IOPS is 396 which is either your input or free(maximum) IOPS supported for your storage size and SKU.
Creating MySQL Server 'azure-spring-workshop-mysql-flex' in group 'azure-spring-workshop'...
Your server 'azure-spring-workshop-mysql-flex' is using sku 'Standard_B1ms' (Paid Tier). Please refer to <https://aka.ms/mysql-pricing> for pricing details
Configuring server firewall rule to accept connections from 'xxx.xxx.xxx.xxx'...
Creating MySQL database 'todos'...
Make a note of your password. If you forget, you would have to reset your password with'az mysql flexible-server update -n azure-spring-workshop-mysql-flex -g azure-spring-workshop -p <new-password>'.
Try using az 'mysql flexible-server connect' command to test out connection.
{
"connectionString": "mysql todos --host azure-spring-workshop-mysql-flex.mysql.database.azure.com --user todosadmin --password=xxx",
"databaseName": "todos",
"firewallName": "FirewallIPAddress_2024-2-12_19-3-46",
"host": "azure-spring-workshop-mysql-flex.mysql.database.azure.com",
"id": "/subscriptions/xxx/resourceGroups/azure-spring-workshop/providers/Microsoft.DBforMySQL/flexibleServers/azure-spring-workshop-mysql-flex",
"location": "Japan East",
"password": "xxx",
"resourceGroup": "azure-spring-workshop",
"skuname": "Standard_B1ms",
"username": "todosadmin",
"version": "5.7"
}
az mysql flexible-serverのCLIコマンド詳細は以下を参照。
MySQLのFirewallパラメータの詳細は以下を参照。
デフォルト値が記載されていないパラメータもあるため、実際に指定せずに確認してみた。最初の3つは明示的に指定した方が良いと思う。。。
実際にサーバーをデプロイするわけではなく、Azure Spring Appsの「Config Server」という項目に対する設定作業のこと。
SpringBootアプリケーションの構成ファイルをGithubリポジトリに保存し、そのリポジトリをAzure Spring Appsの構成ファイルとしてチェックアウトし、SpringBootアプリの各クラスターから構成ファイルを参照する設定を行う。
Javaという言語以外に、Spring Boot、Maven、Azure Spring Apps、MySQLに加えて、ここではGithubも出てくる。。 このように複数の技術要素が絡んだチュートリアルは「初学者泣かせ」だ。 手順の中で色々な要素が出てくると、おそらく自分が今何をやっているのか想像できなくなってくると思う。 全体を俯瞰した図が非常に重要で、どの要素に対して何を実行したかという証跡を書いていくと全部終わった後に見返す事で一気に理解が深まる事がある。 私も一回全体を通して実施してみて全体像を整理してから自分が理解できていなかった部分を深掘りしつつ作業ログを書くようにしている。
マイクロサービスを構成する際の重要な要素として、構成情報を提供する機能のサービス化がある。複数のプログラムから1つの構成情報を参照できるようになるため、サービスを構成するインスタンスがスケールアウトした時にも複数のインスタンスで簡単に同じ構成情報にアクセスする事ができるようになる。
また、構成情報をGithubなどのバージョン管理できるリポジトリで管理することで、構成情報を一元管理できたり、どのバージョンの構成情報がどの環境で利用されているのかなどをタグで管理したり、必要があればロールバックすることで全てのインスタンスで一斉に同じ構成情報を更新する事ができるなどのメリットがある。
自分のGithubに対して、新規にプライベートなリポジトリを作成し、そこに構成ファイルを作成する。
簡単に作成した時のスクリーンショットを掲載する。
今回は「Repository name」に「SpringCloudConfigServer」を指定し、「Private」として作成。
ここでは簡単にGithub上で「application.yml」というファイルを作成する。
ファイルを作成したら「Commit changes」をクリックしてファイルを保存する。
「個人用トークン」とは、有効期限かつアクセススコープを限定した代理パスワード、Azureで言うところのアプリケーションキーのようなもの。Config ServerからGithubにアクセスする場合、クレデンシャル情報(ユーザーIDとパスワード)を入力する必要があるが、通常利用しているパスワードをそのまま利用すると、自分のGithubアカウントで管理している全リポジトリに対してアカウントと同じ権限で恒久的なアクセスを許可するようなものなので、それを目的に応じて制限できる。
今回はConfig Serverに対してapplication.ymlを保存しているリポジトリだけにアクセス権限を付与した個人用トークンを無期限で作成する。
Micorosft Learnでは「repoチェックをオフ」と記載されているが、repoはオンにしないと何もできない。
作成後にトークンが表示されるのでコピーボタンでコピーしてどこかに保持しておく。
先ほど作成したAzure Spring Appsに対してConfig Serverを設定する。
「検証」をクリックして、エラーが発生しなければ「適用」をクリックする。少し時間がかかるがConfig Serverの更新が完了する。
Config Server はAzure Spring Appsリソース単位に設定するが、データベースへの接続設定はリソース内の各Spring Bootアプリケーション単位に設定する。
また、以前は「サービスバインド」という機能で接続文字列を設定していたが、この機能が将来廃止され、今後は「サービスコネクタ」という新しい機能を用いて接続文字列を設定する。(サービスバインドとサービスコネクタの違いについては別途調査する)
Azure PortalからAzure Spring Appリソースにアクセスし、アプリケーションを選択、「サービスコネクタ」を選択して新規に作成する。
「基本情報」タブの「サービスの種類」で「MySQLフレキシブルサーバー用のデータベース」を選択する。
「認証」タブで「接続文字列」を選択し、データベースユーザー情報を登録する。
「ネットワーク」タブで「ファイアウォールルールを構成し・・・」になっていればOK。
最後に「作成」をクリックすればサービスコネクタが作成される。
以下のようにサービスコネクタが作成され、接続情報(URL, username, password)が確認できる。
Microsoft Learnの手順としてはここまでだが、「接続の状態」が「未検証」だったのが気になったので「検証」を実行。
接続先のデータベースの存在確認、Firewall確認(おそらくネットワーク的に到達可能である事の確認)、設定したユーザー情報を用いた接続確認の3つを検証し、正常に接続できた事が確認できたという事だろう。
長かったが、これでSpring Bootマイクロサービスを構築する準備が整った。
Spring Bootのバージョンが「3.1.5-RELEASE」となっているが、mavenのparentプロジェクト参照時に失敗するため、最新安定版(記事作成時点では「3.2.2」)を指定した。
MS Learnの記載(本記事執筆時点)。
% curl https://start.spring.io/starter.tgz /
-d type=maven-project /
-d dependencies=web,mysql,data-jpa,cloud-eureka,cloud-config-client /
-d baseDir=todo-service /
-d bootVersion=3.1.5.RELEASE /
-d javaVersion=17 | tar -xzvf -
最新安定版(本記事執筆時点)に変更。
% curl https://start.spring.io/starter.tgz /
-d type=maven-project /
-d dependencies=web,mysql,data-jpa,cloud-eureka,cloud-config-client /
-d baseDir=todo-service /
-d bootVersion=3.2.2 /
-d javaVersion=17 | tar -xzvf -
ソースコードはMS Learnのものを利用する。
MS Learnに記載のコードはいくつか古い状態なのでそのままビルドしようとするとエラーになるため、以下対応を実施。
javax.persistenceパッケージ参照エラーとなりjavaコンパイルエラーとなる。
(エラー情報がフェーズ毎に出力された後、再度全体で出力される(重複する)のをなんとかしてほしい。。)
% ./mvnw clean package -DskipTests
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ demo ---
[INFO] Deleting /Users/sato/proj/learn/java/SpringTips/todo-service/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ demo ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ demo ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 4 source files with javac [debug release 17] to target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[3,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[4,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[5,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[7,2] シンボルを見つけられません
シンボル: クラス Entity
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[5,24] パッケージjavax.annotationは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoRepository.java:[3,47] シンボルを見つけられません
シンボル: クラス JpaRespository
場所: パッケージ org.springframework.data.jpa.repository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoRepository.java:[5,41] シンボルを見つけられません
シンボル: クラス JpaRespository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[17,6] シンボルを見つけられません
シンボル: クラス Id
場所: クラス com.example.demo.Todo
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[18,6] シンボルを見つけられません
シンボル: クラス GeneratedValue
場所: クラス com.example.demo.Todo
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[16,6] シンボルを見つけられません
シンボル: クラス PostConstruct
場所: クラス com.example.demo.TodoController
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[18,23] シンボルを見つけられません
シンボル: メソッド saveAll(java.util.List<com.example.demo.Todo>)
場所: タイプcom.example.demo.TodoRepositoryの変数 todoRepository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[29,30] シンボルを見つけられません
シンボル: メソッド findAll()
場所: タイプcom.example.demo.TodoRepositoryの変数 todoRepository
[INFO] 12 errors
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.168 s
[INFO] Finished at: 2024-02-18T11:36:15+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project demo: Compilation failure: Compilation failure:
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[3,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[4,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[5,25] パッケージjavax.persistenceは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[7,2] シンボルを見つけられません
[ERROR] シンボル: クラス Entity
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[5,24] パッケージjavax.annotationは存在しません
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoRepository.java:[3,47] シンボルを見つけられません
[ERROR] シンボル: クラス JpaRespository
[ERROR] 場所: パッケージ org.springframework.data.jpa.repository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoRepository.java:[5,41] シンボルを見つけられません
[ERROR] シンボル: クラス JpaRespository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[17,6] シンボルを見つけられません
[ERROR] シンボル: クラス Id
[ERROR] 場所: クラス com.example.demo.Todo
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/Todo.java:[18,6] シンボルを見つけられません
[ERROR] シンボル: クラス GeneratedValue
[ERROR] 場所: クラス com.example.demo.Todo
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[16,6] シンボルを見つけられません
[ERROR] シンボル: クラス PostConstruct
[ERROR] 場所: クラス com.example.demo.TodoController
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[18,23] シンボルを見つけられません
[ERROR] シンボル: メソッド saveAll(java.util.List<com.example.demo.Todo>)
[ERROR] 場所: タイプcom.example.demo.TodoRepositoryの変数 todoRepository
[ERROR] /Users/sato/proj/learn/java/SpringTips/todo-service/src/main/java/com/example/demo/TodoController.java:[29,30] シンボルを見つけられません
[ERROR] シンボル: メソッド findAll()
[ERROR] 場所: タイプcom.example.demo.TodoRepositoryの変数 todoRepository
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] <http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException>
以下の記事を参照すると、Java EE (javax) は jakarta EE (jakarta) に移行したらしい。
よって、パッケージ名に「javax」が含まれている部分を「jakarta」に置き換えると良いようだ。
package com.example.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Todo {
・・・
}
これも「Java EE」が「Jakarta EE」に移行された影響。
package com.example.demo;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.PostConstruct;
import java.util.Arrays;
@RestController
public class TodoController {
・・・
}
これでプロジェクトのビルドに成功
% ./mvnw clean package -DskipTests
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ demo ---
[INFO] Deleting /Users/sato/proj/learn/java/SpringTips/todo-service/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ demo ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ demo ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 4 source files with javac [debug release 17] to target/classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ demo ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/todo-service/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ demo ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 1 source file with javac [debug release 17] to target/test-classes
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ demo ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ demo ---
[INFO] Building jar: /Users/sato/proj/learn/java/SpringTips/todo-service/target/demo-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (repackage) @ demo ---
[INFO] Replacing main artifact /Users/sato/proj/learn/java/SpringTips/todo-service/target/demo-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/SpringTips/todo-service/target/demo-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.395 s
[INFO] Finished at: 2024-02-18T12:04:51+09:00
[INFO] ------------------------------------------------------------------------
以下のCLIコマンドでAzure Spring Appsのtodo-serviceアプリにデプロイする。
デプロイのログからいくつか知っておくべきAzure Spring Appsの機能があるので確認しておく。
また、このログから、今回使った最新版のSpring Boot 3.2.2は、Azure Spring Appsの対応しているバージョンとは互換性がないというエラーが発生しているのでこれに対処する。
% az spring app deploy --name todo-service --service "$AZ_SPRING_CLOUD_NAME" --resource-group "$AZ_RESOURCE_GROUP_NAME" --artifact-path target/demo-0.0.1-SNAPSHOT.jar
Seems you do not import spring actuator, thus metrics are not enabled, please refer to <https://aka.ms/ascdependencies> for more details
This command usually takes minutes to run. Add '--verbose' parameter if needed.
[1/3] Requesting for upload URL.
[2/3] Uploading package to blob.
[3/3] Updating deployment in app "todo-service" (this operation can take a while to complete)
Azure Spring Apps will use rolling upgrade to update your deployment, you have 1 instance, Azure Spring Apps will update the deployment in 1 round.
The deployment is in round 1, 1 old instance is deleted/deleting and 1 new instance is started/starting
Your application is successfully deployed.
Application logs:
BUILD_IN_EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=https://azure-spring-workshop-20240206.svc.azuremicroservices.io/eureka/eureka
BUILD_IN_SPRING_CLOUD_CONFIG_URI=https://azure-spring-workshop-20240206.svc.azuremicroservices.io/config
BUILD_IN_SPRING_CLOUD_CONFIG_FAILFAST=true
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-02-18 03:19:43.790Z INFO c.m.applicationinsights.agent - Application Insights Java Agent 3.4.18 started successfully (PID 1, JVM running for 5.806 s)
2024-02-18 03:19:43.794Z INFO c.m.applicationinsights.agent - Java version: 17.0.9, vendor: Microsoft, home: /usr/lib/jvm/msopenjdk-17
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-18T03:19:50.779Z INFO 1 --- [todo-service] [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 17.0.9 with PID 1 (/tmp/3b379036-ed94-437b-8af0-9e64def009e7.jar started by cnb in /home/cnb)
2024-02-18T03:19:50.793Z INFO 1 --- [todo-service] [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-02-18T03:19:50.904Z INFO 1 --- [todo-service] [ main] o.s.c.c.c.ConfigServerConfigDataLoader : Fetching config from server at : <https://azure-spring-workshop-20240206.svc.azuremicroservices.io/config/>
2024-02-18T03:19:50.904Z INFO 1 --- [todo-service] [ main] o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=todo-service, profiles=[default], label=null, version=b2dd9969510baead7d0c186f78e2809f26a4be8f, state=null
2024-02-18T03:19:53.212Z INFO 1 --- [todo-service] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-02-18T03:19:53.879Z INFO 1 --- [todo-service] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 601 ms. Found 1 JPA repository interface.
2024-02-18T03:19:54.578Z INFO 1 --- [todo-service] [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=96275517-ebea-3a33-94cb-6b989e87d1ff
2024-02-18T03:19:55.577Z INFO 1 --- [todo-service] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 1025 (http)
2024-02-18T03:19:56.285Z INFO 1 --- [todo-service] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-02-18T03:19:56.286Z INFO 1 --- [todo-service] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.18]
2024-02-18T03:19:56.579Z INFO 1 --- [todo-service] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-02-18T03:19:56.580Z INFO 1 --- [todo-service] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 5672 ms
2024-02-18T03:19:57.102Z INFO 1 --- [todo-service] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-02-18T03:19:57.290Z INFO 1 --- [todo-service] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.4.1.Final
2024-02-18T03:19:57.394Z INFO 1 --- [todo-service] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2024-02-18T03:19:58.025Z INFO 1 --- [todo-service] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-02-18T03:19:58.107Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-02-18T03:19:58.712Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@452888c2
2024-02-18T03:19:58.716Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2024-02-18T03:19:59.082Z WARN 1 --- [todo-service] [ main] org.hibernate.dialect.Dialect : HHH000511: The 5.7.0 version for [org.hibernate.dialect.MySQLDialect] is no longer supported, hence certain features may not work properly. The minimum supported version is 8.0.0. Check the community dialects project for available legacy versions.
2024-02-18T03:20:01.576Z INFO 1 --- [todo-service] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-02-18T03:20:01.962Z INFO 1 --- [todo-service] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-02-18T03:20:02.921Z WARN 1 --- [todo-service] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-02-18T03:20:04.100Z INFO 1 --- [todo-service] [ main] DiscoveryClientOptionalArgsConfiguration : Eureka HTTP Client uses RestTemplate.
2024-02-18T03:20:04.309Z WARN 1 --- [todo-service] [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compositeCompatibilityVerifier' defined in class path resource [org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.class]: Failed to instantiate [org.springframework.cloud.configuration.CompositeCompatibilityVerifier]: Factory method 'compositeCompatibilityVerifier' threw exception with message: Spring Cloud/ Spring Boot version compatibility checks have failed: [[VerificationResult@56dce7f7 description = 'Spring Boot [3.2.2] is not compatible with this Spring Cloud release train', action = 'Change Spring Boot version to one of the following versions [3.0.x, 3.1.x] .
You can find the latest Spring Boot versions here [<https://spring.io/projects/spring-boot#learn>].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [<https://spring.io/projects/spring-cloud#overview>] and check the [Release Trains] section.
If you want to disable this check, just set the property [spring.cloud.compatibility-verifier.enabled=false]']]
2024-02-18T03:20:04.311Z INFO 1 --- [todo-service] [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-02-18T03:20:04.457Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-02-18T03:20:04.479Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2024-02-18T03:20:04.482Z INFO 1 --- [todo-service] [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2024-02-18T03:20:04.503Z INFO 1 --- [todo-service] [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-02-18T03:20:04.582Z ERROR 1 --- [todo-service] [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Your project setup is incompatible with our requirements due to following reasons:
- Spring Boot [3.2.2] is not compatible with this Spring Cloud release train
Action:
Consider applying the following actions:
- Change Spring Boot version to one of the following versions [3.0.x, 3.1.x] .
You can find the latest Spring Boot versions here [<https://spring.io/projects/spring-boot#learn>].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [<https://spring.io/projects/spring-cloud#overview>] and check the [Release Trains] section.
If you want to disable this check, just set the property [spring.cloud.compatibility-verifier.enabled=false]
112404: Exit code 1: application error, please refer to <https://aka.ms/exitcode>
CLIのデプロイコマンドのログの最初に「Spring Actuatorがインポートされていないため、メトリクスの収集が無効化される」というログが出力される。
本番環境では有効化することが望ましく、実行時の様々な状態監視に有効なパッケージらしい。
おそらく後々知っておくべき機能だと思うので情報源だけは残し、今は一旦このまま進める。
Seems you do not import spring actuator, thus metrics are not enabled, please refer to https://aka.ms/ascdependencies for more details
Azure Spring Appsにjarファイルをアップロードすると、Azure Spring Apps内のblobストレージに一旦アップロードされるようだ。
[1/3] Requesting for upload URL.
[2/3] Uploading package to blob.
jarファイルのアップロードが完了すると、最後にjarファイルを展開してプログラムファイルとリソースファイルのデプロイが実行される。
この時、Azure Spring Appsでは、複数のレプリカが存在する場合、ダウンタイムを発生させないようkubernetesの「ローリングアップグレード」という処理が実行されるようだ。
[3/3] Updating deployment in app "todo-service" (this operation can take a while to complete)
Azure Spring Apps will use rolling upgrade to update your deployment, you have 1 instance, Azure Spring Apps will update the deployment in 1 round.
The deployment is in round 1, 1 old instance is deleted/deleting and 1 new instance is started/starting
Your application is successfully deployed.
ローリングアップグレードは、新しいデプロイを含んだレプリカを作成し、古いレプリカを順次停止/削除する処理が実行されるが、実行ログからもわかる通り、今回実行中のレプリカは1つのみなので、入れ替えのタイミングでダウンタイムが発生する可能性がある。
ローリングアップグレード自体はKubernetesの仕組みのようだ。以下のオンラインドキュメントに詳細なKubernetesの動作内容が説明されている。
この仕組みは後で詳細に調査する。
Azure Spring Appsでこれが実行されるのは、pom.xmlファイルにNetflix Eurekaというコンポーネントを含めている場合。Netflix Eurekaは、Spring Bootアプリケーションの各レプリカの正常性監視や今回のようなデプロイ時のゼロダウンタイムを実現するための仕組みを提供するコンポーネント。
別途詳細を調査する。
Azure Spring Appsではデプロイ時にゼロダウンタイムを実現するための仕組みとして「ブルーグリーンデプロイ」にも対応しており、ブルーグリーンデプロイの場合は単一レプリカであってもダウンタイムは発生しない。
これも別途詳細に調査する。
上記で言及した処理以外にも、Javaランタイムバージョン、Config Serverから取得した構成情報の適用、JPAやHibernateのセットアップ、Tomcatサーバーの実行と待ち受けポートの初期化など、様々な処理内容が実行ログから読み取れる。
ただ、デプロイログの最後でデプロイの失敗に関する情報が出力された。
--- 失敗の検出は以下の部分で発生し、以降の処理はサービスのシャットダウン処理とエラーに関する情報出力で処理が終わっている ---
2024-02-18T03:20:04.309Z WARN 1 --- [todo-service] [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compositeCompatibilityVerifier' defined in class path resource [org/springframework/cloud/configuration/CompatibilityVerifierAutoConfiguration.class]: Failed to instantiate [org.springframework.cloud.configuration.CompositeCompatibilityVerifier]: Factory method 'compositeCompatibilityVerifier' threw exception with message: Spring Cloud/ Spring Boot version compatibility checks have failed: [[VerificationResult@56dce7f7 description = 'Spring Boot [3.2.2] is not compatible with this Spring Cloud release train', action = 'Change Spring Boot version to one of the following versions [3.0.x, 3.1.x] .
You can find the latest Spring Boot versions here [<https://spring.io/projects/spring-boot#learn>].
If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [<https://spring.io/projects/spring-cloud#overview>] and check the [Release Trains] section.
If you want to disable this check, just set the property [spring.cloud.compatibility-verifier.enabled=false]']]
2024-02-18T03:20:04.311Z INFO 1 --- [todo-service] [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-02-18T03:20:04.457Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-02-18T03:20:04.479Z INFO 1 --- [todo-service] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2024-02-18T03:20:04.482Z INFO 1 --- [todo-service] [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2024-02-18T03:20:04.503Z INFO 1 --- [todo-service] [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-02-18T03:20:04.582Z ERROR 1 --- [todo-service] [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Your project setup is incompatible with our requirements due to following reasons:
- Spring Boot [3.2.2] is not compatible with this Spring Cloud release train
Action:
Consider applying the following actions:
- Change Spring Boot version to one of the following versions [3.0.x, 3.1.x] .
Azure Spring Appsでは、Spring Boot 3.2.2にはまだ対応していないようで、3.0.x系、もしくは3.1.x系に変更せよということだった。
pom.xmlを以下のように変更し、Spring Boot 3.1.x系の最新安定版である3.1.8に変更して再度デプロイ。
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.8</version>
...
</parent>
...
</project>
実行結果として、「provisioningState」が「Succeeded」となり、「status」が「Running」となっていることが確認できる。
% az spring app deploy --name todo-service --service "$AZ_SPRING_CLOUD_NAME" --resource-group "$AZ_RESOURCE_GROUP_NAME" --artifact-path target/demo-0.0.1-SNAPSHOT.jar
Seems you do not import spring actuator, thus metrics are not enabled, please refer to <https://aka.ms/ascdependencies> for more details
This command usually takes minutes to run. Add '--verbose' parameter if needed.
[1/3] Requesting for upload URL.
[2/3] Uploading package to blob.
[3/3] Updating deployment in app "todo-service" (this operation can take a while to complete)
Azure Spring Apps will use rolling upgrade to update your deployment, you have 1 instance, Azure Spring Apps will update the deployment in 1 round.
The deployment is in round 1, 1 old instance is deleted/deleting and 1 new instance is started/starting
Your application is successfully deployed.
Application logs:
BUILD_IN_EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=https://azure-spring-workshop-20240206.svc.azuremicroservices.io/eureka/eureka
BUILD_IN_SPRING_CLOUD_CONFIG_URI=https://azure-spring-workshop-20240206.svc.azuremicroservices.io/config
BUILD_IN_SPRING_CLOUD_CONFIG_FAILFAST=true
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-02-20 04:52:18.706Z INFO c.m.applicationinsights.agent - Application Insights Java Agent 3.4.18 started successfully (PID 1, JVM running for 6.514 s)
2024-02-20 04:52:18.710Z INFO c.m.applicationinsights.agent - Java version: 17.0.9, vendor: Microsoft, home: /usr/lib/jvm/msopenjdk-17
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.8)
2024-02-20T04:52:25.304Z INFO 1 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 17.0.9 with PID 1 (/tmp/7d13edd3-aa57-4997-966f-4f8c911847b7.jar started by cnb in /home/cnb)
2024-02-20T04:52:25.315Z INFO 1 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-02-20T04:52:25.419Z INFO 1 --- [ main] o.s.c.c.c.ConfigServerConfigDataLoader : Fetching config from server at : <https://azure-spring-workshop-20240206.svc.azuremicroservices.io/config/>
2024-02-20T04:52:25.419Z INFO 1 --- [ main] o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=todo-service, profiles=[default], label=null, version=b2dd9969510baead7d0c186f78e2809f26a4be8f, state=null
2024-02-20T04:52:27.530Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-02-20T04:52:28.114Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 575 ms. Found 1 JPA repository interface.
2024-02-20T04:52:28.800Z INFO 1 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=6019977f-6731-31e8-aa84-3225f3c89db9
2024-02-20T04:52:29.927Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 1025 (http)
2024-02-20T04:52:30.803Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-02-20T04:52:30.803Z INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.18]
2024-02-20T04:52:31.112Z INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-02-20T04:52:31.113Z INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 5689 ms
2024-02-20T04:52:31.711Z INFO 1 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-02-20T04:52:31.895Z INFO 1 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.2.20.Final
2024-02-20T04:52:31.899Z INFO 1 --- [ main] org.hibernate.cfg.Environment : HHH000406: Using bytecode reflection optimizer
2024-02-20T04:52:32.542Z INFO 1 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-02-20T04:52:32.629Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-02-20T04:52:33.416Z INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@52d68eb9
2024-02-20T04:52:33.421Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2024-02-20T04:52:36.221Z INFO 1 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-02-20T04:52:36.605Z INFO 1 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-02-20T04:52:37.821Z WARN 1 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-02-20T04:52:39.227Z INFO 1 --- [ main] DiscoveryClientOptionalArgsConfiguration : Eureka HTTP Client uses RestTemplate.
2024-02-20T04:52:39.493Z WARN 1 --- [ main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2024-02-20T04:52:39.611Z INFO 1 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING
2024-02-20T04:52:39.722Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1
2024-02-20T04:52:39.730Z INFO 1 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2024-02-20T04:52:39.821Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2024-02-20T04:52:39.822Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2024-02-20T04:52:39.822Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2024-02-20T04:52:39.822Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false
2024-02-20T04:52:39.822Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2024-02-20T04:52:39.823Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true
2024-02-20T04:52:39.823Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2024-02-20T04:52:40.101Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200
2024-02-20T04:52:40.107Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2024-02-20T04:52:40.115Z INFO 1 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
2024-02-20T04:52:40.121Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1708404760121 with initial instances count: 2
2024-02-20T04:52:40.124Z INFO 1 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application TODO-SERVICE with eureka with status UP
2024-02-20T04:52:40.125Z INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1708404760125, current=UP, previous=STARTING]
2024-02-20T04:52:40.130Z INFO 1 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_TODO-SERVICE/todo-service-default-12-7977dc9d8f-mnwlq:todo-service:1025: registering service...
2024-02-20T04:52:40.302Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 1025 (http) with context path ''
2024-02-20T04:52:40.303Z INFO 1 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 1025
2024-02-20T04:52:40.392Z INFO 1 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_TODO-SERVICE/todo-service-default-12-7977dc9d8f-mnwlq:todo-service:1025 - registration status: 204
2024-02-20T04:52:40.401Z INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 18.682 seconds (process running for 28.209)
{
"id": "/subscriptions/51cf59b2-3184-4d6c-abb6-6ab69af3415c/resourceGroups/azure-spring-workshop/providers/Microsoft.AppPlatform/Spring/azure-spring-workshop-20240206/apps/todo-service/deployments/default",
"name": "default",
"properties": {
"active": true,
"deploymentSettings": {
"addonConfigs": null,
"apms": null,
"containerProbeSettings": null,
"environmentVariables": null,
"livenessProbe": {
"disableProbe": false,
"failureThreshold": 3,
"initialDelaySeconds": 300,
"periodSeconds": 10,
"probeAction": {
"type": "TCPSocketAction"
},
"successThreshold": 1,
"timeoutSeconds": 3
},
"readinessProbe": {
"disableProbe": false,
"failureThreshold": 3,
"initialDelaySeconds": 0,
"periodSeconds": 5,
"probeAction": {
"type": "TCPSocketAction"
},
"successThreshold": 1,
"timeoutSeconds": 3
},
"resourceRequests": {
"cpu": "1",
"memory": "1Gi"
},
"scale": null,
"startupProbe": null,
"terminationGracePeriodSeconds": 90
},
"instances": [
{
"discoveryStatus": "UP",
"name": "todo-service-default-12-7977dc9d8f-mnwlq",
"reason": null,
"startTime": "2024-02-20T04:51:56Z",
"status": "Running",
"zone": null
}
],
"provisioningState": "Succeeded",
"source": {
"jvmOptions": null,
"relativePath": "resources/f18ce5a8a2f955670e093983542115ddadd14c88e75a2cebdd89286a0bff806d-2024022004-cf3ba3bc-5c1e-4522-b48f-0ea6a2e6276a",
"runtimeVersion": "Java_17",
"type": "Jar",
"version": null
},
"status": "Running"
},
"resourceGroup": "azure-spring-workshop",
"sku": {
"capacity": 1,
"name": "S0",
"tier": "Standard"
},
"systemData": {
"createdAt": "2024-02-08T15:00:07.687094+00:00",
"createdBy": "IOCC_sato@gbssandbox.onmicrosoft.com",
"createdByType": "User",
"lastModifiedAt": "2024-02-20T04:51:47.723670+00:00",
"lastModifiedBy": "IOCC_sato@gbssandbox.onmicrosoft.com",
"lastModifiedByType": "User"
},
"type": "Microsoft.AppPlatform/Spring/apps/deployments"
}
Azure Spring Appsのアプリケーションを選択して「テストエンドポイント」のURLをコピー、curlでアクセスしてみる。
% curl <https://primary:hhXQBIoGngnjNclXRtlCVCS7gRLQKrM1kH8wUUP4Mjz6ziG0h3G8lDOaWNRRdMWx@azure-spring-workshop-20240206.test.azuremicroservices.io/todo-service/default/>
[{},{},{}]
TodoController.javaのinit()メソッドで初期値をデータベースに保存しているデータが表示されないためデバッグして原因を追求、一旦デプロイまで完了したので、デバッグ方法については別の記事で記載する。
ようやくまとめ。。
今回のLearnでは、多くのことを学習した。
これらの処理を実際に行う時に発生したエラーの対処方法も確認した。
まだまだ詳細まで理解が及んでいないが、今後の課題として学習リストに入れておいた。
また、Webアプリの初期処理としてデータベースの初期データの登録処理を書いたのだがうまく動作していないので、今後デバッグ方法を確認しながらエラーを取り除いていく方法も学習する。
]]>この事は、世界中で常に誰によるアカウントの乗っ取り行為が行われているという事実を改めて確認できたという事と、今使っているサービスがしっかりガードしてくれているという強い信頼感を持った出来事だった。
こちらも是非合わせて読んでいただきたい。
Authenticatorへの通知が少し気になって、アカウント情報のアクティビティ履歴を確認してみた結果が以下のスクリーンショット。1画面では伝わらないかもしれないが、画面をスクロールするとドイツ、トルコ、クロアチア、英国、中国、ロシアなど、様々な場所から何ヶ月にもわたって1日に何回もサインインアクティビティが検出されている。プログラムを使って自動的にパスワードスプレー攻撃を実施しているものかと思われる。
かつ恐ろしいのは、ドイツからの最近のアクセスにて「正常にサインインしました」となっている点。おそらく、この時のアクティビティが、冒頭で記載したAuthenticatorへのログイン認証要求になったのだと思う。
幸いにも2要素認証が効いて完全な乗っ取りを防止できていた事が判明した瞬間だったが、パスワードが当てられてしまった事はなんとも恐ろしい。ちなみにその時のパスワードは10桁で、数字、大小英文字、記号を全て組み合わせたもの。この組み合わせでパスワードが設定できるサービスはいっぱいあるだろう。
今回のアクティビティログから、攻撃はプログラムを使って自動化したものと思われるので、組み合わせの文字数を増やしたりパスワードを変更したりしても、突破されるのは時間の問題かと思われる。
いまどきパスワード認証だけで利用できるインターネットサービスは使わない方が良いと思う。
私は複数のサービスでアカウントを持っており、AuthenticatorアプリやSMS認証やメール認証に対応しているサービスの場合は必ず2要素認証を有効にしている。
AuthenticatorアプリやSMS認証は手元にデバイスがあるので少し安心感が高い。
メール認証はメールにアクセスされるとどこからでも突破される可能性があるため安心感は低いが、特定のデバイスへの依存がなくなるので利便性はあるし、パスワード認証だけよりはよっぽどマシだ。
2要素認証という話の前に、そもそも「1要素目としてパスワード認証」という事をそろそろやめにしたい。昔はそれでもよかったのだろうが、今となってはデメリットしかない。
ちなみに、Googleのサービスとして、自分のアカウントがどれだけダークウェブに漏れているかを自動検出してくれるサービスがあるので、もしGoogleアカウントを持っている人は試してみるとよい。私のGoogleアカウントは様々なサービスのユーザーIDや連絡先のメールアドレスとして使っているが、びっくりするほどユーザー情報が漏れている事がわかっている。
パスワードまで漏れていたのは1つだけ、かつそのパスワードは今は使っていない10年以上も前の古いモノだったが、いろんなダークウェブに晒されていた。TwitterかEvernote、どちらかのアカウントで使っていたモノだ。
たとえば、Microsoftアカウントであれば、パスワードレスという認証方法が選択できる。ログインしようとした時にパスワードは聞かれず、いきなりAuthenticatorアプリやSMSで登録元のデバイスもしくは自分が所有しているデバイスで認証するという方法。
この方法であれば、アカウント名やユーザーIDが知られてもデバイスが手元にない場合はログインできない。
ただ、デバイスが紛失・故障・盗難された場合など、デバイスが利用できない状況になるとどうしようもなくなるので別のデバイスでアプリが利用できるようになる「回復コード」というものが発行されるので自分で管理しておかなければならない。これはこれでセキュリティリスクとなるが現状やむなしだろうな。ログインする時に使用するパスワードが各サービスのデータベースに保存されて自分の管理外のところで漏れるよりはよっぽどマシなきがする。
ちなみに、Microsoft の Authenticatorは、生体認証に対応しており、アプリにアクセスする時は必ず指紋もしくは顔認証が必要となるため、最悪デバイス自体が盗まれたり、SIMが乗っ取られ最悪バックアップからアプリが別デバイスに復元されたとしても自分がいないとアクセスできない。セキュリティレベルは相当高いと思う。
今はスマホアプリとして、Google AuthenticatorやMicrosoft Authenticatorがあるが、他の会社も自前のAuthenticatorを出してきたら、それはそれで面倒臭い。また、スマホは2〜3年に一回は買い替えると思うので、もう少し息の長い専用デバイスが登場しても良いのではないだろうか。
誰か格安のSIM付き専用デバイス作ってくれないかな。
それか、モバイル通信が必要な通知機能などはスマホアプリで提供し、キーの機能だけを提供するSIMなしデバイスでもいい。QRコードを表示できるようにして、いちいち手入力しなくても済むやつ。Wifi機能があればスマホもいらないというオプション付き。
ネットでサービスを利用する際には必ずアカウントが必要になる。有料サービスの場合、そのアカウントには個人情報だけでなく決済情報が常に紐づいている。情報がサービス側で暗号化されて保存されたりしたとしても、アカウント自体が乗っ取られてしまったらなんの意味もない。
自分のアカウントを守る方法や、自分のアカウントが今どのような状態なのかを確認する手段というのは知っておいた方がいい。ネットを安心して利用するために誰もが知っておくべき必須の基礎知識だろう。アカウントを管理しているサービス提供側としてもそのような情報は常にユーザーが確認できるような機能を提供した方が良いと思う。
]]>環境変数を用いて、今後Slotの動作確認やスケールアウト・インの動作確認で利用できると思うのでそのための前準備とする。
環境変数のKeyとValueの組み合わせを1つの文字列とし、それをさらにHTMLのbrタグをセパレーターとして連結して出力するプログラム。
package com.example.azureappservice;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class AzureAppServiceApplication {
@GetMapping("/")
String printEnvs() {
List<String> workList = new ArrayList<String>();
Map<String, String> envMap = System.getenv();
for (Map.Entry<String, String> entry : envMap.entrySet()) {
workList.add(entry.getKey() + "[" + entry.getValue() + "]");
}
return String.join("<br>", workList);
}
public static void main(String[] args) {
SpringApplication.run(AzureAppServiceApplication.class, args);
}
}
上記のプログラムをmavenプラグインでコンパイル&デプロイする。
% mvn clean package com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:deploy
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:AzureAppService >---------------------
[INFO] Building AzureAppService 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ AzureAppService ---
[INFO] Deleting /Users/sato/proj/learn/java/SpringTips/AzureAppService/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ AzureAppService ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ AzureAppService ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 1 source file with javac [debug release 17] to target/classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ AzureAppService ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/AzureAppService/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ AzureAppService ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 1 source file with javac [debug release 17] to target/test-classes
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ AzureAppService ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.azureappservice.AzureAppServiceApplicationTests
00:08:52.112 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.example.azureappservice.AzureAppServiceApplicationTests]: AzureAppServiceApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
00:08:52.220 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.example.azureappservice.AzureAppServiceApplication for test class com.example.azureappservice.AzureAppServiceApplicationTests
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-05T00:08:52.602+09:00 INFO 11504 --- [ main] c.e.a.AzureAppServiceApplicationTests : Starting AzureAppServiceApplicationTests using Java 21.0.1 with PID 11504 (started by sato in /Users/sato/proj/learn/java/SpringTips/AzureAppService)
2024-02-05T00:08:52.604+09:00 INFO 11504 --- [ main] c.e.a.AzureAppServiceApplicationTests : No active profile set, falling back to 1 default profile: "default"
2024-02-05T00:08:53.736+09:00 INFO 11504 --- [ main] c.e.a.AzureAppServiceApplicationTests : Started AzureAppServiceApplicationTests in 1.388 seconds (process running for 2.318)
WARNING: A Java agent has been loaded dynamically (/Users/sato/.m2/repository/net/bytebuddy/byte-buddy-agent/1.14.11/byte-buddy-agent-1.14.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.001 s -- in com.example.azureappservice.AzureAppServiceApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ AzureAppService ---
[INFO] Building jar: /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (repackage) @ AzureAppService ---
[INFO] Replacing main artifact /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar.original
[INFO]
[INFO] --- azure-webapp:2.9.0:deploy (default-cli) @ AzureAppService ---
Downloading from azure-sdk-for-java: <https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1/net/minidev/json-smart/maven-metadata.xml>
Downloading from shibboleth-repo: <https://build.shibboleth.net/nexus/content/repositories/releases/net/minidev/json-smart/maven-metadata.xml>
Downloading from ossrh: <https://oss.sonatype.org/content/repositories/snapshots/net/minidev/json-smart/maven-metadata.xml>
[INFO] Auth type: AZURE_CLI
[INFO] Default subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Username: IOCC_sato@gbssandbox.onmicrosoft.com
[INFO] Subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Trying to deploy external resources to azure-spring-workshop...
[INFO] Successfully deployed the resources to azure-spring-workshop
[INFO] Trying to deploy artifact to azure-spring-workshop...
[INFO] Deploying (/Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar)[jar] ...
[INFO] Using service version null
[INFO] Using service version null
[INFO] Deployment Status: BuildSuccessful; Successful Instance Count: 0; In-progress Instance Count: 0; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeSuccessful; Successful Instance Count: 1; In-progress Instance Count: 0; Failed Instance Count: 0
[INFO] Successfully deployed the artifact to <https://azure-spring-workshop.azurewebsites.net>
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:05 min
[INFO] Finished at: 2024-02-05T00:10:54+09:00
[INFO] ------------------------------------------------------------------------
curlでリクエストを投げて、HTTPレスポンスボディーでAzure Web Appインスタンスの環境変数を確認する。1行が長く流石に分かりづらいのでbrタグで改行したものを掲載する。またセキュリティの観点から公開しない方が良い情報はマスクする。
% curl --header "Content-Type: application/json" --request GET <https://azure-spring-workshop.azurewebsites.net/>
PATH[/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin]
WEBSITE_OWNER_NAME[xxx+azure-spring-workshop-JapanEastwebspace-Linux]
WEBSITE_RESOURCE_GROUP[azure-spring-workshop]
JAVA_OPTS[-Djava.util.logging.config.file=/usr/local/appservice/logging.properties -Dfile.encoding=UTF-8 -Dserver.port=80 -XX:ErrorFile=/home/LogFiles/java_error_azure-spring-workshop_10-30-0-95_%p.log -XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/LogFiles/java_memdump_azure-spring-workshop_10-30-0-95.log -Duser.dir=/home/site/wwwroot]
JRE_HOME[/usr/lib/jdk]
WEBSITE_SKU[LinuxFree]
SSH_PORT[2222]
PWD[/]
WEBSITE_SITE_NAME[azure-spring-workshop]
SERVER_PORT[80]
LANGUAGE[en_US:en]
APP_JAR_PATH[/home/site/wwwroot/app.jar:/usr/local/appservice/lib/azure.appservice.jar]
PORT[80]
WEBSITE_AUTH_ENABLED[False]
SERVER_MAXHTTPHEADERSIZE[16384]
WEBSITE_INSTANCE_ID[xxx]
LOGGING_FILE_NAME[/home/LogFiles/Application/spring.10-30-0-95.log]
NUM_CORES[2]
REGION_NAME[japaneast]
LC_ALL[en_US.UTF-8]
GLOBAL_PID_MAIN[0]
WEBJOB_HOME[/home]
WEBSITE_JAVA_KEYSTORE_PASSWORD[changeit]
APPSETTING_WEBSITE_AZMON_ENABLED[True]
SHLVL[0]
WEBSITE_HOME_STAMPNAME[waws-prod-ty1-085]
DIAGNOSTIC_LOGS_MOUNT_PATH[/var/log/diagnosticLogs]
LOGICAPPS_ACCESS_CONTROL_CONFIGURATION[null]
WEBSITE_AUTH_ENCRYPTION_KEY[xxx]
WEBSITE_OS[linux]
WEBSITE_JAVA_MAX_HEAP_MB[716]
WEBSITE_AUTH_SIGNING_KEY[xxx]
JAVA_HOME[/usr/lib/jdk]
LANG[en_US.UTF-8]
JAVA_TOOL_OPTIONS[-Xmx716M -Djava.net.preferIPv4Stack=true ]
WEBSITE_STACK[JAVA]
APPSVC_RUN_ZIP[FALSE]
_[/usr/bin/java]
APPSETTING_WEBSITE_AUTH_ENABLED[False]
APPSETTING_ScmType[None]
GIT_COMMIT[]
WEBSITE_ISOLATION[lxc]
WEBSITE_PHYSICAL_MEMORY_MB[1024]
COMPUTERNAME[10-30-0-95]
GLOBAL_EXIT_AFTER_CUSTOM_STARTUP[1]
WEBJOB_ENV[true]
WEBSITE_USE_DIAGNOSTIC_SERVER[false]
DOCKER_SERVER_VERSION[20.10.25]
ScmType[None]
WEBSITE_ROLE_INSTANCE_ID[0]
APPSETTING_WEBSITE_SITE_NAME[azure-spring-workshop]
LOGGING_FILE[/home/LogFiles/Application/spring.10-30-0-95.log]
HOSTNAME[8fcf022a7569]
PLATFORM_VERSION[101.0.7.490]
WEBSITE_HOSTNAME[azure-spring-workshop.azurewebsites.net]
HOME[/root]
今回の実施内容は少し手応えがなく、調査する事もほとんどなかったが、今後、実行中のプログラムがどのインスタンスで実行されている結果が表示・出力されているのかを確認するための手段として、ホストの環境変数を使った識別ができれば良いと思う。
]]>作成する手順は以下の通り
AzureリソースはCLIを使って作成する。デプロイするアプリケーションはいつもの簡単な「Hello, World」RESTサービスを用いる。
今回はプロジェクトファイルをSpring Initiaizrを使用したテンプレートで作成する。まずはローカルで動作確認してから、Azureリソースにデプロイして動作確認する。
今までゼロからpom.xmlやプロジェクトフォルダー構成を作成していたが、今回はWEBで利用できるSpring Initializrを使ってプロジェクトテンプレートを作成してから簡単なサンプルアプリケーションを作成する。
「Project Metadata」に分かりづらいパラメータがあるため以下に補足を記載する。
上記を入力し、「GENERATE」をクリックすると「Artifact名.zip」というファイル名のプロジェクトテンプレートがダウンロードできる。
プロジェクトテンプレートの中身は以下の通り。
% tar tvfz AzureAppService.zip
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/
-rw-r--r-- 0 0 0 858 2 2 15:39 AzureAppService/HELP.md
-rw-r--r-- 0 0 0 395 2 2 15:39 AzureAppService/.gitignore
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/java/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/java/com/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/java/com/example/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/java/com/example/azureappservice/
-rw-r--r-- 0 0 0 338 2 2 15:39 AzureAppService/src/main/java/com/example/azureappservice/AzureAppServiceApplication.java
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/resources/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/resources/templates/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/main/resources/static/
-rw-r--r-- 0 0 0 1 2 2 15:39 AzureAppService/src/main/resources/application.properties
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/test/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/test/java/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/test/java/com/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/test/java/com/example/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/src/test/java/com/example/azureappservice/
-rw-r--r-- 0 0 0 228 2 2 15:39 AzureAppService/src/test/java/com/example/azureappservice/AzureAppServiceApplicationTests.java
-rw-r--r-- 0 0 0 1248 2 2 15:39 AzureAppService/pom.xml
-rwxr-xr-x 0 0 0 11290 2 2 15:39 AzureAppService/mvnw
-rw-r--r-- 0 0 0 7592 2 2 15:39 AzureAppService/mvnw.cmd
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/.mvn/
drwxr-xr-x 0 0 0 0 2 2 15:39 AzureAppService/.mvn/wrapper/
-rw-r--r-- 0 0 0 233 2 2 15:39 AzureAppService/.mvn/wrapper/maven-wrapper.properties
-rw-r--r-- 0 0 0 62547 2 2 15:39 AzureAppService/.mvn/wrapper/maven-wrapper.jar
自動生成されたpom.xmlは以下の通り。前回までにほとんど理解したので説明なし。
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!-- Spring Boot プロジェクトテンプレートの継承 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>AzureAppService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>AzureAppService</name>
<description>Demo project for Spring Boot</description>
<!-- 想定java version -->
<properties>
<java.version>21</java.version>
</properties>
<!-- web関連とtest関連のライブラリを利用 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- mavenプロジェクトのビルドプラグイン -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
このpom.xmlで新しいのはjava versionを指定するプロパティと、test関連ライブラリ「spring-boot-starter-test」への依存関係定義。今回のスコープはAzure App Serviceへのデプロイなので、test関連は無視。
いつものHello World REST。こちらも前回までにほとんど理解したので説明なし。
package com.example.azureappservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class AzureAppServiceApplication {
@GetMapping("/")
String home() {
return "Hello, World.";
}
public static void main(String[] args) {
SpringApplication.run(AzureAppServiceApplication.class, args);
}
}
いつも通り「mvn spring-boot:run」でローカル実行。
% mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:AzureAppService >---------------------
[INFO] Building AzureAppService 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.2.2:run (default-cli) > test-compile @ AzureAppService >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ AzureAppService ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ AzureAppService ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ AzureAppService ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/AzureAppService/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ AzureAppService ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot:3.2.2:run (default-cli) < test-compile @ AzureAppService <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.2.2:run (default-cli) @ AzureAppService ---
[INFO] Attaching agents: []
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-02T15:49:16.165+09:00 INFO 2703 --- [ main] c.e.a.AzureAppServiceApplication : Starting AzureAppServiceApplication using Java 21.0.1 with PID 2703 (/Users/sato/proj/learn/java/SpringTips/AzureAppService/target/classes started by sato in /Users/sato/proj/learn/java/SpringTips/AzureAppService)
2024-02-02T15:49:16.166+09:00 INFO 2703 --- [ main] c.e.a.AzureAppServiceApplication : No active profile set, falling back to 1 default profile: "default"
2024-02-02T15:49:16.785+09:00 INFO 2703 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-02-02T15:49:16.797+09:00 INFO 2703 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-02-02T15:49:16.798+09:00 INFO 2703 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.18]
2024-02-02T15:49:16.836+09:00 INFO 2703 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-02-02T15:49:16.837+09:00 INFO 2703 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 635 ms
2024-02-02T15:49:17.077+09:00 INFO 2703 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-02-02T15:49:17.083+09:00 INFO 2703 --- [ main] c.e.a.AzureAppServiceApplication : Started AzureAppServiceApplication in 1.178 seconds (process running for 1.414)
curlで確認。
% curl --header "Content-Type: application/json" --request GET http://localhost:8080
Hello, World.
CLIを用いたリソース作成時に毎回同じパラメータを指定するよう求められるため、先に環境変数にパラメータ値を設定しておき、CLIから参照できるようにする。
CLIを用いたリソース作成時に、毎回指定する必要がある情報は、リソースを作成するコンテナとなるリソースグループと、リソースを配置するリージョン。
この時、作成するリソースのリージョン名を「az account list-locations」で確認しておく。多くの情報が表示される場合、「—output table」を指定すると出力結果がテーブル形式になるため読みやすい。
今回は東日本リージョンにリソースを作成したいので「japaneast」を指定する。
% az account list-locations --output table
DisplayName Name RegionalDisplayName
------------------------ ------------------- -------------------------------------
East US eastus (US) East US
East US 2 eastus2 (US) East US 2
South Central US southcentralus (US) South Central US
West US 2 westus2 (US) West US 2
West US 3 westus3 (US) West US 3
Australia East australiaeast (Asia Pacific) Australia East
Southeast Asia southeastasia (Asia Pacific) Southeast Asia
North Europe northeurope (Europe) North Europe
Sweden Central swedencentral (Europe) Sweden Central
UK South uksouth (Europe) UK South
West Europe westeurope (Europe) West Europe
Central US centralus (US) Central US
South Africa North southafricanorth (Africa) South Africa North
Central India centralindia (Asia Pacific) Central India
East Asia eastasia (Asia Pacific) East Asia
Japan East japaneast (Asia Pacific) Japan East
Korea Central koreacentral (Asia Pacific) Korea Central
Canada Central canadacentral (Canada) Canada Central
France Central francecentral (Europe) France Central
Germany West Central germanywestcentral (Europe) Germany West Central
Italy North italynorth (Europe) Italy North
Norway East norwayeast (Europe) Norway East
Poland Central polandcentral (Europe) Poland Central
Switzerland North switzerlandnorth (Europe) Switzerland North
UAE North uaenorth (Middle East) UAE North
Brazil South brazilsouth (South America) Brazil South
Central US EUAP centraluseuap (US) Central US EUAP
Israel Central israelcentral (Middle East) Israel Central
Qatar Central qatarcentral (Middle East) Qatar Central
Central US (Stage) centralusstage (US) Central US (Stage)
East US (Stage) eastusstage (US) East US (Stage)
East US 2 (Stage) eastus2stage (US) East US 2 (Stage)
North Central US (Stage) northcentralusstage (US) North Central US (Stage)
South Central US (Stage) southcentralusstage (US) South Central US (Stage)
West US (Stage) westusstage (US) West US (Stage)
West US 2 (Stage) westus2stage (US) West US 2 (Stage)
Asia asia Asia
Asia Pacific asiapacific Asia Pacific
Australia australia Australia
Brazil brazil Brazil
Canada canada Canada
Europe europe Europe
France france France
Germany germany Germany
Global global Global
India india India
Japan japan Japan
Korea korea Korea
Norway norway Norway
Singapore singapore Singapore
South Africa southafrica South Africa
Sweden sweden Sweden
Switzerland switzerland Switzerland
United Arab Emirates uae United Arab Emirates
United Kingdom uk United Kingdom
United States unitedstates United States
United States EUAP unitedstateseuap United States EUAP
East Asia (Stage) eastasiastage (Asia Pacific) East Asia (Stage)
Southeast Asia (Stage) southeastasiastage (Asia Pacific) Southeast Asia (Stage)
Brazil US brazilus (South America) Brazil US
East US STG eastusstg (US) East US STG
North Central US northcentralus (US) North Central US
West US westus (US) West US
Japan West japanwest (Asia Pacific) Japan West
Jio India West jioindiawest (Asia Pacific) Jio India West
East US 2 EUAP eastus2euap (US) East US 2 EUAP
West Central US westcentralus (US) West Central US
South Africa West southafricawest (Africa) South Africa West
Australia Central australiacentral (Asia Pacific) Australia Central
Australia Central 2 australiacentral2 (Asia Pacific) Australia Central 2
Australia Southeast australiasoutheast (Asia Pacific) Australia Southeast
Jio India Central jioindiacentral (Asia Pacific) Jio India Central
Korea South koreasouth (Asia Pacific) Korea South
South India southindia (Asia Pacific) South India
West India westindia (Asia Pacific) West India
Canada East canadaeast (Canada) Canada East
France South francesouth (Europe) France South
Germany North germanynorth (Europe) Germany North
Norway West norwaywest (Europe) Norway West
Switzerland West switzerlandwest (Europe) Switzerland West
UK West ukwest (Europe) UK West
UAE Central uaecentral (Middle East) UAE Central
Brazil Southeast brazilsoutheast (South America) Brazil Southeast
リソースグループ名は適当な名前で良い。
以下2つの情報は環境変数に設定しておき、いつでも参照できるようにしておく。
% AZ_RESOURCE_GROUP=azure-spring-workshop
% AZ_LOCATION=japaneast
Bashでは以前にログインした時のセッション情報が残っている場合がある。複数テナントにアカウント持っている人(私もその一人)は、どこにリソースを作成使用しているのか確認するために、現在ログイン中のアカウント情報を確認しておいた方がよい。
ログイン中のAzureアカウント情報は「az account show」で確認できる。
% az account show
{
"environmentName": "AzureCloud",
"homeTenantId": "xxx",
"id": "xxx",
"isDefault": true,
"managedByTenants": [],
"name": "PayAsYouGo",
"state": "Enabled",
"tenantId": "xxx",
"user": {
"name": "xxx@xxx.onmicrosoft.com",
"type": "user"
}
}
まずは「az group create」コマンドでリソースコンテナとなるリソースグループを作成する。
% az group create --name $AZ_RESOURCE_GROUP --location $AZ_LOCATION | jq
{
"id": "/subscriptions/xxx/resourceGroups/azure-spring-workshop",
"location": "japaneast",
"managedBy": null,
"name": "azure-spring-workshop",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
}
作成したリソースグループの確認には「az group list」コマンドを用いる。
% az group list --output table
Name Location Status
--------------------------------- ------------- ---------
rg-openai-chat-playground japaneast Succeeded
DefaultResourceGroup-EJP japaneast Succeeded
NetworkWatcherRG japaneast Succeeded
azure-spring-workshop japaneast Succeeded
cloud-shell-storage-southeastasia southeastasia Succeeded
DefaultResourceGroup-EUS eastus Succeeded
mavenのazure app serviceプラグインを用いて、以下の2つを実行するためにpom.xmlに設定を追加する。
Azure App Serviceのmavenプラグインは以下にて説明がある。
Azure App Serviceプラグインのconfigゴールを指定してmavenを実行すると、大量にプラグインモジュールがダウンロードされた後に、pom.xmlに設定を追加する処理が実行される。
% mvn com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:config
[INFO] Scanning for projects...
Downloading from central: <https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-webapp-maven-plugin/2.9.0/azure-webapp-maven-plugin-2.9.0.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-webapp-maven-plugin/2.9.0/azure-webapp-maven-plugin-2.9.0.pom> (13 kB at 24 kB/s)
... <プラグインモジュールが大量にダウンロードされる> ...
Create new run configuration (Y/N) [Y]:
Define value for OS [Linux]:
1: Windows
* 2: Linux
3: Docker
Enter your choice: Linux
Invalid value. Enter a value between 1 and 3.
Define value for OS [Linux]:
1: Windows
* 2: Linux
3: Docker
Enter your choice: 2
Define value for javaVersion [Java 17]:
1: Java 8
2: Java 11
* 3: Java 17
Enter your choice: 3
Define value for pricingTier [P1v2]:
1: B1
2: B2
3: B3
4: D1
5: EP1
6: EP2
7: EP3
8: F1
* 9: P1v2
10: P1v3
11: P2v2
12: P2v3
13: P3v2
14: P3v3
15: S1
16: S2
17: S3
18: Y1
Enter your choice: 8
Please confirm webapp properties
AppName : AzureAppService-1707007404722
ResourceGroup : AzureAppService-1707007404722-rg
Region : centralus
PricingTier : F1
OS : Linux
Java Version: Java 17
Web server stack: Java SE
Deploy to slot : false
Confirm (Y/N) [Y]:
[INFO] Saving configuration to pom.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16:52 min
[INFO] Finished at: 2024-02-04T09:48:14+09:00
[INFO] ------------------------------------------------------------------------
今回はLinuxベースのFreeプランを選択。
上記を実行した後、pom.xmlにはauzre app serviceのプラグインが登録される。
いくつかの設定値がデフォルトの値で登録されていたので、何か見逃したのかと思ったが、どうやらそういう仕様のようだ。
プロンプトで聞いて欲しかったのに聞かれずにデフォルト値が設定されたのは以下の3つ。
<build>
<plugins>
<!-- 元々登録してあったspring bootのプラグイン -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 新たに追加されたAzure App Serviceのプラグイン -->
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>2.9.0</version>
<configuration>
<schemaVersion>v2</schemaVersion>
<resourceGroup>AzureAppService-1707008778169-rg</resourceGroup>
<appName>AzureAppService-1707008778169</appName>
<pricingTier>F1</pricingTier>
<region>centralus</region>
<runtime>
<os>Linux</os>
<javaVersion>Java 17</javaVersion>
<webContainer>Java SE</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
</plugins>
</build>
上記デフォルト値は、再度configゴールを実行するとプロンプトで聞かれる内容が変わり変更できる。なんだこの変な仕様。。
% mvn com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:config
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:AzureAppService >---------------------
[INFO] Building AzureAppService 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- azure-webapp:2.9.0:config (default-cli) @ AzureAppService ---
Please choose which part to config [Application]:
* 1: Application
2: Runtime
3: DeploymentSlot
Enter your choice:
Define value for appName [AzureAppService-1707008778169]: azure-spring-workshop
Define value for resourceGroup [AzureAppService-1707008778169-rg]: azure-spring-workshop
Define value for region [centralus]: japaneast
Define value for pricingTier [F1]:
1: B1
2: B2
3: B3
4: D1
5: EP1
6: EP2
7: EP3
* 8: F1
9: P1v2
10: P1v3
11: P2v2
12: P2v3
13: P3v2
14: P3v3
15: S1
16: S2
17: S3
18: Y1
Enter your choice: 8
Please confirm webapp properties
AppName : azure-spring-workshop
ResourceGroup : azure-spring-workshop
Region : japaneast
PricingTier : F1
OS : Linux
Java Version: Java 17
Web server stack: Java SE
Deploy to slot : false
Confirm (Y/N) [Y]:
[INFO] Saving configuration to pom.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 29.118 s
[INFO] Finished at: 2024-02-04T10:16:49+09:00
[INFO] ------------------------------------------------------------------------
再実行により変更されたpom.xml。
<build>
<plugins>
<!-- 元々登録してあったspring bootのプラグイン -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 新たに追加されたAzure App Serviceのプラグイン -->
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>2.9.0</version>
<configuration>
<schemaVersion>v2</schemaVersion>
<!-- 指定したリソースグループ -->
<resourceGroup>azure-spring-workshop</resourceGroup>
<!-- 指定したAppService名 -->
<appName>azure-spring-workshop</appName>
<pricingTier>F1</pricingTier>
<!-- 指定したリージョン -->
<region>japaneast</region>
<runtime>
<os>Linux</os>
<javaVersion>Java 17</javaVersion>
<webContainer>Java SE</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
</plugins>
</build>
以下のコマンドを実行してデプロイを実行。
% mvn package com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:deploy
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:AzureAppService >---------------------
[INFO] Building AzureAppService 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ AzureAppService ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ AzureAppService ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ AzureAppService ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/AzureAppService/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ AzureAppService ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ AzureAppService ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.azureappservice.AzureAppServiceApplicationTests
10:48:54.942 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.example.azureappservice.AzureAppServiceApplicationTests]: AzureAppServiceApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
10:48:55.055 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.example.azureappservice.AzureAppServiceApplication for test class com.example.azureappservice.AzureAppServiceApplicationTests
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-04T10:48:55.421+09:00 INFO 4618 --- [ main] c.e.a.AzureAppServiceApplicationTests : Starting AzureAppServiceApplicationTests using Java 21.0.1 with PID 4618 (started by sato in /Users/sato/proj/learn/java/SpringTips/AzureAppService)
2024-02-04T10:48:55.422+09:00 INFO 4618 --- [ main] c.e.a.AzureAppServiceApplicationTests : No active profile set, falling back to 1 default profile: "default"
2024-02-04T10:48:56.449+09:00 INFO 4618 --- [ main] c.e.a.AzureAppServiceApplicationTests : Started AzureAppServiceApplicationTests in 1.247 seconds (process running for 2.279)
WARNING: A Java agent has been loaded dynamically (/Users/sato/.m2/repository/net/bytebuddy/byte-buddy-agent/1.14.11/byte-buddy-agent-1.14.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.515 s -- in com.example.azureappservice.AzureAppServiceApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ AzureAppService ---
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (repackage) @ AzureAppService ---
[INFO] Replacing main artifact /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar.original
[INFO]
[INFO] --- azure-webapp:2.9.0:deploy (default-cli) @ AzureAppService ---
[INFO] Auth type: AZURE_CLI
[INFO] Default subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Username: IOCC_sato@gbssandbox.onmicrosoft.com
[INFO] Subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Start creating App Service plan (asp-azure-spring-workshop)...
[INFO] Using service version null
[INFO] Using service version null
[INFO] App Service plan (asp-azure-spring-workshop) is successfully created
[INFO] Start creating Web App(azure-spring-workshop)...
[INFO] Web App(azure-spring-workshop) is successfully created
[INFO] Trying to deploy external resources to azure-spring-workshop...
[INFO] Successfully deployed the resources to azure-spring-workshop
[INFO] Trying to deploy artifact to azure-spring-workshop...
[INFO] Deploying (/Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar)[jar] ...
[INFO] Deployment Status: BuildSuccessful; Successful Instance Count: 0; In-progress Instance Count: 0; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[WARNING] Resource deployed, but the deployment is still in process in Azure
[INFO] Successfully deployed the artifact to <https://azure-spring-workshop.azurewebsites.net>
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 04:45 min
[INFO] Finished at: 2024-02-04T10:53:38+09:00
[INFO] ------------------------------------------------------------------------
Azure側で処理中だがデプロイは完了したようで、一旦デプロイ処理は終了した。
Azure App Service PlanとWeb Appインスタンスが作成されている。App Serviceプランの名前には接頭辞として「asp-」が付与されている。
デプロイ結果を確認するためにURL「https://azure-spring-workshop.azurewebsites.net」にアクセスしてみるとエラーになった。。
% curl --header "Content-Type: application/json" --request GET <https://azure-spring-workshop.azurewebsites.net/>
<div style="display: block; margin: auto; width: 600px; height: 500px; text-align: center; font-family: 'Courier', cursive, sans-serif;"><h1 style="color: 747474">:( Application Error</h1><p style="color:#666">If you are the application administrator, you can access the <a style="color: grey"href="<https://azure-spring-workshop.scm.azurewebsites.net/detectors>">diagnostic resources</a>.</div>
Web Appのログストリームを確認すると、Exceptionが発生していた。ログストリームはWeb Appのサイドメニュー「監視」カテゴリにある「ログストリーム」で確認できる。(ログを見ると何が実行されているのか色々分かって素敵)
重要な部分だけ抜粋したログが以下で、原因箇所は「Exception in thread “main” java.lang.UnsupportedClassVersionError: com/example/azureappservice/AzureAppServiceApplication has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0」の部分。
2024-02-04T02:06:14.850755481Z _|_|
2024-02-04T02:06:14.850820881Z _| _| _|_|_|_| _| _| _| _|_| _|_|
2024-02-04T02:06:14.850830481Z _|_|_|_| _| _| _| _|_| _|_|_|_|
2024-02-04T02:06:14.850836981Z _| _| _| _| _| _| _|
2024-02-04T02:06:14.850859481Z _| _| _|_|_|_| _|_|_| _| _|_|_|
2024-02-04T02:06:14.850865581Z
2024-02-04T02:06:14.850871081Z J A V A O N A P P S E R V I C E
2024-02-04T02:06:14.850876682Z
2024-02-04T02:06:14.850882482Z Documentation: <https://aka.ms/appservice>
2024-02-04T02:06:14.850888182Z
2024-02-04T02:06:14.850894082Z **NOTE**: No files or system changes outside of /home will persist beyond your application's current session. /home is your application's persistent storage and is shared across all the server instances.
2024-02-04T02:06:14.851083283Z
2024-02-04T02:06:14.851099883Z
2024-02-04T02:06:14.851364586Z Updating /etc/ssh/sshd_config to use PORT 2222
2024-02-04T02:06:14.883977853Z Starting ssh service...
2024-02-04T02:06:15.074875220Z ssh-keygen: generating new host keys: DSA
2024-02-04T02:06:15.647616520Z * Starting OpenBSD Secure Shell server sshd
2024-02-04T02:06:15.714304867Z ...done.
2024-02-04T02:06:15.769937824Z * sshd is running
2024-02-04T02:06:15.816965610Z ## Printing build info...
2024-02-04T02:06:15.820861442Z PACKAGE | VERSION | COMMIT
2024-02-04T02:06:15.820891542Z Microsoft.AppService.WebsitesExtensionsJava | 1.0.0250117 |
2024-02-04T02:06:15.820902442Z 0a01496e | |
2024-02-04T02:06:15.820909342Z self | 1.0.0250556 | e16c7680
2024-02-04T02:06:15.822887458Z ## Done printing build info.
2024-02-04T02:06:15.822993059Z Container info: WEBSITE_INSTANCE_ID = 004249e9fb0eec022da5f5cd88d24abf01465ff1cb61a03116d48c4a0329538b ; WEBSITE_SITE_NAME = azure-spring-workshop
2024-02-04T02:06:15.841019907Z Add public certificates to keystore if exists...
2024-02-04T02:06:15.842432719Z Add private certificates to keystore if exists...
2024-02-04T02:06:15.844745638Z Configuring max heap = 716 MB
2024-02-04T02:06:15.877763009Z STARTUP_FILE=
2024-02-04T02:06:15.877799809Z STARTUP_COMMAND=
2024-02-04T02:06:15.877810209Z No STARTUP_FILE available.
2024-02-04T02:06:15.877817409Z No STARTUP_COMMAND defined.
2024-02-04T02:06:16.014689232Z Picked up JAVA_TOOL_OPTIONS: -Xmx716M -Djava.net.preferIPv4Stack=true
2024-02-04T02:06:18.197001939Z Mangled result from Jar entry point parser is: __COM_MICROSOFT_AZURE_APPSERVICE_JARENTRYPOINT_PREFIX__org.springframework.boot.loader.launch.JarLauncher__COM_MICROSOFT_AZURE_APPSERVICE_JARENTRYPOINT_SUFFIX__
2024-02-04T02:06:18.300758391Z Extracted jar entry point. Class name is: 'org.springframework.boot.loader.launch.JarLauncher'
2024-02-04T02:06:18.349664392Z Defaulting to UTF-8
2024-02-04T02:06:18.350231197Z Running command: java -cp /home/site/wwwroot/app.jar:/usr/local/appservice/lib/azure.appservice.jar: -Djava.util.logging.config.file=/usr/local/appservice/logging.properties -Dfile.encoding=UTF-8 -Dserver.port=80 -XX:ErrorFile=/home/LogFiles/java_error_azure-spring-workshop_10-30-0-54_%p.log -XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/LogFiles/java_memdump_azure-spring-workshop_10-30-0-54.log -Duser.dir=/home/site/wwwroot org.springframework.boot.loader.launch.JarLauncher
2024-02-04T02:06:18.352509715Z Launched child process with pid: 87
2024-02-04T02:06:18.353103020Z Waiting for main process to exit. GLOBAL_PID_MAIN=87
2024-02-04T02:06:18.353127720Z Waiting for GLOBAL_PID_MAIN == 87
2024-02-04T02:06:18.381338852Z Picked up JAVA_TOOL_OPTIONS: -Xmx716M -Djava.net.preferIPv4Stack=true
2024-02-04T02:06:19.131870609Z Exception in thread "main" java.lang.UnsupportedClassVersionError: com/example/azureappservice/AzureAppServiceApplication has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0
2024-02-04T02:06:19.138272862Z at java.base/java.lang.ClassLoader.defineClass1(Native Method)
2024-02-04T02:06:19.138427863Z at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
2024-02-04T02:06:19.139173369Z at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
2024-02-04T02:06:19.139812375Z at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
2024-02-04T02:06:19.139834475Z at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427)
2024-02-04T02:06:19.139844475Z at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421)
2024-02-04T02:06:19.139852575Z at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
2024-02-04T02:06:19.140320579Z at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420)
2024-02-04T02:06:19.140538981Z at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
2024-02-04T02:06:19.140859683Z at org.springframework.boot.loader.net.protocol.jar.JarUrlClassLoader.loadClass(JarUrlClassLoader.java:104)
2024-02-04T02:06:19.141152386Z at org.springframework.boot.loader.launch.LaunchedClassLoader.loadClass(LaunchedClassLoader.java:91)
2024-02-04T02:06:19.141308187Z at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
2024-02-04T02:06:19.145602322Z at java.base/java.lang.Class.forName0(Native Method)
2024-02-04T02:06:19.145637622Z at java.base/java.lang.Class.forName(Class.java:467)
2024-02-04T02:06:19.145650022Z at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:88)
2024-02-04T02:06:19.145676223Z at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53)
2024-02-04T02:06:19.145685723Z at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58)
2024-02-04T02:06:19.189583783Z Wait for pid == 87 either returned successfully or was interrupted due to a signal 87
2024-02-04T02:06:19.191009095Z Done waiting for main process. GLOBAL_PID_MAIN=87.
2024-02-04T02:06:19.191050895Z Exiting entry script!
2024-02-04T02:06:13.086Z INFO - 17-java17_20231113 Pulling from azure-app-service/java
2024-02-04T02:06:13.109Z INFO - Digest: sha256:dbad22ba1100bb21199bdb0373ea04afb9d25a7a5767521d2a230daf4dee0c0f
2024-02-04T02:06:13.112Z INFO - Status: Image is up to date for 10.1.0.5:13209/azure-app-service/java:17-java17_20231113
2024-02-04T02:06:13.144Z INFO - Pull Image successful, Time taken: 0 Seconds
2024-02-04T02:06:13.813Z INFO - Starting container for site
2024-02-04T02:06:13.814Z INFO - docker run -d --expose=80 --name azure-spring-workshop_0_f4501acc -e WEBSITE_USE_DIAGNOSTIC_SERVER=false -e WEBSITE_SITE_NAME=azure-spring-workshop -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=azure-spring-workshop.azurewebsites.net -e WEBSITE_INSTANCE_ID=004249e9fb0eec022da5f5cd88d24abf01465ff1cb61a03116d48c4a0329538b mcr.microsoft.com/azure-app-service/java:17-java17_20231113
2024-02-04T02:06:13.815Z INFO - Logging is not enabled for this container.Please use <https://aka.ms/linux-diagnostics> to enable logging to see container logs here.
2024-02-04T02:06:16.194Z INFO - Initiating warmup request to container azure-spring-workshop_0_f4501acc for site azure-spring-workshop
2024-02-04T02:06:31.542Z ERROR - Container azure-spring-workshop_0_f4501acc for site azure-spring-workshop has exited, failing site start
2024-02-04T02:06:31.560Z ERROR - Container azure-spring-workshop_0_f4501acc didn't respond to HTTP pings on port: 80, failing site start. See container logs for debugging.
2024-02-04T02:06:31.581Z INFO - Stopping site azure-spring-workshop because it failed during startup.
単純に、ビルド時のJavaバージョンとランタイムのJavaバージョンの不一致で、classファイルのロードに失敗しているという事だった。
pom.xmlのターゲットjavaバージョンをランタイムバージョンに合わせる。
<project ...>
...
<properties>
<!-- ここをruntime/javaVersionに合わせた -->
<java.version>17</java.version>
</properties>
...
<build>
<plugins>
<plugin>
<groupId>com.microsoft.azure</groupId>
...
<configuration>
...
<runtime>
<os>Linux</os>
<javaVersion>Java 17</javaVersion>
<webContainer>Java SE</webContainer>
</runtime>
再デプロイ時の注意事項は、今回pom.xmlファイルだけ変更しJavaソースコードは変更していないため、そのままdeployコマンドを実行すると再コンパイルされずに前のclassファイルがデプロイされる。
このため、最初のゴールとしてcleanも含める事で再コンパイルを実行してからdeployする。
% mvn clean package com.microsoft.azure:azure-webapp-maven-plugin:2.9.0:deploy
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:AzureAppService >---------------------
[INFO] Building AzureAppService 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ AzureAppService ---
[INFO] Deleting /Users/sato/proj/learn/java/SpringTips/AzureAppService/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ AzureAppService ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ AzureAppService ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 1 source file with javac [debug release 17] to target/classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ AzureAppService ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/AzureAppService/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ AzureAppService ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 1 source file with javac [debug release 17] to target/test-classes
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ AzureAppService ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.azureappservice.AzureAppServiceApplicationTests
11:21:48.404 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.example.azureappservice.AzureAppServiceApplicationTests]: AzureAppServiceApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
11:21:48.501 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.example.azureappservice.AzureAppServiceApplication for test class com.example.azureappservice.AzureAppServiceApplicationTests
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-02-04T11:21:48.836+09:00 INFO 5458 --- [ main] c.e.a.AzureAppServiceApplicationTests : Starting AzureAppServiceApplicationTests using Java 21.0.1 with PID 5458 (started by sato in /Users/sato/proj/learn/java/SpringTips/AzureAppService)
2024-02-04T11:21:48.837+09:00 INFO 5458 --- [ main] c.e.a.AzureAppServiceApplicationTests : No active profile set, falling back to 1 default profile: "default"
2024-02-04T11:21:49.764+09:00 INFO 5458 --- [ main] c.e.a.AzureAppServiceApplicationTests : Started AzureAppServiceApplicationTests in 1.132 seconds (process running for 1.984)
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
WARNING: A Java agent has been loaded dynamically (/Users/sato/.m2/repository/net/bytebuddy/byte-buddy-agent/1.14.11/byte-buddy-agent-1.14.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.408 s -- in com.example.azureappservice.AzureAppServiceApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ AzureAppService ---
[INFO] Building jar: /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (repackage) @ AzureAppService ---
[INFO] Replacing main artifact /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar.original
[INFO]
[INFO] --- azure-webapp:2.9.0:deploy (default-cli) @ AzureAppService ---
[INFO] Auth type: AZURE_CLI
[INFO] Default subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Username: IOCC_sato@gbssandbox.onmicrosoft.com
[INFO] Subscription: GBS Azure Sandbox(51cf59b2-3184-4d6c-abb6-6ab69af3415c)
[INFO] Trying to deploy external resources to azure-spring-workshop...
[INFO] Successfully deployed the resources to azure-spring-workshop
[INFO] Trying to deploy artifact to azure-spring-workshop...
[INFO] Deploying (/Users/sato/proj/learn/java/SpringTips/AzureAppService/target/AzureAppService-0.0.1-SNAPSHOT.jar)[jar] ...
[INFO] Using service version null
[INFO] Using service version null
[INFO] Deployment Status: BuildSuccessful; Successful Instance Count: 0; In-progress Instance Count: 0; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[INFO] Deployment Status: RuntimeStarting; Successful Instance Count: 0; In-progress Instance Count: 1; Failed Instance Count: 0
[WARNING] Resource deployed, but the deployment is still in process in Azure
[INFO] Successfully deployed the artifact to <https://azure-spring-workshop.azurewebsites.net>
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 04:09 min
[INFO] Finished at: 2024-02-04T11:25:55+09:00
[INFO] ------------------------------------------------------------------------
今度は正常に実行された。
% curl --header "Content-Type: application/json" --request GET https://azure-spring-workshop.azurewebsites.net
Hello, World.
mavenでSpring Bootアプリケーションのfat-jarの作成方法は以下にまとめた通り、「spring-boot-starter-parent」にはすでに「repackage」ゴールが定義されているため、mvn コマンドにて「spring-boot:repackage」を指定する事で作成できる。
ただし、このままでは「mvn pakcage spring-boot:repackage」という少し長いコマンドを実行しないと正しくrepackageは実行されない。
ここでは「spring-boot-maven-plugin」により、mavenの標準フェーズ「package」に「repackage」を関連づけし、「package」実行時に「repackage」も実行するようにpom.xmlを更新する。
まずmaven自体がpluginを実行するためのコアフレームワークという前提があり、mavenで実行するbuildもしくはpackagingの各タスクはpluginのコレクションで構成されている。
各pluginは、buildで使用するか、reportingで使用するかが意図されて作成されている。
各pluginはmojo(Maven plain Old Java Objectの略)形式のクラスライブラリで、mavenにて実行できるゴールを定義したもの。
1つのmojoには1つのゴールが定義され、それがjarでコレクション化されている。
例えば、spring-boot-maven-pluginは、buildで使われるように構築されたpluginで、複数のmojo(ゴール)で構成されたjarファイルとなっている。
mavenのpluginには「build plugin」と「reporting plugin」という2種類のpluginがある。
build pluginは、アーティファクト(通常はjar or war)のbuild時に特定のフェーズに関連づけたゴールを実行するためのプラグインであり、reporting pluginは、プロジェクトのサイト情報を生成する時に、サイト情報の生成の一部として実行するためのプラグイン。
今回使用するspring-boot-maven-pluginはbuild時にjarをrepackageするためのpluginなので、buildタグの中に定義する。
様々なサイトでspring boot maven pluginをpom.xmlに定義しているサンプルを見るが、そもそも使いたいpluginがある場合、「groupId」「artifactId」「version」をどのように確認すれば良いか。
その情報はmaven central repositoryサイトで調べる事ができる。
今回利用する「spring-boot-maven-plugin」の情報を検索すると以下のように表示された。先ほど作成したpom.xmlに定義した情報はこのようにして得る事ができる。
上記の各情報を元に、更新前と更新後のpom.xmlを掲載する。
以下、更新前のpom.xml。
<!-- 更新前pom.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<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/POM/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
</parent>
<!-- Additional lines to be added here... -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
そして以下が更新後のpom.xml。
<!-- 更新後pom.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<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/POM/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
</parent>
<!-- Additional lines to be added here... -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- ここにpluginを追加-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
ちなみに、上記の記述により、「mvn package」を実行すると「repackage」が自動的に実行されるが、さまざまなサイトで紹介されているpom.xmlは以下のようになっている。これを実行すると実行ログからrepackageが2回実行されていたため、余計なタスクが実行されている事がわかり、私は上記のようにpluginの定義だけとした。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 以降の定義があるとrepackageが重複して実行されてしまう -->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
以下にspring-boot-maven-pluginを指定して「mvn package」を実行した時の結果をログする。
$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/getstartedapp/src/main/resources
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/getstartedapp/src/main/resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ myproject ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/SpringTips/getstartedapp/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ myproject ---
[INFO] No sources to compile
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ myproject ---
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ myproject ---
[INFO] Building jar: /Users/sato/proj/learn/java/SpringTips/getstartedapp/target/myproject-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (repackage) @ myproject ---
[INFO] Replacing main artifact /Users/sato/proj/learn/java/SpringTips/getstartedapp/target/myproject-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/SpringTips/getstartedapp/target/myproject-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.580 s
[INFO] Finished at: 2024-01-29T18:42:07+09:00
[INFO] ------------------------------------------------------------------------
それは、以下リンク先に記載がある通り、デフォルトでrepackageゴールがpackageフェーズに関連づけされているから。
pom.xmlにspring-boot-maven-pluginを定義し、repackageゴールを実行するように定義する事で、mvn packageを実行するとrepackageも実行され、java -jarコマンドで実行可能なjarファイルが生成される。
これが常に必要という事ではなく、以下のように使い分けする必要があると思う。今後どのように定義すれば1つのpom.xmlでこれらの用途でbuildできるjarを分ける事ができるか確認したい。
この2つのゴールには違いがあり、生成されるjar/warファイルに含まれるものが異なる。その違いを確認してみた。
今回は以下のサイトを参考に確認してみた。
今回2つのゴールの違いを確認するためのJavaソースプログラムとpom.xmlファイルは以下の通り。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class MyApplication {
@GetMapping("/")
String home() {
return "Hello, World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<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/POM/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
</parent>
<!-- Additional lines to be added here... -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
上記の通り、@RestControllerを付与したControllerクラスを作成し、URLパス”/”を受信した場合のリクエストハンドラーメソッドを定義している。
このプロジェクトで”mvn spring-boot:run”を実行すると以下の処理が実行される。
$ mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.2.1:run (default-cli) > test-compile @ myproject >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/main/resources
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/main/resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ myproject ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ myproject ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.2.1:run (default-cli) < test-compile @ myproject <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.2.1:run (default-cli) @ myproject ---
[INFO] Attaching agents: []
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.1)
2024-01-25T22:51:51.815+09:00 INFO 6743 --- [ main] com.example.MyApplication : Starting MyApplication using Java 21.0.1 with PID 6743 (/Users/sato/proj/learn/java/spring/getstartedapp/target/classes started by sato in /Users/sato/proj/learn/java/spring/getstartedapp)
2024-01-25T22:51:51.818+09:00 INFO 6743 --- [ main] com.example.MyApplication : No active profile set, falling back to 1 default profile: "default"
2024-01-25T22:51:52.657+09:00 INFO 6743 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-01-25T22:51:52.674+09:00 INFO 6743 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-01-25T22:51:52.674+09:00 INFO 6743 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2024-01-25T22:51:52.776+09:00 INFO 6743 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-01-25T22:51:52.777+09:00 INFO 6743 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 909 ms
2024-01-25T22:51:53.117+09:00 INFO 6743 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-01-25T22:51:53.124+09:00 INFO 6743 --- [ main] com.example.MyApplication : Started MyApplication in 1.673 seconds (process running for 2.006)
上記処理の中にはjarファイルもしくはwarファイルの作成は含まれていない。”spring-boot:run”はコンパイルしたプログラムをローカルで実行するためのゴールになっているので、作成したプログラムを別プログラムから参照したり、クラウドにデプロイするためにjarファイルやwarファイルにする場合は”package”ゴールもしくは”spring-boot:repackage”ゴールを指定する必要がある。
mavenの”package”ゴールを実行すると、ビルド対象のプロジェクト配下に存在するソースコードをコンパイルしてclassファイルを生成し、リソースファイルと共にjarもしくはwarファイルを作成する。
実行して作成したjarファイルの内容を見てみた。
$ jar -tvf myproject-0.0.1-SNAPSHOT.jar
0 Thu Jan 25 22:42:26 JST 2024 META-INF/
154 Thu Jan 25 22:42:26 JST 2024 META-INF/MANIFEST.MF
0 Tue Jan 23 21:01:32 JST 2024 com/
0 Tue Jan 23 21:01:32 JST 2024 com/example/
0 Thu Jan 25 22:42:26 JST 2024 META-INF/maven/
0 Thu Jan 25 22:42:26 JST 2024 META-INF/maven/com.example/
0 Thu Jan 25 22:42:26 JST 2024 META-INF/maven/com.example/myproject/
975 Tue Jan 23 21:01:32 JST 2024 com/example/MyApplication.class
835 Sun Jan 14 17:49:42 JST 2024 META-INF/maven/com.example/myproject/pom.xml
64 Tue Jan 23 17:35:20 JST 2024 META-INF/maven/com.example/myproject/pom.properties
ここで生成されたjarファイルを使ってローカルでWEBアプリを実行しようとすると、実行できない。
$ java -jar ./target/myproject-0.0.1-SNAPSHOT.jar
./target/myproject-0.0.1-SNAPSHOT.jarにメイン・マニフェスト属性がありません
マニフェストファイルを見てみると、当然メインクラスが指定されていないため、Javaランタイムはエントリーポイントが分からずエラーになった。
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
Implementation-Title: myproject
Implementation-Version: 0.0.1-SNAPSHOT
また、当然のことながら生成したjarにはWEBアプリに必要となるサーブレットコンテナーや依存関係のあるクラスが含まれていないため、クラスパスもしくはjarファイルとして別途サーブレットコンテナーや依存関係のあるクラスを指定する必要がある。
ここで分かることは、mavenの”package”ゴールは、ターゲットとなったソースコードをjarもしくはwarにアーカイブするだけで、実行するために必要なライブラリやメインクラスの指定が含まれないため”java -jar xxx.jar”の形式では実行できない。
上記のリンク先にある説明は非常に分かりづらいのだが、要するに作成したjarファイルを元に、MANIFEST.MFを書き換えてメインクラスを指定し、かつ実行するために必要となるすべてのjarファイルを含めた”fat jar”を作成するというゴールであるということ。
だが、この「repackage」という名前、なぜ「再パッケージ」なのか最初は疑問だった。
実際に実行すると、「ソースファイルの指定が必須」的なエラーになり、「なんか使い方がよくわからない」という感じだった。
$ mvn spring-boot:repackage
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- spring-boot:3.2.1:repackage (default-cli) @ myproject ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.242 s
[INFO] Finished at: 2024-01-25T22:31:55+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.2.1:repackage (default-cli) on project myproject: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:3.2.1:repackage failed: Source file must not be null -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] <http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException>
(エラーの理解が雑なのだが、ここに時間をかけていられないので、このエラーは一旦「spring-boot:repackageを単独で実行すると上記エラーとなる」ということだけメモっておく)
以下のサイトの情報を元にrepackageというゴールを実行できたことでこの名の理由がわかった。
spring-boot:repackageを実行するためにはpackageの後に指定する必要がある。
情報は古いが、このビルド方法はこのログを書いている時点(2024/1/28)で有効だ。
$ mvn package spring-boot:repackage
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/diffMvnPackageAndSpringBootRepackage/src/main/resources
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/diffMvnPackageAndSpringBootRepackage/src/main/resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ myproject ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/diffMvnPackageAndSpringBootRepackage/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ myproject ---
[INFO] No sources to compile
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ myproject ---
[INFO] No tests to run.
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ myproject ---
[INFO]
[INFO] --- spring-boot:3.2.2:repackage (default-cli) @ myproject ---
Downloading from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-buildpack-platform/3.2.2/spring-boot-buildpack-platform-3.2.2.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-buildpack-platform/3.2.2/spring-boot-buildpack-platform-3.2.2.pom> (3.2 kB at 11 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-loader-tools/3.2.2/spring-boot-loader-tools-3.2.2.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-loader-tools/3.2.2/spring-boot-loader-tools-3.2.2.pom> (2.2 kB at 140 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-buildpack-platform/3.2.2/spring-boot-buildpack-platform-3.2.2.jar>
Downloading from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-loader-tools/3.2.2/spring-boot-loader-tools-3.2.2.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-buildpack-platform/3.2.2/spring-boot-buildpack-platform-3.2.2.jar> (272 kB at 3.4 MB/s)
Downloaded from central: <https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-loader-tools/3.2.2/spring-boot-loader-tools-3.2.2.jar> (434 kB at 4.2 MB/s)
[INFO] Replacing main artifact /Users/sato/proj/learn/java/spring/diffMvnPackageAndSpringBootRepackage/target/myproject-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/sato/proj/learn/java/spring/diffMvnPackageAndSpringBootRepackage/target/myproject-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.553 s
[INFO] Finished at: 2024-01-28T18:41:48+09:00
[INFO] ------------------------------------------------------------------------
packageゴールでjarを作成し、その後にrepackageゴールにてjarファイルを再作成している。
再作成したjarファイルの内容を確認してみると、spring-boot関連のクラスが追加され、サードパーティjarファイルが複数含まれており、かつMANIFEST.MFファイルにもmain-classが指定されている。
$ jar xvf myproject-0.0.1-SNAPSHOT.jar
META-INF/が作成されました
META-INF/MANIFEST.MFが展開されました
META-INF/services/が作成されました
META-INF/services/java.nio.file.spi.FileSystemProviderが展開されました
org/が作成されました
org/springframework/が作成されました
org/springframework/boot/が作成されました
org/springframework/boot/loader/が作成されました
org/springframework/boot/loader/jar/が作成されました
org/springframework/boot/loader/jar/ManifestInfo.classが展開されました
org/springframework/boot/loader/jar/MetaInfVersionsInfo.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$JarEntriesEnumeration.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$JarEntryInflaterInputStream.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$JarEntryInputStream.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$NestedJarEntry.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$RawZipDataInputStream.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile$ZipContentEntriesSpliterator.classが展開されました
org/springframework/boot/loader/jar/NestedJarFile.classが展開されました
org/springframework/boot/loader/jar/NestedJarFileResources.classが展開されました
org/springframework/boot/loader/jar/SecurityInfo.classが展開されました
org/springframework/boot/loader/jar/ZipInflaterInputStream.classが展開されました
org/springframework/boot/loader/jarmode/が作成されました
org/springframework/boot/loader/jarmode/JarMode.classが展開されました
org/springframework/boot/loader/launch/が作成されました
org/springframework/boot/loader/launch/Archive$Entry.classが展開されました
org/springframework/boot/loader/launch/Archive.classが展開されました
org/springframework/boot/loader/launch/ClassPathIndexFile.classが展開されました
org/springframework/boot/loader/launch/ExecutableArchiveLauncher.classが展開されました
org/springframework/boot/loader/launch/ExplodedArchive$FileArchiveEntry.classが展開されました
org/springframework/boot/loader/launch/ExplodedArchive.classが展開されました
org/springframework/boot/loader/launch/JarFileArchive$JarArchiveEntry.classが展開されました
org/springframework/boot/loader/launch/JarFileArchive.classが展開されました
org/springframework/boot/loader/launch/JarLauncher.classが展開されました
org/springframework/boot/loader/launch/JarModeRunner.classが展開されました
org/springframework/boot/loader/launch/LaunchedClassLoader$DefinePackageCallType.classが展開されました
org/springframework/boot/loader/launch/LaunchedClassLoader.classが展開されました
org/springframework/boot/loader/launch/Launcher.classが展開されました
org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator$Using.classが展開されました
org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator.classが展開されました
org/springframework/boot/loader/launch/PropertiesLauncher.classが展開されました
org/springframework/boot/loader/launch/SystemPropertyUtils.classが展開されました
org/springframework/boot/loader/launch/WarLauncher.classが展開されました
org/springframework/boot/loader/log/が作成されました
org/springframework/boot/loader/log/DebugLogger$DisabledDebugLogger.classが展開されました
org/springframework/boot/loader/log/DebugLogger$SystemErrDebugLogger.classが展開されました
org/springframework/boot/loader/log/DebugLogger.classが展開されました
org/springframework/boot/loader/net/が作成されました
org/springframework/boot/loader/net/protocol/が作成されました
org/springframework/boot/loader/net/protocol/Handlers.classが展開されました
org/springframework/boot/loader/net/protocol/jar/が作成されました
org/springframework/boot/loader/net/protocol/jar/Canonicalizer.classが展開されました
org/springframework/boot/loader/net/protocol/jar/Handler.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarFileUrlKey.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrl.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader$OptimizedEnumeration.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$ConnectionInputStream.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$EmptyUrlStreamHandler.classが展開されました
org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.classが展開されました
org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.classが展開されました
org/springframework/boot/loader/net/protocol/jar/Optimizations.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarEntry.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarFile.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarFileFactory.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarFiles$Cache.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarFiles.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarManifest$ManifestSupplier.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlJarManifest.classが展開されました
org/springframework/boot/loader/net/protocol/jar/UrlNestedJarFile.classが展開されました
org/springframework/boot/loader/net/protocol/nested/が作成されました
org/springframework/boot/loader/net/protocol/nested/Handler.classが展開されました
org/springframework/boot/loader/net/protocol/nested/NestedLocation.classが展開されました
org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection$ConnectionInputStream.classが展開されました
org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection.classが展開されました
org/springframework/boot/loader/net/protocol/nested/NestedUrlConnectionResources.classが展開されました
org/springframework/boot/loader/net/util/が作成されました
org/springframework/boot/loader/net/util/UrlDecoder.classが展開されました
org/springframework/boot/loader/nio/が作成されました
org/springframework/boot/loader/nio/file/が作成されました
org/springframework/boot/loader/nio/file/NestedByteChannel$Resources.classが展開されました
org/springframework/boot/loader/nio/file/NestedByteChannel.classが展開されました
org/springframework/boot/loader/nio/file/NestedFileStore.classが展開されました
org/springframework/boot/loader/nio/file/NestedFileSystem.classが展開されました
org/springframework/boot/loader/nio/file/NestedFileSystemProvider.classが展開されました
org/springframework/boot/loader/nio/file/NestedPath.classが展開されました
org/springframework/boot/loader/ref/が作成されました
org/springframework/boot/loader/ref/Cleaner.classが展開されました
org/springframework/boot/loader/ref/DefaultCleaner.classが展開されました
org/springframework/boot/loader/zip/が作成されました
org/springframework/boot/loader/zip/ByteArrayDataBlock.classが展開されました
org/springframework/boot/loader/zip/CloseableDataBlock.classが展開されました
org/springframework/boot/loader/zip/DataBlock.classが展開されました
org/springframework/boot/loader/zip/DataBlockInputStream.classが展開されました
org/springframework/boot/loader/zip/FileChannelDataBlock$ManagedFileChannel.classが展開されました
org/springframework/boot/loader/zip/FileChannelDataBlock$Tracker.classが展開されました
org/springframework/boot/loader/zip/FileChannelDataBlock.classが展開されました
org/springframework/boot/loader/zip/NameOffsetLookups.classが展開されました
org/springframework/boot/loader/zip/VirtualDataBlock.classが展開されました
org/springframework/boot/loader/zip/VirtualZipDataBlock$DataPart.classが展開されました
org/springframework/boot/loader/zip/VirtualZipDataBlock.classが展開されました
org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryLocator.classが展開されました
org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryRecord.classが展開されました
org/springframework/boot/loader/zip/ZipCentralDirectoryFileHeaderRecord.classが展開されました
org/springframework/boot/loader/zip/ZipContent$Entry.classが展開されました
org/springframework/boot/loader/zip/ZipContent$Kind.classが展開されました
org/springframework/boot/loader/zip/ZipContent$Loader.classが展開されました
org/springframework/boot/loader/zip/ZipContent$Source.classが展開されました
org/springframework/boot/loader/zip/ZipContent.classが展開されました
org/springframework/boot/loader/zip/ZipDataDescriptorRecord.classが展開されました
org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord$Located.classが展開されました
org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord.classが展開されました
org/springframework/boot/loader/zip/ZipLocalFileHeaderRecord.classが展開されました
org/springframework/boot/loader/zip/ZipString$CompareType.classが展開されました
org/springframework/boot/loader/zip/ZipString.classが展開されました
BOOT-INF/が作成されました
BOOT-INF/classes/が作成されました
BOOT-INF/classes/com/が作成されました
BOOT-INF/classes/com/example/が作成されました
META-INF/maven/が作成されました
META-INF/maven/com.example/が作成されました
META-INF/maven/com.example/myproject/が作成されました
BOOT-INF/classes/com/example/MyProject.classが展開されました
META-INF/maven/com.example/myproject/pom.xmlが展開されました
META-INF/maven/com.example/myproject/pom.propertiesが展開されました
BOOT-INF/lib/が作成されました
BOOT-INF/lib/spring-boot-3.2.2.jarが抽出されました
BOOT-INF/lib/spring-boot-autoconfigure-3.2.2.jarが抽出されました
BOOT-INF/lib/logback-classic-1.4.14.jarが抽出されました
BOOT-INF/lib/logback-core-1.4.14.jarが抽出されました
BOOT-INF/lib/slf4j-api-2.0.11.jarが抽出されました
BOOT-INF/lib/log4j-to-slf4j-2.21.1.jarが抽出されました
BOOT-INF/lib/log4j-api-2.21.1.jarが抽出されました
BOOT-INF/lib/jul-to-slf4j-2.0.11.jarが抽出されました
BOOT-INF/lib/jakarta.annotation-api-2.1.1.jarが抽出されました
BOOT-INF/lib/spring-core-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-jcl-6.1.3.jarが抽出されました
BOOT-INF/lib/snakeyaml-2.2.jarが抽出されました
BOOT-INF/lib/jackson-databind-2.15.3.jarが抽出されました
BOOT-INF/lib/jackson-annotations-2.15.3.jarが抽出されました
BOOT-INF/lib/jackson-core-2.15.3.jarが抽出されました
BOOT-INF/lib/jackson-datatype-jdk8-2.15.3.jarが抽出されました
BOOT-INF/lib/jackson-datatype-jsr310-2.15.3.jarが抽出されました
BOOT-INF/lib/jackson-module-parameter-names-2.15.3.jarが抽出されました
BOOT-INF/lib/tomcat-embed-core-10.1.18.jarが抽出されました
BOOT-INF/lib/tomcat-embed-el-10.1.18.jarが抽出されました
BOOT-INF/lib/tomcat-embed-websocket-10.1.18.jarが抽出されました
BOOT-INF/lib/spring-web-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-beans-6.1.3.jarが抽出されました
BOOT-INF/lib/micrometer-observation-1.12.2.jarが抽出されました
BOOT-INF/lib/micrometer-commons-1.12.2.jarが抽出されました
BOOT-INF/lib/spring-webmvc-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-aop-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-context-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-expression-6.1.3.jarが抽出されました
BOOT-INF/lib/spring-boot-jarmode-layertools-3.2.2.jarが抽出されました
BOOT-INF/classpath.idxが展開されました
BOOT-INF/layers.idxが展開されました
大きくは以下のように変わっていた。
ようやく”repackage”という名前の意味がわかった。
また、”repackage”は、単独では動作せず、必ずpackageにてjarを作成してから実行するものであるということもわかった。
毎回”mvn package spring-boot:repackage”を実行するのも面倒ということで、それを簡易化するためにも利用できるspring boot のmaven pluginを次回確認する。
]]>想定の知識レベルは、「Hellow worldを最小構成のpom.xmlでmavenビルド」ぐらいを勉強した程度。SpringBootが何者かについてはざっくりと知っている。この記事を読んでもらえば無知程度がわかると思う。
以下、参考にした情報。
WebアプリケーションサーバーとしてTomcatを使用したSpring Bootで「Hello World」Webアプリを作る。
SpringではBuildシステムとしてMavenとGradleが利用できるようだ。今回はMavenでビルドできるように準備する。
以下の内容で理解していく。
Javaは現役の時のメイン言語だったので新しい言語仕様があっても少し勉強すれば理解できるのだが、MavenとSpringは全くの素人。「Hello World」でも1つ1つの用語やプロセスがわからないので最初はかなりハードルが高く感じる。
Spring Bootを使用する場合、Mavenの最小構成pom.xmlに対して<parent>タグを追加し、Spring Boot関連のプロジェクト設定情報を継承すればよいようだ。
これはプロジェクト設定のみであり、必要となるライブラリなどは後ほど定義していく。
<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/POM/maven-4.0.0.xsd">
<!-- pom.xmlの最小構成要素 -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- ここがSpring Bootの親プロジェクトを継承しているところ -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
</parent>
<!-- Additional lines to be added here... -->
</project>
pom.xmlの最小構成は理解しているが、parentがお初なので少し調べて理解する。
上記に理解が難しい内容が書かれているが、まずは以下のように理解しておけば良い。
mavenのpom.xmlファイルではpom.xmlの継承(pom.xmlの親子関係)がサポートされている。Spring Boot用のプロジェクト設定情報は「spring-boot-starter-parent」というプロジェクトに集約されているようで、Spring Bootのアプリケーションを開発する場合はこのプロジェクト情報を継承すれば良い。
Spring Bootのプロジェクト設定を定義したあと、Webアプリで必要となるクラスライブラリを<dependency>タグで追加定義する。
<?xml version="1.0" encoding="UTF-8" ?>
<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/POM/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
</parent>
<!-- Additional lines to be added here... -->
<!-- Spring BootのWEB系クラスライブラリ群を追加-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
上記依存関係を追加してmavenの依存関係ツリー表示コマンド「mvn dependency:tree」を実行すると、どのようなjarが依存関係として紐づけられたか確認できる。
$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- dependency:3.6.1:tree (default-cli) @ myproject ---
[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT
[INFO] \\- org.springframework.boot:spring-boot-starter-web:jar:3.2.1:compile
[INFO] +- org.springframework.boot:spring-boot-starter:jar:3.2.1:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:3.2.1:compile
[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.2.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:3.2.1:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.4.14:compile
[INFO] | | | +- ch.qos.logback:logback-core:jar:1.4.14:compile
[INFO] | | | \\- org.slf4j:slf4j-api:jar:2.0.9:compile
[INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.21.1:compile
[INFO] | | | \\- org.apache.logging.log4j:log4j-api:jar:2.21.1:compile
[INFO] | | \\- org.slf4j:jul-to-slf4j:jar:2.0.9:compile
[INFO] | +- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile
[INFO] | +- org.springframework:spring-core:jar:6.1.2:compile
[INFO] | | \\- org.springframework:spring-jcl:jar:6.1.2:compile
[INFO] | \\- org.yaml:snakeyaml:jar:2.2:compile
[INFO] +- org.springframework.boot:spring-boot-starter-json:jar:3.2.1:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.15.3:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.15.3:compile
[INFO] | | \\- com.fasterxml.jackson.core:jackson-core:jar:2.15.3:compile
[INFO] | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.15.3:compile
[INFO] | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.15.3:compile
[INFO] | \\- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.15.3:compile
[INFO] +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.2.1:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.17:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-el:jar:10.1.17:compile
[INFO] | \\- org.apache.tomcat.embed:tomcat-embed-websocket:jar:10.1.17:compile
[INFO] +- org.springframework:spring-web:jar:6.1.2:compile
[INFO] | +- org.springframework:spring-beans:jar:6.1.2:compile
[INFO] | \\- io.micrometer:micrometer-observation:jar:1.12.1:compile
[INFO] | \\- io.micrometer:micrometer-commons:jar:1.12.1:compile
[INFO] \\- org.springframework:spring-webmvc:jar:6.1.2:compile
[INFO] +- org.springframework:spring-aop:jar:6.1.2:compile
[INFO] +- org.springframework:spring-context:jar:6.1.2:compile
[INFO] \\- org.springframework:spring-expression:jar:6.1.2:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.214 s
[INFO] Finished at: 2024-01-14T17:50:36+09:00
[INFO] ------------------------------------------------------------------------
Spring以外にも、log4j、yaml、json、tomcatなどの関連クラスライブラリが含まれている事が確認できる。
通常、mavenでは以下の3つの要素でライブラリを特定している。
だが、今回dependencyタグで指定したspring-boot-starter-webではversionタグを指定していない。これは、spring-boot-starter-parentプロジェクト設定にて、各クラスライブラリのバージョンが定義されており、明示的に指定しない場合はそのバージョンが参照されるようだ。
もちろん指定する事で特定バージョンを参照できるようだが、今のところその必要性がないため、指定は可能ということだけは覚えておく。
SpringBootアプリであること、Restアプリであること、この2つをClass Annotationで宣言する事で、昔のデザインパターンであるServletのRequest Handlerの仕組みが出来上がるイメージを持った。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class MyApplication {
@RequestMapping("/")
String home() {
return "Hello, World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
クラスのmainメソッドでは、SpringApplicationのrunメソッドに対して自分自身のクラスオブジェクトを引数として指定して実行しているだけ。Webアプリとはいえプログラムの開始ポイントが明確であり、それが昔ながらのmainメソッドであるというのは非常にわかりやすい。あとは必要なHTTPリクエストを受け付けるコールバック関数を書いていく感じ。
Spring Bootではメソッド単位にHTTPリクエストを受け付けるためのマッピングが可能になっている。以前はRequest Handler(HTTPリクエストを処理する)クラスだったが、Spring Bootではメソッド単位にRequest Handler(ハンドラーメソッド)を宣言できる。
それでは、以下Springアノテーションについて調査した内容を記載する。
RestControllerアノテーションは、REST通信を行うサーバーを宣言するためのSpringステレオタイプアノテーションで、宣言されたクラスはWebアプリのリクエストハンドラー(Controller)として機能する。ハンドラーメソッド(サンプルプログラムの”home”メソッド、@RequestMappingが付与されている)を定義する事で、特定URLパスへのHTTPメソッド呼び出しに対応する処理を定義する事ができる。
サーバー側の処理結果データを直接HTTPレスポンスボディとして返却でき、データはJSONやXMLフォーマットでも良いし、テキストでもよい。
通常のHTMLを返却する@Controllerアノテーションとの違いは、モデル・ビューが不要であるところ。
@RestControllerでは、Request Handlerとして宣言したメソッドの戻り値がそのままHTTPレスポンスになるようで、プログラマーはそれがHTTPレスポンスかどうか意識する必要がなく、かつユニットテストが簡単になるのでとても都合がいい。
以下のサイトの説明がわかりやすかったのでリンクを掲載。
日本語でググっても分かりやすい説明が見つけられず、英語のサイトでようやく腹落ちできる情報に出会った。
https://medium.com/javarevisited/stereotype-annotations-in-spring-the-building-blocks-of-efficient-application-development-710eeb9dfb0a#:~:text=Stereotype annotations are a powerful,a particular role or purpose.
上記の記事では、ステレオタイプアノテーションを以下のように定義している。
ステレオタイプアノテーションは、アプリケーション内のクラスに対して特定の役割を付与することを目的とする。 ステレオタイプアノテーションは、Springコンポーネント構成定義を簡素化するために用いる事ができる。クラスには、ステレオタイプが示す共通のデフォルト値と振る舞いが付与される。
以下にステレオタイプとして定義されているSpringアノテーションの例を示す。WebアプリのMVCパターンをアノテーションで組めるようになっている。
多くのSpring Boot開発者は、自分のアプリケーションに対して以下のSpring機能を有効にする。
@SpringBootApplicationアノテーションは、これら3つの基本的な機能を1つのアノテーションで有効にするメタアノテーションとして利用できる。各アノテーションは今後調査するが、ここで重要なのは@EnableAutoConfiguration。
先ほど作成したpom.xmlの依存関係には「spring-boot-starter-web」を追加した。このコンポーネントにはSpring MVCとTomcatが含まれている。@EnableAutoConfigurationを宣言すると、Springはこれらの依存関係に含まれているコンポーネントを自動検出し、Webアプリケーションを開発するためのコンポーネントを推測して自動構成する。
“/”から始まるHTTPリクエストのルーティング情報を定義するメソッドレベルアノテーション。
サンプルプログラムでは、”/”というURLパスが指定されたら”home”メソッドを実行するように定義している。
このようにRequestMappingアノテーションが定義されたメソッドのことを「ハンドラーメソッド」ともいう。
サンプルで使われているゴール「spring-boot:run」はspring-boot-starter-webに含まれているSpring Bootプラグインのゴールで、以下の処理が実行される。
1つだけ気になったのは、ここではjarやwarといった配布用のファイルは作成していないという事。配布用ファイルの作成(パッケージングというらしい)には、mavenデフォルトのゴールとSpring Bootの再パッケージを行うゴールという2つの方法があるようなので、このあと少し詳細に確認してみる。
以下実行ログにより上記の項目が順次実行されている事がわかる。
$ mvn spring-boot:run
[INFO] Scanning for projects...
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-failsafe-plugin/3.1.2/maven-failsafe-plugin-3.1.2.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-failsafe-plugin/3.1.2/maven-failsafe-plugin-3.1.2.pom> (10 kB at 9.8 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-failsafe-plugin/3.1.2/maven-failsafe-plugin-3.1.2.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-failsafe-plugin/3.1.2/maven-failsafe-plugin-3.1.2.jar> (54 kB at 598 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-invoker-plugin/3.6.0/maven-invoker-plugin-3.6.0.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-invoker-plugin/3.6.0/maven-invoker-plugin-3.6.0.pom> (16 kB at 394 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/groovy/groovy-bom/4.0.12/groovy-bom-4.0.12.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/groovy/groovy-bom/4.0.12/groovy-bom-4.0.12.pom> (27 kB at 1.2 MB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-invoker-plugin/3.6.0/maven-invoker-plugin-3.6.0.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-invoker-plugin/3.6.0/maven-invoker-plugin-3.6.0.jar> (135 kB at 633 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-javadoc-plugin/3.6.3/maven-javadoc-plugin-3.6.3.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-javadoc-plugin/3.6.3/maven-javadoc-plugin-3.6.3.pom> (22 kB at 612 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/41/maven-plugins-41.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/41/maven-plugins-41.pom> (7.4 kB at 306 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/41/maven-parent-41.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/41/maven-parent-41.pom> (50 kB at 1.9 MB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/apache/31/apache-31.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/apache/31/apache-31.pom> (24 kB at 1.0 MB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-javadoc-plugin/3.6.3/maven-javadoc-plugin-3.6.3.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-javadoc-plugin/3.6.3/maven-javadoc-plugin-3.6.3.jar> (510 kB at 2.8 MB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-shade-plugin/3.5.1/maven-shade-plugin-3.5.1.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-shade-plugin/3.5.1/maven-shade-plugin-3.5.1.pom> (14 kB at 643 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-shade-plugin/3.5.1/maven-shade-plugin-3.5.1.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-shade-plugin/3.5.1/maven-shade-plugin-3.5.1.jar> (147 kB at 1.0 MB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-source-plugin/3.3.0/maven-source-plugin-3.3.0.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-source-plugin/3.3.0/maven-source-plugin-3.3.0.pom> (6.7 kB at 337 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-source-plugin/3.3.0/maven-source-plugin-3.3.0.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-source-plugin/3.3.0/maven-source-plugin-3.3.0.jar> (32 kB at 703 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-war-plugin/3.4.0/maven-war-plugin-3.4.0.pom>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-war-plugin/3.4.0/maven-war-plugin-3.4.0.pom> (8.4 kB at 399 kB/s)
Downloading from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-war-plugin/3.4.0/maven-war-plugin-3.4.0.jar>
Downloaded from central: <https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-war-plugin/3.4.0/maven-war-plugin-3.4.0.jar> (83 kB at 2.5 MB/s)
[INFO]
[INFO] -----------------------< com.example:myproject >------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot:3.2.1:run (default-cli) > test-compile @ myproject >>>
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/main/resources
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/main/resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ myproject ---
[INFO] Changes detected - recompiling the module! :input tree
[INFO] Compiling 1 source file with javac [debug release 17] to target/classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ myproject ---
[INFO] skip non existing resourceDirectory /Users/sato/proj/learn/java/spring/getstartedapp/src/test/resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ myproject ---
[INFO] No sources to compile
[INFO]
[INFO] <<< spring-boot:3.2.1:run (default-cli) < test-compile @ myproject <<<
[INFO]
[INFO]
[INFO] --- spring-boot:3.2.1:run (default-cli) @ myproject ---
[INFO] Attaching agents: []
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.1)
2024-01-22T16:57:40.989+09:00 INFO 2507 --- [ main] com.example.MyApplication : Starting MyApplication using Java 21.0.1 with PID 2507 (/Users/sato/proj/learn/java/spring/getstartedapp/target/classes started by sato in /Users/sato/proj/learn/java/spring/getstartedapp)
2024-01-22T16:57:40.992+09:00 INFO 2507 --- [ main] com.example.MyApplication : No active profile set, falling back to 1 default profile: "default"
2024-01-22T16:57:41.662+09:00 INFO 2507 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-01-22T16:57:41.677+09:00 INFO 2507 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-01-22T16:57:41.677+09:00 INFO 2507 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2024-01-22T16:57:41.773+09:00 INFO 2507 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-01-22T16:57:41.774+09:00 INFO 2507 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 737 ms
2024-01-22T16:57:42.043+09:00 INFO 2507 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-01-22T16:57:42.050+09:00 INFO 2507 --- [ main] com.example.MyApplication : Started MyApplication in 1.356 seconds (process running for 1.668)
2024-01-22T16:57:55.471+09:00 INFO 2507 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-01-22T16:57:55.471+09:00 INFO 2507 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-01-22T16:57:55.473+09:00 INFO 2507 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
^C[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 30.231 s
[INFO] Finished at: 2024-01-22T16:58:06+09:00
[INFO] ------------------------------------------------------------------------
sato@[16:58:06]:~/proj/learn/java/spring/getstartedapp%
実行まで、やっと辿り着いた。
]]>久しぶりにJavaでサンプルを作ってみようかなと思ってみたのだが、どのサンプルを見てもspring bootやmavenやgradleが利用されており、どれも慣れていないので、まずはmavenでも覚えようかな。そのための手始めとして、簡単なpom.xmlを理解しつつ、簡単なjavaプログラムでコンパイル&配布パッケージの作成までやってみた。
以下の情報を参考にしつつ、私が理解できていない箇所は適宜調べて記載した。
目的はmavenを理解することなので、Javaプログラムは小さいもので済ませる。
package hello;
public class HelloWorld {
public static void main(String[] args) {
Greeter greeter = new Greeter();
System.out.println(greeter.sayHello());
}
}
package hello;
public class Greeter {
public String sayHello() {
return "Hello, World!";
}
}
pom.xmlファイルとは、mavenのプロジェクト定義ファイルのことで、プログラムの名前、バージョン、依存関係などを定義するファイル。プロジェクトのルートフォルダーに保存する。
以下は簡単なpom.xmlファイルで、1つだけシェーディング用のプラグインを追加し、外部ライブラリは使用していないので依存関係は含まれていない。(外部ライブラリの定義やリポジトリについてはSpring Bootをやり始める時に改めて確認する)
調査しつつ、xmlではなくjsonにして欲しかったと思った。。
<?xml version="1.0" encoding="UTF-8"?>
<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>">
<!-- POM自体のモデルバージョン(固定) -->
<modelVersion>4.0.0</modelVersion>
<!-- グループや組織を表す文字列、通常は逆ドメイン名を使用、パッケージ名とは異なる -->
<groupId>com.juncleit</groupId>
<!-- 生成ファイルのファイル名 -->
<artifactId>simplemaven</artifactId>
<!-- 生成ファイル拡張子 "jar" or "war" -->
<packaging>jar</packaging>
<!-- 生成ファイルのバージョン -->
<version>0.1.0</version>
<!-- javaコンパイラーに指定する言語仕様とランタイムのバージョン -->
<properties>
<!-- ソースコードで使用している言語バージョン -->
<maven.compiler.source>1.8</maven.compiler.source>
<!-- 生成ファイルの互換ランタイムバージョン -->
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- ビルド中に利用されるプラグインの定義領域 -->
<build>
<plugins>
<plugin>
<!-- 使用するプラグインのgroupId, artifactId, versionは必須要素 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<!-- shadeプラグインを実行するフェーズの指定-->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<!-- shadeプラグインの設定 -->
<configuration>
<transformers>
<!-- shadeプラグインの実行クラス=Manifestの書き換え -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- ManifestファイルのMain-Classをhello.HelloWorldクラスに置き換え -->
<mainClass>hello.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
mavenのプラグインとは、ライフサイクルもしくはphaseで実行できる処理(=goal)が定義されたmojoのこと。1つのプラグインには複数の処理(goal)が定義されている。
以下、参考にした情報。
buildライフサイクルの特定フェーズにおいて、カスタム処理をmojoプラグインで実行するためのタグ。executionsには複数のexecutionを含めることができ、各executionには1つのmojoを含めることができる。executionには、実行対象のフェーズを示すphaseタグを指定できる。
1つのmojoは複数のexecutionで指定することができるので、複数のフェーズで異なるプロパティを指定したmojoを実行することができる。
サンプルpom.xmlの例では、ManifestResourceTranfomerというシェーディングを行うmojoが指定されており、packageフェーズでMANIFESTファイルのMain-Classを定義する。
Maven plain Old Java Objectの略。各mojoというのは実行可能なgoalを定義しており、mavenプラグインは1つ以上のmojoで構成されている。
mavenのbuild ライフサイクルは、いくつかのフェーズで構成されており、以下の順番にフェーズを実行する。
mavenでbuildライフサイクルを実行する時に実行対象のフェーズを指定できる。例えば、packageフェーズを指定すると、compileからpackageまでのフェーズを順番に実行する。(通常デフォルトのフェーズはpackage)
以下、参考にした情報。
buildライフサイクル、cleanライフサイクルを構成する一つのタスク。各phaseは一連のgoalで構成されている。
以下、参考にした情報。
各phaseを構成するプラグインの処理。1つのphaseを実行するとき、そのphaseにバインドされているプラグインのgoalが順番に実行される。
例えば、compile phaseにはデフォルトでmaven-compilerプラグインが指定されており、maven-compilerプラグインには「compile」という処理(=goal)が含まれている。
以下、compile phaseに含まれるプラグインとgoalの確認結果。
> mvn help:describe -Dcmd=compile
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< org.springframework:gs-maven >--------------------
[INFO] Building gs-maven 0.1.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- help:3.4.0:describe (default-cli) @ gs-maven ---
[INFO] **'compile' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile**
上記を要約すると、compileというphaseには、compilerというmavenプラグインのcompileというgoalが定義されており、compileフェーズの実行=mavenプラグインのcompileが実行されると解釈する。
以下、参考にした情報。
依存関係のある外部ライブラリを1つのjarファイルにまとめて、以下のように単純なコマンドで実行できるようにしたjarファイルのことをu-ber.jarと呼ぶ。
> java -jar target.jar
例えば、spring-bootでRESTfulなWEBサービス用のjarファイルを作成した場合、jarファイルにはSpring-Boot関連ライブラリ、Tomcat、Jackson、その他多くの外部ライブラリが含まれ、上記コマンドを実行することでTomcatが起動し、ブラウザからのリクエストに応答できるWEBアプリケーションが起動する。作成したjarファイルを配布する場合、利用者は依存関係のある全ての外部ライブラリを自分で準備することなく、簡単に利用することができるようになる。
以下、参考にした情報。
最もシンプルなmavenのpom.xmlファイルと言いつつも、上記で定義したファイルにはManifestファイルのShade pluginが使用されているが、これは先に説明したu-ber.jarを作成する場合にいくつかの考慮が必要な場合に便利なプラグイン。自分のプロジェクトファイルの他に多くの外部ライブラリを1つのjarファイルにマージすると多くの箇所でコンフリクトが発生する可能性がある。これを回避するための処理をshadeもしくはシェーディングという。
shade pluginにはいくつかのTransformerというクラスが定義されており、例えば、パッケージ名が衝突するようなライブラリを利用している場合はパッケージ名をリネームし、参照箇所も一緒に書き換える処理などが定義されている。
Shade pluginに含まれる各Transformerの用途が以下に定義されている。
最もシンプルなpom.xmlファイルで使用されているManifestResourceTransformerというクラスは、MANIFESTファイルのエントリーを設定するための処理が定義されている。
以下、参考にした情報。
mvnコマンドを実行した後、ManifestResourceTranformerによって置き換えられた(シェーディングされた)結果を確認してみる。
上記のpom.xmlでは、package phaseでシェーディング処理が実行される。シェーディングされると、targetディレクトリには”original”という接頭辞が含まれるファイルが生成されているが、これがシェーディングされる前のファイルで、”ogirinal”という接頭辞がないファイルがシェーディングされた後のファイル。
% ls
classes/ maven-archiver/
generated-sources/ maven-status/
gs-maven-0.1.0.jar original-gs-maven-0.1.0.jar
それぞれのjarファイルの内容を確認すると、MANIFEST.MFファイルだけサイズが異なっている。(表示されるファイルの順番が異なる理由はわからない)
% tar -tvf gs-maven-0.1.0.jar
-rw-rw-r-- 0 0 0 111 1 1 11:52 META-INF/MANIFEST.MF
drwxrwxr-x 0 0 0 0 1 1 11:52 META-INF/
drwxrwxr-x 0 0 0 0 1 1 11:52 hello/
-rw-rw-r-- 0 0 0 648 1 1 11:52 hello/HelloWorld.class
-rw-rw-r-- 0 0 0 370 1 1 11:52 hello/Greeter.class
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/org.springframework/
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/org.springframework/gs-maven/
-rw-rw-r-- 0 0 0 1177 12 31 23:00 META-INF/maven/org.springframework/gs-maven/pom.xml
-rw-rw-r-- 0 0 0 62 1 1 11:52 META-INF/maven/org.springframework/gs-maven/pom.properties
% tar -tvf original-gs-maven-0.1.0.jar
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/
-rw-r--r-- 0 0 0 81 1 1 11:52 META-INF/MANIFEST.MF
drwxr-xr-x 0 0 0 0 1 1 11:52 hello/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/org.springframework/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/org.springframework/gs-maven/
-rw-r--r-- 0 0 0 648 1 1 11:52 hello/HelloWorld.class
-rw-r--r-- 0 0 0 370 1 1 11:52 hello/Greeter.class
-rw-r--r-- 0 0 0 1177 12 31 23:00 META-INF/maven/org.springframework/gs-maven/pom.xml
-rw-r--r-- 0 0 0 62 1 1 11:52 META-INF/maven/org.springframework/gs-maven/pom.properties
それぞれのMANIFEST.MFファイルを確認すると、シェーディングされたjarファイルのMANIFEST.MFには「Main-Class: hello.HelloWorld」というメインクラスが指定されている。
# gs-maven-0.1.0.jarのMANIFEST.MFファイルの内容
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
Main-Class: hello.HelloWorld
# original-gs-maven-0.1.0.jarのMANIFEST.MFファイルの内容
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
MANIFEST.MFにメインクラスの指定があると、javaコマンドでプログラムを実行する際に、メインクラスを指定する必要がなく、「java -jar *.jar」という単純なコマンドでプログラムを起動できる。
]]>古いUSBメモリに10年以上前に作りかけだったプログラムを発見したので興味本位で再開しました。
元々ビルドツールとしてAntを使っていたのですが、後継としてGradleが良さそうだと思い、Libraryサンプルを元に学習しています。
そのサンプルのプログラムで使用されていた”import static”というステートメントが分からなかったので使い方や方針をまとめてみました。
ご自身のコーディング方針と比較しつつ読んでいただければ、新たな知見やアイデアが得られるかもしれませんし、もしあなたがJava言語勉強中であれば、言語習得のアプローチ等、何かしらの参考になるかもしれませんので是非ご一読ください。
開発を再開したシステムで使っている自作ライブラリパッケージのビルドツールをAntからGradleにしたかったので、Gradleのドキュメントを参考にライブラリプロジェクトのサンプルを学習ました。その際に非常に簡単なサンプルプログラムで定義されていた”import static”がわかりませんでした。
この構文の意味と使い方を学習する中で、疑問に思ったことや使い方の方針をまとめてみました。
“import static”はJava1.5で追加されたステートメントで、クラスに定義されたpublic staticな変数およびメソッドを他のクラスからクラス名修飾なしで参照できるようにします。
これにより、ソース内で変数やメソッドを複数回参照する必要がある場合に、毎回クラス名修飾もしくはFQDNを記述する必要がなくなります。
上記内容だけに注目すると単なるユーティリティ的な機能に感じますが、その背景や効果的な使い方を調べると、ある有名なアンチパターンを回避しつつ、かつ効率的にコーディングができるようにするために利用できるということがわかりました。
昔のOracleのドキュメントには、そもそもinterfaceに対して定数を定義してしまうという「定数インタフェース」というアンチパターンへの対応として利用できることが記載されています。
※最新のドキュメントで同様の記載のあるページが見つけられませんでした。
このOracleドキュメントにも記載されいている「Effective Java」という書籍でもアンチパターンへの代替案としてこのimport staticが活用できるという事で紹介されています。
私が持っている書籍は第2版ですが、第3版が出版されているのでリンクを掲載しておきます。
Effective Java 第3版【電子書籍】[ ジョシュア・ブロック ]
※楽天ブックスへのリンクです。
私は”import static”の潜在的なデメリットも感じたので、この記事の後半で紹介します。
私が学習したGradleのサンプルでは、単体テストのソース内でAssertionsクラスに定義されている多くのpublic staticメソッドを参照するために使っていました。
後で気づきましたが、この使い方は「テストを実行する」という関心事の領域において「多くのテスト関連メソッドを参照する必要がある」という状況でimport staticを使っており、非常に理想的な使い方だと思います。
import static を理解するために私が書いた最初のコードです。
これは後でダサい使い方だと気が付きましたが、import staticというものを理解するためには十分機能したソースでした。
import static java.lang.System.out; // publicなスタティック変数
import static java.util.Arrays.sort; // publiciなスタティックメソッド
class HelloWorld {
static void main(string[] args) {
String[] strs = {"c", "a", "b"};
List<String> strList1 = Arrays.asList(strs);
strList1.forEach(str -> out.println(str)); // c, a, b
sort(strs);
List<String> strList2 = Arrays.asList(strs);
strList2.forEach(str -> out.println(str)); // a, b, c
}
}
いくつか特徴がわかったので以下にまとめてみました。
複数のクラスに定義されている同名メソッドを参照する場合、メソッドシグネチャーが異なっていれば自動的にオーバーロードしてくれるので、引数に一致するメソッドが自動的に実行されます。
複数のクラスに定義されている同名メソッドで、かつ同じシグネチャーのメソッドを参照している場合、コンパイル時にエラーとなります。
複数のクラスに定義されている同名変数を参照している場合、コンパイル時にエラーとなります。
Enumメンバーもpublic staticなメンバーなのでimport static 対象にできます。この時の特徴は変数と同じです。
Javaソースコードの特定スコープ内において、”優先度の高い変数と同名の優先度の低い変数”がある場合、優先度の低い変数は対象スコープ内では参照されない変数となるので注意が必要です。
これはJavaの言語仕様であり、「シャドウイング」といい、import static で参照される変数にも適用されます。
クラス変数と同じ名前のメソッド引数やメソッド内ローカル変数を定義した場合、優先されるのはメソッド引数やメソッド内ローカル変数となり、import staticした変数は参照されません。
例えば、先のサンプルプログラムにおいてjava.lang.Systemクラスのout変数を参照してましたが、メソッドローカルにも同名の変数を定義した場合、優先度が高いのはローカル変数なので、以下のプログラムでは標準出力されず、ファイルへ出力されます。
import static java.lang.System.out; // publicなスタティック変数
import static java.util.Arrays.sort; // publiciなスタティックメソッド
class HelloWorld {
static void main(string[] args) {
String[] strs = {"c", "a", "b"};
PrintStream out = new PrintStream("sample.txt"); // ファイル出力ストリームとして生成
List<String> strList1 = Arrays.asList(strs);
strList1.forEach(str -> out.println(str)); // c, a, b
sort(strs);
List<String> strList2 = Arrays.asList(strs);
strList2.forEach(str -> out.println(str)); // a, b, c
out.close();
}
}
import static は、コード記述量が削減できるというメリットはありますが、昨今のIDEやエディタには自動補完機能があるため、私は殆どメリットを感じませんでした。
もし使用する場合は、使い所や使用時のルールを慎重に検討した方がよいと思いました。
理由は、私の経験上、複数のクラスに対して同名の変数名やメソッド名を定義することはあるので、名前が被る可能性は結構あると思います。その際にコンパイルエラーに対処したり、シャドウイングが原因となるバグに対処したりといった余計な作業が発生しそうです。
名前が一意に解決できない場合はコンパイルエラーとなるし、そもそも使いすぎるとソース可読性が落ちるのではと懸念を抱いてしまいました。
例えば、以下3つの変数に対しクラス名修飾がない変数記述が特定スコープ内に混在する場合、開発者がどの実体を参照しているのかぱっと見で分からなくなりそうで、エンジニアの不注意で発見しづらいコーディングミスによるバグを引き起こしそうです。
私が現役エンジニアだった時、コーディング中はできるだけ思考範囲が局所化できるコーディングを心かけてました。
そのスコープからできるだけ複雑さを取り除くことが高品質/高スピード開発につながるという考えです。
import staticはその逆で、使い方を間違えると複雑さを持ち込む1つの要因になりそうです。
コーディング中は変数やメソッドの参照元を意識するのは必須なのですが、シャドウイングにより意図しない変数が参照されたりとか、メソッドの引数を間違えることにより意図しないクラスのメソッドをコールしてしまうとか、効率が落ちそうな気がしてなりません。
変数やメソッドを定義しているクラス名を付与するだけで、直感的にかつ確実に自分の意図した通りのコーディングだと確信を持てると思いますし、コーディングが面倒ならIDEのコード補完使えば良いと感じてしまいます。
もちろん便利な用途もあると思いましたので、その案を提示します。
Gradleのサンプルにもありましたが、例えばテストするためのユーティリティ的なメソッドや、決まりきった定数というのはあるわけで、それをまとめたユーティリティクラスとしてまとめてimport staticで参照するというのは非常に効率的な使い方だと思いました。
要するに、特定のドメインに関する共通な情報やロジックをクラスに定義して”import static”するということです。
例えば、Webサーバーの機能を提供するユーティリティとして、環境情報へのアクセス処理がまとまっているServletContextクラスだったり、テスト実行のユーティリティあれば今回Gradleのサンプルに記載のあったJunitのAssertionsクラスなどです。
これらはとてもわかりやすいし直感的であり、コーディングの効率化および情報とロジックの局所化という面で非常によい用途かと思います。
私は自分のコード内でimport staticを利用する場合、packageである程度の領域を区切り、その中で何をimport staticするか方針を決めて使用するのが良いと思います。
[Udemy] 一週間で身につくJava言語
※Udemyへのリンクです。
Effective Java 第3版【電子書籍】[ ジョシュア・ブロック ]
※楽天ブックスへのリンクです。
スタックトレースの表示方法はググればすぐに出てくるかなと思ったのですが、意外にも日本語・英語どちらもバチっと解決できる情報にすぐに辿り着かなかったので、今回はこれの対処方法をご紹介します。
”利用可能なデバッガーがありません。’variables’を送信できません”というメッセージが表示され、スタックトレースが表示されない事象です。
日本語の情報から検索してもなかなか解決できる情報に行きつかず、ためにしにVSCodeを英語モードで実行し、英語のメッセージを表示させてググってみました。
ようやくいくつかそれっぽい情報が検索できましたが、有効な情報は2つで、かつ明確に欲しかった情報として1つありました。
まず最初にヒットした情報をご紹介します。
VSCodeの環境設定ファイルである”launch.json”で統合ターミナルを使用する方法が記載されていました。
ここで紹介されている設定を行うと、デバッグコンソールではなく、ターミナルウィンドウにエラーの詳細情報が表示されます。
launch.jsonファイルを作成し、”configurations”要素に”console”を追加し、”integratedTerminal”を指定します。
この状態でデバッグを実行すると、ターミナルウィンドウに例外発生時の情報と関連するスタックトレースが表示されます。
これでも確かにスタックトレースが表示されて、やりたいこと(どこで発生したエラーなのか、どのルートで発生したのかを知る)は達成できるのですが、デバッグコンソール上の表示は変わらずで、何か釈然としなかったのでもう少し調査したところ、Microsoftのサイトでとても良いTipsが紹介されていました。
VSCodeにはこの設定ができるオプションが用意されており、MicrosoftのNode.jsのビギナーシリーズで紹介されていました。
初期の状態ではこのオプションは選択できないので、デバッグ実行前に少し事前準備が必要なので、そこからご紹介します。
プログラムをまっさらの状態から書いて、初めてデバッグ実行アイコンをクリックしたとき、以下のような画面になると思います。
このとき、手っ取り早いのは、「create a launch.json file」をクリックして、ファイルを自動作成します。
「Select environment」で”Node.js”を選択します。
以下のファイルが自動的に生成されますが、これはそのままでよく、「BREAKPOINTS」という領域の「Uncaught Exceptions」にチェックを入れます。
この状態でデバッグを実行すると、例外が発生した箇所でデバッガーがプログラムを停止させ、発生した例外のスタックトレースもとてもわかりやすく表示されます。
デバッガーは終了せず実行中の状態なので、このままデバッグを開始することができるというのがなんとも素晴らしい。
そもそもこのメッセージが表示される理由はなんなのかというと、このメッセージが表示されたということは、デバッガー上で実行したプログラムが終了し(正常終了・異常終了にかかわらず)、デバッガーも終了した状態なので、VSCodeが連携できるデバッガーのプロセスを見つけることができなかったことを示しているだけです。
よって、このメッセージから読み取れるのは、VSCodeが「おい、デバッガー君がいねーよ」と文句言っているだけですので、あまり気にしなくても良いというのが私の理解です。
ログメッセージというのは、開発者が作成しているため、往々にして利用者には分かりづらいメッセージになる場合があります。 その理由の1つは、アプリケーションというのは複数のプログラムから構成されており、それぞれ異なる開発者が作成しているということも関係していると思われます。 各プログラムは他のプログラムと密接に連携して動作しており、あるプログラムが他のプログラムに対して期待する連携ができなかった場合、メッセージを出力する責務があるのは要求元のプログラムです。 なぜプログラムが正常に機能しなかったのかをメッセージとして出すためには、「要求先のプログラムから期待した結果が返ってこなかったので正しく動作しなかった」というメッセージを出すのが、一番合理的な場合があります。 連携先プログラムから期待した結果が返ってこなかったという事実だけがあり、その理由や原因がわからないため、このようなメッセージしか出しようがないというのが正直なところでしょう。 SIerの世界では、このようなメッセージの内容もアプリケーションの品質として捉えられ、分かりづらいメッセージ=「品質が低い」と評価されることがあるため、メッセージ自体にもかなり気を使ってシステム開発をおこなっていますが、万人にわかりやすいメッセージを考えるというのは本当に難しいですね。。
Node.jsを勉強しつつ、開発ツールについても少しずつ慣れていこうとしている中で、さまざまな問題にぶつかっています。その都度今回のように疑問に思ったことはできるだけ自分が納得できる回答が得られるまで調べるようにしています。
調べればそれだけ自分の血肉となり、今後プログラムを作成するときにもより効率的に作ることができるようになると思うので、皆さんもそこは是非突っ込んで調べるようにすることをお薦めします。