Google App Engine Java 8向Java 11的迁移 – 实践篇
首先
这篇文章是关于从Google App Engine Java 8迁移到Java 11的对策的延续。
它详细描述了如何将Google App Engine / Java(GAE/J)中不支持Servlet的Java 11替换为可以在GAE/J Java 11上运行的Spring Boot应用程序的具体方法。
从Java8的Servlet转移到Java11的Spring Boot。
我們將以GAE/J Java8的Hello World Servlet作為範例,將其轉換為Spring Boot應用並部署到GAE/J Java11上。
前提条件 tí
这篇文章的主要目的是将Java 8版的GAE/J代码重写为Java 11版,因此假设已经完成了Java 11和Cloud SDK的安装。
此外,假设已经创建了可以部署到GAE的项目。
同时,假设使用了Spring Boot作为Web框架,并具有Spring Framework和Spring Boot的知识。
假设的环境
-
- Java 11 (OpenJDK 11)
-
- Apache Maven 3.6
-
- Google Cloud SDK 319
-
- Spring Boot 2.4
- Maven 3.6
获取GAE/J Java8的Hello World
GAE/J Java8示例可以从以下的Git存储库获取。
git clone https://github.com/GoogleCloudPlatform/appengine-try-java
获取到的文件如下所示。
|--pom.xml
|--src
| |--main
| | |--java
| | | |--myapp
| | | | |--DemoServlet.java
| | |--webapp
| | | |--index.html
| | | |--WEB-INF
| | | | |--appengine-web.xml
| | | | |--web.xml
让我们看一下web.xml文件的内容。
<!DOCTYPE web-app PUBLIC
"-//Oracle Corporation//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>demo</servlet-name>
<servlet-class>myapp.DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
只有一个名为DemoServlet的servlet和一个名为index.html的HTML文件。
package myapp;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.getWriter().println("{ \"name\": \"World\" }");
}
}
<!doctype html>
<html>
<head>
<title>App Engine Demo</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<div id="result">Loading...</div>
<script>
$(document).ready(function() {
$.getJSON('/demo', function(data) {
$('#result').html("Hello, " + data.name);
});
});
</script>
</body>
</html>
在index.html中调用DemoServlet,并接收名为{“name”:”World”}的JSON,将其显示在Hello World的DIV中。
最后,检查appengine-web.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<threadsafe>true</threadsafe>
<runtime>java8</runtime>
</appengine-web-app>
在文中提到了该运行时(Runtime)是Java8。
另外,可通过以下Git获取使用GAE/J的Java11版Spring Boot创建的Hello World示例。其中的appengine-java11/springboot-helloworld几乎是成品,如果您想快速查看项目框架,建议参考此处。
git clone https://github.com/GoogleCloudPlatform/java-docs-samples
将GAE/J Java11 和 Spring Boot进行整合和优化。
首先,让我们调整文件结构使得Maven的编译通过。
修改了pom.xml。
暂时将Maven的依赖关系和构建插件替换成适用于GAE/J和Spring Boot的版本。
当然,根据您自己的应用程序需要添加必要的依赖关系,需要随时进行添加。
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<version>2.0</version>
<groupId>com.google.appengine.demos</groupId>
<artifactId>appengine-try-java</artifactId>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<skip>true</skip>
<version>2.0.0</version>
</configuration>
</plugin>
</plugins>
</build>
</project>
<插件>com.google.cloud.tools</插件>中的<版本>2.0.0</版本>部分表示了部署到GAE的版本。如果将这个版本设置为与Java8版本不同的值,那么就可以保留在旧版本的GAE中,以备不时之需进行回退。
在这里,只要mvn compile一下,应该就可以下载所依赖的Jar文件。
mvn compile
创建app.yaml
在GAE Java11中,appengine-web.xml已被废弃,配置被统一到yaml文件中,因此需要创建app.yaml文件。
文件保存的路径如下:
src/main/appengine/app.yaml
最简单的app.yaml文件描述如下。只需要指定Runtime为java11,其他部分可以使用默认值运行。
runtime: java11
所有的 app.yaml 设置都可以在 app.yaml 配置文件中找到。
创建端点
最后创建一个作为Spring Boot端点的类。
在DemoServlet.java的同一个包中创建一个新的类DemoApp。
package myapp;
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;
@SpringBootApplication
@RestController
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}
骨架完成
暫時來說,它沒有內容,但是外觀已經整齊了。
我們不需要DemoServlet.java和WEB-INF以下的文件,所以讓我們刪除它們。
結果應該會形成下面的文件夾結構。
此外,如果你通過Maven進行webapp以下的文件建構,它們將被Spring Boot作為靜態文件處理,所以你可以將它們保留不變。
|--pom.xml
|--src
| |--main
| | |--appengine
| | | |--app.yaml
| | |--java
| | | |--myapp
| | | | |--DemoApp.java
| | |--webapp
| | | |--index.html
这样的话,应该能够将这个作为 Spring Boot 应用,并通过 Maven 进行编译。
mvn install
Servlet转换成/迁移到
现在开始正式阶段。终于要将Servlet类改写为Spring Boot兼容的类了。
更改DemoServlet
如果在DemoApp类中添加以下方法,可以创建一个对/demoget请求返回{“name”:”World”}的JSON字符串的端点。
一目了然地,@GetMapping注解表示该方法是Get请求的端点,参数”/demo”部分是URL映射。
这样就完成了。
@GetMapping("/demo")
public String hello() {
return "{ \"name\": \"World\" }";
}
但是,如果要更加符合Spring Boot的风格,就应该把返回值设为Bean。如果返回Bean,则框架会自动生成JSON字符串的响应。考虑到这些因素,最终DemoApp.java的完整源代码如下所示。
package myapp;
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;
@SpringBootApplication
@RestController
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
@GetMapping("/demo")
public Hello hello() {
Hello hello = new Hello();
hello.name = "World";
return hello;
}
public class Hello {
public String name;
}
}
启动测试服务器
为了进行测试,只需要执行下面的命令。
在浏览器中打开 http://localhost:8080,应该会显示”Hello, World”。
mvn spring-boot:run
部署
最后进行部署。此部分与Java8版本无异。
由于Maven插件已经加载了用于执行部署的Gcloud目标,因此只需执行以下命令,应该可以将应用部署到GAE服务器上。
mvn appengine:deploy
最终
最后一个Maven命令应该可以在GAE上进行部署。
最后,列举更实用的参考信息。
Servlet映射
这次的重点是将Servlet全面改写为Spring Boot格式,但也有一种方法是直接从Spring Boot中调用Servlet,而不需要修改Servlet类。希望您可以参考Spring Boot Servlet映射等文章来了解这种方法。
特别是在Java8阶段,如果已经使用@WebServlet注解而不是Web.xml来进行映射,那么只需要在端点类上附加@ServletComponentScan注解,迁移工作就完成了。
请求参数/主体
这份示例虽然是一个不需要请求参数或body信息的servlet,但实际上,肯定会有参数等需要接收的情况。
在Spring Boot(具体来说是Spring MVC的功能)中,可以参考Spring MVC控制器参数等文章来了解如何接收请求参数和body信息。