API 在 webapp-runner 中对 Heroku web-app 运行 的调用最终因 NoSuchMethodError 而失败,然后在 google.common 中出现 NoClassDefFoundError
API calls to Heroku web-app running in webapp-runner eventually fail with NoSuchMethodError then NoClassDefFoundError in google.common
我正在 运行使用 webapp-runner
在 Heroku 上安装 war。我通过以下命令使用 heroku-maven-plugin
版本 1.2 部署应用程序:mvn heroku:deploy-war
。最初,该应用程序运行正常并且所有端点 return 有效响应。但是,如果我允许应用程序空闲足够长的时间让 Heroku 使其进入睡眠状态,然后调用一个调用番石榴的端点,我会收到 NoSuchMethodError
:
2017-09-23T19:19:45.388865+00:00 app[web.1]: SEVERE: Servlet.service() for servlet [jersey-serlvet] in context with path [] threw exception [org.glassfish.jersey.server.ContainerException: java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;] with root cause
2017-09-23T19:19:45.388866+00:00 app[web.1]: java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;
2017-09-23T19:19:45.388867+00:00 app[web.1]: at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:453)
2017-09-23T19:19:45.388868+00:00 app[web.1]: at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:892)
2017-09-23T19:19:45.388869+00:00 app[web.1]: at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:317)
...application specific stack trace
对同一个 API 的所有后续调用在同一点产生 NoClassDefFoundError
2017-09-23T19:22:24.454901+00:00 app[web.1]: SEVERE: Servlet.service() for servlet [jersey-serlvet] in context with path [] threw exception [org.glassfish.jersey.server.ContainerException: java.lang.NoClassDefFoundError: Could not initialize class com.google.common.io.BaseEncoding] with root cause
2017-09-23T19:22:24.454903+00:00 app[web.1]: java.lang.NoClassDefFoundError: Could not initialize class com.google.common.io.BaseEncoding
...application specific stack trace
这些问题似乎表明 guava jar 在编译时存在,但在 运行 时不存在。但是,我登录到网络测功机并验证番石榴罐已包含在我的 warfile
中
my-mbp:TrickServer me$ heroku ps:exec
Establishing credentials... done
Connecting to web.1 on ⬢ myapp...
~ $ cd target/
~/target $ ls
MyApp.war dependency mvn-dependency-list.log tomcat.52079
~/target $ jar -tf MyApp.war
...lots of dependencies...
WEB-INF/lib/google-oauth-client-1.20.0.jar
WEB-INF/lib/gson-2.2.4.jar
WEB-INF/lib/guava-23.0.jar <---guava
WEB-INF/lib/guava-jdk5-13.0.jar
...lots more dependencies...
我很难解释为什么端点在应用程序部署后立即工作,但稍后会产生这些错误。对我来说,这种行为似乎表明当我的应用程序从睡眠中醒来时,Heroku 可能会提供与最初 运行 或 Heroku moving/cleaning 番石榴 jarfile 时不同的类路径。
我的 Procfile
的内容:
web: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT --expand-war target/MyApp.war
Java 在我的 web dyno 上处理 运行nning:
~/target $ ps -ef | grep java
u30439 4 1 0 18:50 ? 00:00:44 java -Xmx300m -Xss512k -Dfile.encoding=UTF-8 -Duser.timezone=UTC -jar target/dependency/webapp-runner.jar --port 52079 target/MyApp.war
u30439 27 4 0 18:50 ? 00:00:00 bash --login -c java $JAVA_OPTS -jar target/dependency/webapp-runner.jar $WEBAPP_RUNNER_OPTS --port 52079 target/MyApp.war
更新 1
因为我使用 --expand-war
参数调用我的 webapp,所以我还检查了扩展目录中的 jar 文件以验证是否存在番石榴。它是:
~/target/tomcat.55320/webapps/expanded/WEB-INF/lib $ ls
...dependencies...
google-oauth-client-1.20.0.jar
gson-2.2.4.jar
guava-23.0.jar
guava-jdk5-13.0.jar
...more dependencies...
更新 2
我向有问题的 Web 服务添加了以下逻辑以打印出类路径及其上的资源:
logger.info("System Classpath: " + System.getProperty("java.class.path"));
logger.info("Runtime Classes...");
ClassLoader cl = UserService.class.getClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for(URL url: urls){
logger.info(url.getFile());
}
下次错误发生时,我检查了日志,令我惊讶的是 运行time 类路径中出现了 guava jar!
2017-09-24T12:07:40.843438+00:00 app[web.1]: [heroku-exec] ERROR: Could not connect to proxy:
2017-09-24T12:07:40.844145+00:00 app[web.1]: [heroku-exec] ERROR: Too many reconnect attempts. Waiting 30 seconds...
2017-09-24T12:07:52.671620+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.671631+00:00 app[web.1]: INFO: System Classpath: target/dependency/webapp-runner.jar
2017-09-24T12:07:52.671931+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.671932+00:00 app[web.1]: INFO: Runtime Classes...
2017-09-24T12:07:52.672277+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.672279+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/classes/
....
2017-09-24T12:07:52.690304+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.690306+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/lib/google-oauth-client-1.20.0.jar
2017-09-24T12:07:52.690501+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.690503+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/lib/guava-23.0.jar <--- Guava!!!
....
这是怎么回事?我该如何调试?
您的类路径中可能有两个版本的番石榴或其相关的 jar。参见 NoSuchMethodError exception when using com.google.common.base.Splitter
经过一些调试,我发现我的程序在 class 路径上有两个不同版本的 Guava (guava-23.0.jar
& guava-jdk5-13.0.jar
)。调试提示建议 是必要的,但不足以让我深入了解。
使用 ClassLoader 时,重要的是要记住 .class
对象中定义的 getClassLoader
方法 returns 是对最初加载 class 的 ClassLoader 的引用。要找到重复的 jar,至关重要的是在加载 class 的同一个 ClassLoader 上调用 classLoader.getResource("/com/google/common/base/CharMatcher.class")
,后者后来因 NoSuchMethodError
.
而失败
对于后代来说,导致冲突的具体依赖是com.google.api-client
。我通过将以下 exclusion
添加到我的 pom.xml
中的依赖项来解决它
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.22.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
</exclusion>
</exclusions>
</dependency>
我正在 运行使用 webapp-runner
在 Heroku 上安装 war。我通过以下命令使用 heroku-maven-plugin
版本 1.2 部署应用程序:mvn heroku:deploy-war
。最初,该应用程序运行正常并且所有端点 return 有效响应。但是,如果我允许应用程序空闲足够长的时间让 Heroku 使其进入睡眠状态,然后调用一个调用番石榴的端点,我会收到 NoSuchMethodError
:
2017-09-23T19:19:45.388865+00:00 app[web.1]: SEVERE: Servlet.service() for servlet [jersey-serlvet] in context with path [] threw exception [org.glassfish.jersey.server.ContainerException: java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;] with root cause
2017-09-23T19:19:45.388866+00:00 app[web.1]: java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;
2017-09-23T19:19:45.388867+00:00 app[web.1]: at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:453)
2017-09-23T19:19:45.388868+00:00 app[web.1]: at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:892)
2017-09-23T19:19:45.388869+00:00 app[web.1]: at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:317)
...application specific stack trace
对同一个 API 的所有后续调用在同一点产生 NoClassDefFoundError
2017-09-23T19:22:24.454901+00:00 app[web.1]: SEVERE: Servlet.service() for servlet [jersey-serlvet] in context with path [] threw exception [org.glassfish.jersey.server.ContainerException: java.lang.NoClassDefFoundError: Could not initialize class com.google.common.io.BaseEncoding] with root cause
2017-09-23T19:22:24.454903+00:00 app[web.1]: java.lang.NoClassDefFoundError: Could not initialize class com.google.common.io.BaseEncoding
...application specific stack trace
这些问题似乎表明 guava jar 在编译时存在,但在 运行 时不存在。但是,我登录到网络测功机并验证番石榴罐已包含在我的 warfile
my-mbp:TrickServer me$ heroku ps:exec
Establishing credentials... done
Connecting to web.1 on ⬢ myapp...
~ $ cd target/
~/target $ ls
MyApp.war dependency mvn-dependency-list.log tomcat.52079
~/target $ jar -tf MyApp.war
...lots of dependencies...
WEB-INF/lib/google-oauth-client-1.20.0.jar
WEB-INF/lib/gson-2.2.4.jar
WEB-INF/lib/guava-23.0.jar <---guava
WEB-INF/lib/guava-jdk5-13.0.jar
...lots more dependencies...
我很难解释为什么端点在应用程序部署后立即工作,但稍后会产生这些错误。对我来说,这种行为似乎表明当我的应用程序从睡眠中醒来时,Heroku 可能会提供与最初 运行 或 Heroku moving/cleaning 番石榴 jarfile 时不同的类路径。
我的 Procfile
的内容:
web: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT --expand-war target/MyApp.war
Java 在我的 web dyno 上处理 运行nning:
~/target $ ps -ef | grep java
u30439 4 1 0 18:50 ? 00:00:44 java -Xmx300m -Xss512k -Dfile.encoding=UTF-8 -Duser.timezone=UTC -jar target/dependency/webapp-runner.jar --port 52079 target/MyApp.war
u30439 27 4 0 18:50 ? 00:00:00 bash --login -c java $JAVA_OPTS -jar target/dependency/webapp-runner.jar $WEBAPP_RUNNER_OPTS --port 52079 target/MyApp.war
更新 1
因为我使用 --expand-war
参数调用我的 webapp,所以我还检查了扩展目录中的 jar 文件以验证是否存在番石榴。它是:
~/target/tomcat.55320/webapps/expanded/WEB-INF/lib $ ls
...dependencies...
google-oauth-client-1.20.0.jar
gson-2.2.4.jar
guava-23.0.jar
guava-jdk5-13.0.jar
...more dependencies...
更新 2
我向有问题的 Web 服务添加了以下逻辑以打印出类路径及其上的资源:
logger.info("System Classpath: " + System.getProperty("java.class.path"));
logger.info("Runtime Classes...");
ClassLoader cl = UserService.class.getClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for(URL url: urls){
logger.info(url.getFile());
}
下次错误发生时,我检查了日志,令我惊讶的是 运行time 类路径中出现了 guava jar!
2017-09-24T12:07:40.843438+00:00 app[web.1]: [heroku-exec] ERROR: Could not connect to proxy:
2017-09-24T12:07:40.844145+00:00 app[web.1]: [heroku-exec] ERROR: Too many reconnect attempts. Waiting 30 seconds...
2017-09-24T12:07:52.671620+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.671631+00:00 app[web.1]: INFO: System Classpath: target/dependency/webapp-runner.jar
2017-09-24T12:07:52.671931+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.671932+00:00 app[web.1]: INFO: Runtime Classes...
2017-09-24T12:07:52.672277+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.672279+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/classes/
....
2017-09-24T12:07:52.690304+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.690306+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/lib/google-oauth-client-1.20.0.jar
2017-09-24T12:07:52.690501+00:00 app[web.1]: Sep 24, 2017 12:07:52 PM org.myorg.server.web.services.MyService authenticate
2017-09-24T12:07:52.690503+00:00 app[web.1]: INFO: /app/target/tomcat.28304/webapps/expanded/WEB-INF/lib/guava-23.0.jar <--- Guava!!!
....
这是怎么回事?我该如何调试?
您的类路径中可能有两个版本的番石榴或其相关的 jar。参见 NoSuchMethodError exception when using com.google.common.base.Splitter
经过一些调试,我发现我的程序在 class 路径上有两个不同版本的 Guava (guava-23.0.jar
& guava-jdk5-13.0.jar
)。调试提示建议
使用 ClassLoader 时,重要的是要记住 .class
对象中定义的 getClassLoader
方法 returns 是对最初加载 class 的 ClassLoader 的引用。要找到重复的 jar,至关重要的是在加载 class 的同一个 ClassLoader 上调用 classLoader.getResource("/com/google/common/base/CharMatcher.class")
,后者后来因 NoSuchMethodError
.
对于后代来说,导致冲突的具体依赖是com.google.api-client
。我通过将以下 exclusion
添加到我的 pom.xml
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.22.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
</exclusion>
</exclusions>
</dependency>