LoginSignup
1
2

More than 3 years have passed since last update.

Google App Engine (Java 8) + Spring Boot + Gradle で Hello World

Last updated at Posted at 2019-11-12

概要

  • 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系を使用。

build.gradle
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

参考:

settings.gradle

setting.gradle を設置しないと親ディレクトリを辿って setting.gradle を探しにいってしまうのでシングルプロジェクトでも置いておく。

settings.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 が表示された。

参考資料

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2