API-вызовы к веб-приложению Heroku, работающему в webapp-runner, в конечном итоге завершаются с ошибкой NoSuchMethodError, а затем NoClassDefFoundError в google.common

Я веду войну на Heroku, используя webapp-runner, Я развертываю приложение, используя heroku-maven-plugin Версия 1.2 с помощью следующей команды: mvn heroku:deploy-war, Первоначально приложение работает, и все конечные точки возвращают правильные ответы. Однако, если я позволю приложению бездействовать достаточно долго, чтобы 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

Похоже, эти проблемы указывают на то, что java-файл guava присутствует во время компиляции, но не присутствует во время выполнения. Тем не менее, я вошел в веб-dyno и убедился, что банка гуавы была включена в мой 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 перемещает / очищает файл java-файла гуавы.

Содержание моего Procfile:

web:    java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT --expand-war target/MyApp.war

Java-процессы, запущенные в моем веб-dyno:

~/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 Аргумент Я также проверил jarfiles в расширенном каталоге, чтобы убедиться в наличии guava. Это:

~/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

Я добавил следующую логику в проблемный веб-сервис для распечатки пути к классам и ресурсов на нем:

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());
    }

В следующий раз, когда произошла ошибка, я проверил журналы и, к своему удивлению, обнаружил, что на пути к классам во время выполнения был фляга гуавы!

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!!!
....

Что здесь происходит? Как мне отладить это?

2 ответа

Решение

После некоторой отладки я обнаружил, что в моей программе есть две разные версии Guava на пути к классам (guava-23.0.jar & guava-jdk5-13.0.jar). Совет по отладке, предложенный здесь, был необходим, но не достаточен для меня, чтобы понять суть этого.

При работе с ClassLoaders важно помнить, что getClassLoader метод, определенный в .class Объект возвращает ссылку на ClassLoader, который первоначально загрузил класс. Чтобы найти дубликат банку, было важно призвать classLoader.getResource("/com/google/common/base/CharMatcher.class") на том же ClassLoader, который загрузил класс, который позже потерпел неудачу с 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>

Вероятно, у вас есть две версии guava или связанных с ней jar-файлов на вашем пути к классам. Смотрите исключение NoSuchMethodError при использовании com.google.common.base.Splitter

Другие вопросы по тегам