java - App Engine Java 11 无法在实时服务器上找到或加载主类
问题描述
tl;dr:为什么这可以在本地工作,但在我部署到我的实时 App Engine 项目时却不行?
我正在尝试使用 Java 11 版本的 App Engine 创建基于准系统 servlet 的网络应用程序。我正在按照本指南将一些项目从 Java 8 更新到 Java 11 。我也在使用这个指南和这个例子。我的目标是使用 Jetty 运行一个非常简单的网络应用程序,该应用程序在 App Engine 中提供一个静态 HTML 文件和一个 servlet 文件。
当我在本地运行时,我的网络应用程序运行良好:
mvn clean install
mvn exec:java -Dexec.args="target/app-engine-hello-world-1.war"
当我运行这些命令时,我index.html
和我的 servlet URL 都可以正常工作。
但是当我部署到我的实时站点时:
mvn package appengine:deploy
...命令成功,但是当我导航到我的实时 URL 时,HTML 文件和 servlet URL 都出现此错误:"Error: Server Error. The server encountered an error and could not complete your request. Please try again in 30 seconds."
如果我查看 Cloud 控制台中的日志,我会看到此错误:
Error: Could not find or load main class io.happycoding.Main
Caused by: java.lang.ClassNotFoundException: io.happycoding.Main
我的设置有些问题,但我没有发现任何明显错误。
以下是我项目中的文件:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.happycoding</groupId>
<artifactId>app-engine-hello-world</artifactId>
<version>1</version>
<packaging>war</packaging>
<properties>
<!-- App Engine currently supports Java 11 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>io.happycoding.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>2.2.0</version>
<configuration>
<projectId>happy-coding-gcloud</projectId>
<version>1</version>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/appengine/app.yaml
runtime: java11
entrypoint: 'java -cp "*" io.happycoding.Main app-engine-hello-world-1.war'
src/main/java/io/happycoding/Main.java
package io.happycoding;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.webapp.Configuration.ClassList;
import org.eclipse.jetty.webapp.WebAppContext;
import io.happycoding.servlets.HelloWorldServlet;
/** Simple Jetty Main that can execute a WAR file when passed as an argument. */
public class Main {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: need a relative path to the war file to execute");
System.exit(1);
}
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
Server server = new Server(8080);
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar(args[0]);
ClassList classlist = ClassList.setServerDefault(server);
// Enable Annotation Scanning.
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
server.setHandler(webapp);
server.join();
}
}
src/main/webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Google Cloud Hello World</title>
</head>
<body>
<h1>Google Cloud Hello World</h1>
<p>This is a sample HTML file. Click <a href="/hello">here</a> to see content served from a servlet.</p>
<p>Learn more at <a href="https://happycoding.io">HappyCoding.io</a>.</p>
</body>
</html>
src/main/java/io/happycoding/servlets/HelloWorldServlet.java
package io.happycoding.servlets;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;");
response.getOutputStream().println("<h1>Hello world!</h1>");
}
}
我猜我设置实时站点的类路径的方式有些不对劲,但我没有看到任何明显错误的地方。
将packaging
属性pom.xml
设置为war
,我得到一个.war
包含以下内容的文件:
index.html
META-INF/MANIFEST.MF
META-INF/maven/io.happycoding/app-engine-hello-world/pom.properties
META-INF/maven/io.happycoding/app-engine-hello-world/pom.xml
WEB-INF/classes/io/happycoding/Main.class
WEB-INF/classes/io/happycoding/servlets/HelloWorldServlet.class
WEB-INF/classes/lib/asm-7.3.1.jar
WEB-INF/classes/lib/asm-analysis-7.3.1.jar
WEB-INF/classes/lib/asm-commons-7.3.1.jar
WEB-INF/classes/lib/asm-tree-7.3.1.jar
WEB-INF/classes/lib/javax.annotation-api-1.3.jar
WEB-INF/classes/lib/javax.servlet-api-4.0.1.jar
WEB-INF/classes/lib/jetty-annotations-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-http-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-io-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-jndi-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-plus-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-security-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-server-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-servlet-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-util-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-webapp-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-xml-9.4.31.v20200723.jar
如果我将packaging
属性更改pom.xml
为jar
,那么我会得到一个.jar
包含以下内容的文件:
io/happycoding/Main.class
io/happycoding/servlets/HelloWorldServlet.class
META-INF/MANIFEST.MF
META-INF/maven/io.happycoding/app-engine-hello-world/pom.properties
META-INF/maven/io.happycoding/app-engine-hello-world/pom.xml
而我在实时站点的日志中收到此错误:
Error: Unable to initialize main class io.happycoding.Main
Caused by: java.lang.NoClassDefFoundError: org/eclipse/jetty/server/Handler
这感觉像是进步,但是我的实时服务器中也出现了 404 错误,所以我感觉很卡。
我需要对上述设置进行哪些更改才能使其在本地和我的实时服务器上工作?
编辑:我可以在 App Engine 调试器中看到以下文件:
我尝试将此添加到我的pom.xml
文件中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/appengine-staging
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
然后我在 App Engine 调试器中看到这些文件:
但我仍然得到同样的错误。
我相信这个问题是由于我的Main
类在一个.war
对类路径没有影响的文件中引起的,这就是找不到它的原因。
如何打包我的项目以便它在本地和我的实时服务器上工作?
解决方案
我认为您的问题是您将Main
课程包括在战争本身中,而 App Engine 无法找到它。
正如您在GCP 迁移指南中所见,Main
该类是在名为simple-jetty-main
.
随着执行,maven-dependency-plugin
此依赖项被复制到appengine-staging
目录中,使其可以从 Java 类路径访问。
这就是为什么在Main
从以下位置执行命令时可以在指南中提出的示例中找到该类的原因app.yaml
entrypoint
:
entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main helloworld.war'
因此,解决方案是将您的Main
类包含在另一个库中,独立于您需要部署的 war 文件。
也许您可以创建一个库 - 就像 Google 所做的那样simple-jetty-main
- 可以在您的 GCP 项目中重用此任务。
只是为了测试,为了确认这一点,您可以使用simple-jetty-main
库本身(您可以从https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java11/appengine -简单的码头主要)。安装它,将依赖项包含在您的 中pom.xml
,还包含,并按如下方式maven-dependency-plugin
定义您的:entrypoint
entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main app-engine-hello-world-1.war'
对于您的评论,您将不希望将Main
类与其余代码分开。
为了满足这个要求,我们必须首先更改Main
类,以便 Jetty 可以服务HelloWorldSevlet
和静态内容。该代码实际上与您提供的代码非常相似。请原谅设置的简单性,它是基于web.xml
文件的;如有必要,可以进行进一步的开发以处理注释或任何认为合适的内容:
package io.happycoding;
import java.net.URL;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class Main {
public static final String WEBAPP_RESOURCES_LOCATION = "META-INF/resources";
public static void main(String[] args) throws Exception {
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
Server server = new Server(8080);
URL webAppDir = Thread.currentThread().getContextClassLoader().getResource(WEBAPP_RESOURCES_LOCATION);
if (webAppDir == null) {
throw new RuntimeException(String.format("Unable to find %s directory into the JAR file", WEBAPP_RESOURCES_LOCATION));
}
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setDescriptor(WEBAPP_RESOURCES_LOCATION + "/WEB-INF/web.xml");
webAppContext.setResourceBase(webAppDir.toURI().toString());
webAppContext.setParentLoaderPriority(true);
server.setHandler(webAppContext);
server.start();
server.join();
}
}
静态资源可以从您选择的目录中加载(它将在 中参数化pom.xml
)。
例如,我创建了src/main/webapp
用于存储静态内容的文件夹。
在此文件夹中,您还需要定义 - 在这种情况下,由于我们设置 Jetty 的方式 - 一个WEB-INF
包含此web.xml
文件的目录:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>io.happycoding.servlets.HelloWorldServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
这是tree
我的源代码设置:
该pom.xml
文件与您提供的文件非常相似。我只包括将maven-resources-plugin
Web 应用程序静态内容复制到 jar 文件,以及maven-shade-plugin
生成 UberJar:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.happycoding</groupId>
<artifactId>app-engine-hello-world</artifactId>
<version>1</version>
<packaging>jar</packaging>
<properties>
<!-- App Engine currently supports Java 11 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
<!-- Directory where static content resides -->
<webapp.dir>./src/main/webapp</webapp.dir>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>io.happycoding.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-web-resources</id>
<phase>compile</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/META-INF/resources</outputDirectory>
<resources>
<resource>
<directory>${webapp.dir}</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.happycoding.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>2.2.0</version>
<configuration>
<projectId>happy-coding-gcloud</projectId>
<version>1</version>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用此设置,您可以通过执行以下命令在本地运行应用程序:
mvn exec:java
您还可以直接从 java 工具在本地运行程序:
java -jar appengine-deploy-sample-1.jar
抱歉,我无法在 GCP 中测试设置,但我认为,根据迁移指南,您可以尝试部署应用程序而不entrypoint
在app.yaml
.
如果它不起作用,您可以尝试通过配置entrypoint
类似于以下内容来运行该应用程序:
entrypoint: 'java -jar appengine-deploy-sample-1.jar'
或者可能:
entrypoint: 'java -cp "*" -jar appengine-deploy-sample-1.jar'
推荐阅读
- ios - 具有演示样式 formSheet 的模态 UIViewController 在 iPhone XS Max 和 iPhone XR 上无法正确显示
- appium-ios - 错误:连接 ECONNREFUSED 127.0.0.1:8100
- google-cloud-storage - 如何将 Power BI 服务连接到谷歌云存储(Bucket)
- reactjs - 在初始化 BrowserRouter 的组件中获取 location 属性
- c# - 当按钮获得焦点时,WPF阻止执行命令
- linux - 重定向到 dev/null
- c# - 将类型传递给表达式函数输出
- linux - 哪个线程监听特定的 UDP 端口
- python - 扩展 pandas 数据框以包含“缺失”周
- c++ - 使用动态分配将 char* 复制到另一个 char**