概要
- Google App Engine Java 8 スタンダード環境 + Spring Boot の構成でシンプルな Hello World 表示 Web アプリケーションを作る
環境
- Google App Engine Java 8 スタンダード環境
- Spring Boot 2.2.0
- Gradle 6.0
- JUnit 5
- Thymeleaf 3
ソースコード
ソースコード一覧
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── helloworld
│ │ ├── HelloworldApplication.java
│ │ ├── HelloworldController.java
│ │ ├── HelloworldErrorController.java
│ │ └── HelloworldServletInitializer.java
│ ├── resources
│ │ ├── application.yml
│ │ ├── static
│ │ │ └── assets
│ │ │ └── helloworld.png
│ │ └── templates
│ │ ├── error.html
│ │ └── helloworld.html
│ └── webapp
│ └── WEB-INF
│ └── appengine-web.xml
└── test
└── java
└── com
└── example
└── helloworld
└── HelloworldApplicationTests.java
build.gradle
Gradle でビルドに関する処理を記述するファイル。
Google App Engine Gradle plugin はバージョン2系を使用。
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Spring Boot Gradle Plugin を使用
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.2.0.RELEASE'
// Google App Engine Gradle plugin を使用
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.2.0'
}
}
plugins {
// Java プラグインを導入
id 'java'
// War プラグインを導入
id 'war'
// https://plugins.gradle.org/plugin/org.springframework.boot
id 'org.springframework.boot' version '2.2.0.RELEASE'
// https://plugins.gradle.org/plugin/io.spring.dependency-management
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
}
// App Engine プラグインを導入
apply plugin: 'com.google.cloud.tools.appengine'
repositories {
mavenCentral()
}
dependencies {
// App Engine API の最新版
implementation 'com.google.appengine:appengine-api-1.0-sdk:+'
// Thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// Spring Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// 組み込み Tomcat はデプロイの際には使わない
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
// Test
testImplementation('org.springframework.boot:spring-boot-starter-test') {
// JUnit 4 のサポートを除外する
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
// JUnit 5 のサポートを有効にする
useJUnitPlatform()
testLogging {
// テスト時の標準出力と標準エラー出力を表示する
showStandardStreams true
// イベントを出力する (TestLogEvent)
events 'started', 'skipped', 'passed', 'failed'
}
}
// Web アプリケーションのグループIDとバージョン
group = "com.example.helloworld"
version = "0.0.1"
// Java 8 を使用
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
// Google App Engine タスクの設定
appengine {
// デプロイ時の設定
// GCLOUD_CONFIG を指定しておくと
// gcloud config で設定しているプロジェクト情報がセットされる
deploy {
// デプロイ先の Google Cloud Project ID
projectId = "GCLOUD_CONFIG"
// デプロイによって反映される Web アプリのバージョン
// 指定しなければ新しく生成される
version = "GCLOUD_CONFIG"
}
}
// デプロイ前にテストを実行
appengineDeploy.dependsOn test
appengineStage.dependsOn test
参考:
- GitHub - GoogleCloudPlatform/app-gradle-plugin: Gradle plugin to build and deploy Google App Engine applications.
- app-gradle-plugin/USER_GUIDE.md at master · GoogleCloudPlatform/app-gradle-plugin · GitHub
- Gradle と App Engine プラグインを使用する | Java 8 の App Engine スタンダード環境 | Google Cloud
- App Engine Gradle プラグインのタスクとプロパティ | Java 8 の App Engine スタンダード環境 | Google Cloud
- Test - Gradle DSL Version 6.0
- TestLogEvent (Gradle API 6.0)
settings.gradle
setting.gradle を設置しないと親ディレクトリを辿って setting.gradle を探しにいってしまうのでシングルプロジェクトでも置いておく。
rootProject.name = 'helloworld'
HelloworldApplication.java
アプリケーションクラス。Spring Boot を使うための定型的な処理だけを書いている。
package com.example.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloworldApplication.class, args);
}
}
HelloworldController.java
コントローラークラス。
package com.example.helloworld;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HelloworldController {
/**
* application.yml から取得したメッセージ。
*/
@Value("${application.message}")
private String applicationYamlMessage;
/**
* トップページのレスポンスを返す。
*
* @return ページ表示情報
*/
@GetMapping("/")
public ModelAndView index() {
System.out.println("HelloworldController#index");
// システム・プロパティから取得
String systemPropertyMessage = System.getProperty("com.example.helloworld.message");
// 表示するデータをセット
ModelAndView mav = new ModelAndView();
mav.addObject("systemPropertyMessage", systemPropertyMessage);
mav.addObject("applicationYamlMessage", applicationYamlMessage);
mav.setViewName("helloworld"); // ビュー名。Thymeleaf テンプレートファイルを指定
return mav;
}
/**
* エラーページを表示するテスト用メソッド。
*/
@GetMapping("/exception/")
public void exception() {
System.out.println("HelloworldController#exception");
throw new RuntimeException("This is a sample exception.");
}
}
HelloworldErrorController.java
Web アプリケーション全体のエラーコントローラークラス。
一般的なコントローラクラスでは捕捉できない Not Found などを処理している。
ここでは最低限の処理しか書いていないが、必要に応じてカスタマイズする。
package com.example.helloworld;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* Web アプリケーション全体のエラーコントローラー。
* ErrorController インターフェースの実装クラス。
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}") // エラーページへのマッピング
public class HelloworldErrorController implements ErrorController {
/**
* エラーページのパス。
*/
@Value("${server.error.path:${error.path:/error}}")
private String errorPath;
/**
* エラーページのパスを返す。
*
* @return エラーページのパス
*/
@Override
public String getErrorPath() {
return errorPath;
}
/**
* レスポンス用の ModelAndView オブジェクトを返す。
*
* @param req リクエスト情報
* @param mav レスポンス情報
* @return HTML レスポンス用の ModelAndView オブジェクト
*/
@RequestMapping
public ModelAndView error(HttpServletRequest req, ModelAndView mav) {
System.out.println("HelloWorldErrorController#error");
// どのエラーでも 404 Not Found にする
// 必要に応じてステータコードや出力内容をカスタマイズ可能
HttpStatus status = HttpStatus.NOT_FOUND;
mav.setStatus(status);
mav.setViewName("error"); // error.html
return mav;
}
}
HelloworldServletInitializer.java
WAR ファイルをデプロイして動作させる環境で必要な WebApplicationInitializer 実装クラス。
Google App Engine では WAR ファイルをデプロイして動作させるためこのクラスが必要になる。
package com.example.helloworld;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class HelloworldServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.out.println("HelloworldServletInitializer#configure");
return application.sources(HelloworldApplication.class);
}
}
参考: SpringBootServletInitializer (Spring Boot Docs 2.2.0.RELEASE API)
application.yml
Web アプリケーション設定情報を記述するファイル。application.properties でも良い。
今回は、アプリケーション独自の情報だけ設定している。
application:
message: Hello, application yaml.
helloworld.png
静的ファイル置き場を示すサンプルとして置いている画像。
静的ファイルは src/main/resources/static に置くと、http://hostname/ にマッピングされる。
今回は http://hostname/assets/helloworld.png にアクセスしたときに src/main/resources/static/assets/helloworld.png のファイルを配信するような構成になっている。
error.html
エラーが発生する際に表示する HTML の Thymeleaf テンプレートファイル。
今回は動的な値を埋め込んでいないが、必要に応じてエラーコントローラークラスで値をセットして、テンプレート側で表示するようにカスタマイズすることが可能。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>
helloworld.html
コントローラークラスがセットした値を表示するための HTML の Thymeleaf テンプレートファイル。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello, world.</title>
</head>
<body>
<h1>Hello, world.</h1>
<div th:text="'System Property: ' + ${systemPropertyMessage}"></div>
<div th:text="'application.yml: ' + ${applicationYamlMessage}"></div>
<div><img src="./assets/helloworld.png"></div>
</body>
</html>
appengine-web.xml
Google App Engine 用の設定ファイル。Web アプリケーションの情報を記述する。
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<!-- Java VM -->
<runtime>java8</runtime>
<!-- スレッドセーフ設定 -->
<threadsafe>true</threadsafe>
<!-- インスタンス数のオートスケール設定 -->
<automatic-scaling>
<!-- ここで指定したCPU負荷率を超えたら新しいインスタンスを立ち上げる -->
<target-cpu-utilization>0.95</target-cpu-utilization>
<!-- 最小インスタンス数。0にすると使われていないときはインスタンス数が0になる -->
<min-instances>0</min-instances>
<!-- 最大インスタンス数 -->
<max-instances>1</max-instances>
<!-- 許容する同時リクエスト数 -->
<max-concurrent-requests>80</max-concurrent-requests>
</automatic-scaling>
<!-- 静的ファイル -->
<static-files>
<include path="/assets/**.*"/>
</static-files>
<!-- Google App Engine 上や gradle appengineRun で動かしているときに有効になるシステム・プロパティ -->
<system-properties>
<property name="com.example.helloworld.message" value="Hello, system property."/>
</system-properties>
</appengine-web-app>
参考: appengine-web.xml リファレンス | Java 8 の App Engine スタンダード環境 | Google Cloud
HelloworldApplicationTests.java
最低限のテストクラス。形式的なことだけ記述している。実際には何もテストしない。
package com.example.helloworld;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HelloworldApplicationTests {
@Test
void contextLoads() {
}
}
オペレーション
テストを実行
gradle の test タスクでテストを実行できる。
$ gradle test
gradle appengineRun でローカルにサーバを起動
gradle appengineRun でローカルサーバ http://localhost:8080/ を起動できる。
$ gradle appengineRun
gradle appengineDeploy で Google App Engine にデプロイ
gradle appengineDeploy で Google App Engine にデプロイできる。
$ gradle appengineDeploy
その他
Web アプリケーション全体のエラーハンドリングが適用されない
エラーコントローラーなどで独自のエラー処理が使用されない場合もある。
デプロイ記述子: web.xml | Java 8 の App Engine スタンダード環境 | Google Cloud
注: 現在のところ、一部のエラー条件ではカスタム エラーハンドラを構成できません。具体的には、特定の URL にサーブレット マッピングが定義されていない場合は HTTP 404 レスポンス ページをカスタマイズできません。また、「403 割り当てエラー」のページや、App Engine の内部エラーにより表示される「500 サーバーエラー」のページもカスタマイズできません。
実際に試してみたところ、
gradle appengineRun でローカルに立てたサーバでは 404 Not Found が独自にカスタマイズしたものになってくれなかった。
gradle appengineDeploy で Google App Engine にデプロイしたものは独自にカスタマイズした 404 Not Found が表示された。