--Create a simple Hello World display web application with Google App Engine Java 8 standard environment + Spring Boot configuration
--Google App Engine Java 8 standard environment
├── 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
A file that describes the process related to build in Gradle. Google App Engine Gradle plugin uses version 2 series.
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
//Use Spring Boot Gradle Plugin
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.2.0.RELEASE'
//Use Google App Engine Gradle plugin
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.2.0'
}
}
plugins {
//Introduced Java plugin
id 'java'
//Introduced War plugin
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'
}
//Introduced App Engine plugin
apply plugin: 'com.google.cloud.tools.appengine'
repositories {
mavenCentral()
}
dependencies {
//Latest version of 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'
//Embedded Tomcat is not used when deploying
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
// Test
testImplementation('org.springframework.boot:spring-boot-starter-test') {
//Exclude support for JUnit 4
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
//Enable JUnit 5 support
useJUnitPlatform()
testLogging {
//Display standard output and standard error output during testing
showStandardStreams true
//Output event(TestLogEvent)
events 'started', 'skipped', 'passed', 'failed'
}
}
//Web application group ID and version
group = "com.example.helloworld"
version = "0.0.1"
//Uses Java 8
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
//Google App Engine task settings
appengine {
//Deployment settings
// GCLOUD_If you specify CONFIG
//The project information set in gcloud config is set
deploy {
//Deploy to Google Cloud Project ID
projectId = "GCLOUD_CONFIG"
//Web app version reflected by deployment
//If not specified, a new one will be generated
version = "GCLOUD_CONFIG"
}
}
//Run tests before deploy
appengineDeploy.dependsOn test
appengineStage.dependsOn test
reference:
settings.gradle
If you do not set setting.gradle, you will go to the parent directory to find setting.gradle, so put it in a single project as well.
settings.gradle
rootProject.name = 'helloworld'
HelloworldApplication.java
Application class. I'm writing only routine processes for using 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
Controller class.
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.Message obtained from yml.
*/
@Value("${application.message}")
private String applicationYamlMessage;
/**
*Returns the response of the top page.
*
* @return page display information
*/
@GetMapping("/")
public ModelAndView index() {
System.out.println("HelloworldController#index");
//Obtained from system properties
String systemPropertyMessage = System.getProperty("com.example.helloworld.message");
//Set the data to be displayed
ModelAndView mav = new ModelAndView();
mav.addObject("systemPropertyMessage", systemPropertyMessage);
mav.addObject("applicationYamlMessage", applicationYamlMessage);
mav.setViewName("helloworld"); //View name. Specify Thymeleaf template file
return mav;
}
/**
*A test method that displays an error page.
*/
@GetMapping("/exception/")
public void exception() {
System.out.println("HelloworldController#exception");
throw new RuntimeException("This is a sample exception.");
}
}
HelloworldErrorController.java
Error controller class for the entire web application. It handles Not Found etc. that cannot be captured by a general controller class. Only the minimum processing is described here, but customize it as needed.
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;
/**
*Error controller for the entire web application.
*Implementation class of the ErrorController interface.
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}") //Mapping to error page
public class HelloworldErrorController implements ErrorController {
/**
*The path of the error page.
*/
@Value("${server.error.path:${error.path:/error}}")
private String errorPath;
/**
*Returns the path of the error page.
*
* @return Error page path
*/
@Override
public String getErrorPath() {
return errorPath;
}
/**
*Returns a ModelAndView object for the response.
*
* @param req request information
* @param mav response information
* @ModelAndView object for return HTML response
*/
@RequestMapping
public ModelAndView error(HttpServletRequest req, ModelAndView mav) {
System.out.println("HelloWorldErrorController#error");
//404 Not Found for any error
//The stator code and output contents can be customized as needed.
HttpStatus status = HttpStatus.NOT_FOUND;
mav.setStatus(status);
mav.setViewName("error"); // error.html
return mav;
}
}
HelloworldServletInitializer.java
WebApplicationInitializer implementation class required in the environment where the WAR file is deployed and operated. Google App Engine requires this class to deploy and run WAR files.
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);
}
}
Reference: [SpringBootServletInitializer \ (Spring Boot Docs 2 \ .2 \ .0 \ .RELEASE API )](https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/api/org/ springframework / boot / web / servlet / support / SpringBootServletInitializer.html)
application.yml
A file that describes web application configuration information. It can also be application.properties. This time, only the information unique to the application is set.
application:
message: Hello, application yaml.
helloworld.png
An image placed as a sample showing the static file storage. If you put static files in src / main / resources / static, they will be mapped to http: // hostname /. This time, when you access http://hostname/assets/helloworld.png, the file src / main / resources / static / assets / helloworld.png is delivered.
error.html
HTML Thymeleaf template file to display when an error occurs. This time, the dynamic value is not embedded, but it is possible to set the value in the error controller class as needed and customize it to be displayed on the template side.
<!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
An HTML Thymeleaf template file for displaying the values set by the controller class.
<!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
Configuration file for Google App Engine. Describe the information of the Web application.
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<!-- Java VM -->
<runtime>java8</runtime>
<!--Threadsafe settings-->
<threadsafe>true</threadsafe>
<!--Autoscale setting for the number of instances-->
<automatic-scaling>
<!--Launch a new instance when the CPU load factor specified here is exceeded-->
<target-cpu-utilization>0.95</target-cpu-utilization>
<!--Minimum number of instances. If set to 0, the number of instances will be 0 when not in use.-->
<min-instances>0</min-instances>
<!--Maximum number of instances-->
<max-instances>1</max-instances>
<!--Allowed number of simultaneous requests-->
<max-concurrent-requests>80</max-concurrent-requests>
</automatic-scaling>
<!--Static file-->
<static-files>
<include path="/assets/**.*"/>
</static-files>
<!--System properties enabled when running on Google App Engine or with gradle appengineRun-->
<system-properties>
<property name="com.example.helloworld.message" value="Hello, system property."/>
</system-properties>
</appengine-web-app>
reference: appengine-web.xml reference|Java 8 App Engine standard environment| Google Cloud
HelloworldApplicationTests.java
Minimal test class. Only formal things are described. Doesn't actually test anything.
package com.example.helloworld;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HelloworldApplicationTests {
@Test
void contextLoads() {
}
}
You can run the test with gradle's test task.
$ gradle test
You can start the local server http: // localhost: 8080 / with gradle appengineRun.
$ gradle appengineRun
You can deploy to Google App Engine with gradle appengineDeploy.
$ gradle appengineDeploy
In some cases, the error controller does not use its own error handling.
Deployment descriptor: web.xml |Java 8 App Engine standard environment| Google Cloud
Note: Currently, some error conditions do not allow you to configure a custom error handler. Specifically, you cannot customize an HTTP 404 response page if a Servlet mapping is not defined for a particular URL. You also can't customize the "403 Allocation Error" page or the "500 Server Error" page that appears due to an App Engine internal error.
When I actually tried it, 404 Not Found wasn't my own customization on the server I set up locally with gradle appengineRun. The gradle appengineDeploy deployed to Google App Engine displayed my own customized 404 Not Found.
Recommended Posts