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信息。

bannerAds