В этом сообщении мы используем JHades для устранения проблем в нашем пути к классам. JHades — это мощный инструмент, который дает нам полезную информацию при попытке решить определенный набор проблем в разработке Java.
Демонстрация этого поста основана на следующем: генерация кода RAML .
Это не имеет ничего общего с RAML как таковым. Я просто буду использовать демонстрацию, представленную в этом посте, и продолжу ее (обратите внимание, что «результирующее» состояние проекта каждого поста находится в отдельной ветке).
Итак, прежде чем мы сможем добраться до сути вещей, нам нужно решить проблему.
Идите вперед и попробуйте git clone https://github.com/ricston-git/blog-contacts-app && cd blog-contacts-app && git checkout генерация raml-кода (это демонстрация позади предыдущего поста). Теперь перейдите в контакт-приложение и установите mvn. Перейдите в contacts-app-impl и mvn exec: java -Dexec.mainClass = «com.ricston.contacts.Main». Если ваша переменная JAVA_HOME env указывает на установку Java 6, вы получите: Unsupported major.minor version 51.0. Это потому, что я обновил Jersey до 2.13 — поэтому вы захотите указать его в домашнем каталоге Java 7. Затем вы должны увидеть, что сервер прослушивает порт 4433 — это означает, что наш сервер работает, и мы можем отправлять ему запросы, как определено в нашем файле RAML.
Так что … без проблем — приложение работает отлично. Это потому, что вы используете фиксированную версию
Для этого вам нужно открыть файл pom.xml contacts-app-api и удалить область, указанную в зависимости от raml-jaxrs-codegen-core. Это (и несколько других ненужных вещей) было состоянием проекта, когда я начал работать над демо-кодом за постом в блоге, который я собирался написать изначально (затем я закончил писать этот). На самом деле произошло то, что я смог продолжить работу, не сталкиваясь с этой проблемой в течение достаточно долгого времени, потому что я запускал программу из своей среды IDE. Я установил еще один модуль Maven с военным пакетом, перенес конфигурацию из Main.java в дескриптор развертывания web.xml и запустил все без проблем в моей IDE. Только когда я попытался запустить проект из Maven с помощью плагина Tomcat — mvn clean tomcat7: run, я столкнулся со следующим исключением.Это исключение будет тем же, которое вы получите в этот момент, если вы установите все заново и запустите mvn, как только что сделали (только на этот раз у вас должна быть область компиляции по умолчанию для зависимости raml-jaxrs-codegen-core):
java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:297) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.NoSuchMethodError: javax.ws.rs.core.Application.getProperties()Ljava/util/Map; at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:304) at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:285) at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:311) at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:170) at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:358) at javax.servlet.GenericServlet.init(GenericServlet.java:244) at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:532) at org.eclipse.jetty.servlet.ServletHolder.doStart(ServletHolder.java:344) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:64) at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:791) at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:265) at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:717) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:64) at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:95) at org.eclipse.jetty.server.Server.doStart(Server.java:282) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:64) at com.ricston.contacts.Main.main(Main.java:35) ... 6 more
Когда вы получаете такое поведение; программа работает нормально, когда выполняется из вашей IDE (например), но выдает исключение при выполнении в другой среде (в данном случае от Maven), у вас есть веские основания полагать, что у вас есть проблема с Jar-адом. Под «такого рода исключениями» я подразумеваю исключение, которое намекает на то, что в вашем classpath может быть несколько jar-файлов, имеющих одинаковое имя класса в одном и том же пакете, но с другой реализацией. Исключения при использовании класса и отсутствие ошибок такого метода — обычные признаки ада Джара. Опять же, сами по себе это не так, но когда вы получаете такие вещи при работе в определенной среде, но не в другой, то это довольно хороший признак того, что у вас возникают проблемы с адом в Jar.
Хорошо, теперь у нас проблемы. Очевидно, мы не можем просто полагаться на случайность, надеясь, что правильный класс загружен в среде, в которой мы развертываем. Технически, если бы у меня был пакет mvn в модуле Maven, упомянутый выше, я бы получил свой файл war и развернул его в своей локальной установке Tomcat, в которой — как это происходит — загружается нужный класс, а это исключение не ‘ поднимите свою уродливую голову. Но это не решает проблему.
Здесь нам действительно нужно больше информации. Было бы замечательно, если бы NoSuchMethodError также давал нам местоположение jar-файла, из которого загружался класс javax.ws.rs.core.Application при сбое. Мы не получаем этого, но мы можем использовать инструмент под названием JHades, чтобы сообщить нам, если у нас есть повторяющиеся классы в банках в нашем classpath. Эта информация практически решит эту проблему.
Идите дальше и добавьте jhades (не jhades-standalone-report) в pom contacts-app-impl — используйте последнюю версию здесь :
<dependency> <groupId>org.jhades</groupId> <artifactId>jhades</artifactId> <version>1.0.4</version> </dependency>
new JHades().overlappingJarsReport();
При установке и запуске, как и раньше, вы должны получить список перекрывающихся jar-файлов в выводе консоли вместе с предыдущим сообщением об ошибке. Важной частью является следующее:
file:/Users/justin/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar overlaps with file:/Users/justin/.m2/repository/javax/ws/rs/javax.ws.rs-api/2.0/javax.ws.rs-api-2.0.jar - total overlapping classes: 55 - same classloader ! This is an ERROR!
Из сообщения об ошибке мы знаем, что проблемным классом является javax.ws.rs.core.Application, поэтому имеет смысл сосредоточиться на столкновении с участием jsr311-api-1.1.1.jar и javax.ws.rs.api-2.0. баночка. Конечно же, если мы откроем класс javax.ws.rs.core.Application в обоих этих jar-файлах, мы увидим, что класс jsr311-api-1.1.1.jar не имеет метода getProperties (), который наш Код пытается использовать. Тот, который сделал injavax.ws.rs.api-2.0.jar. Обратите внимание: если у вас нет источников в вашем локальном репо, вы сможете получить их, запустив mvn dependency: sources в каталоге родительского модуля contacts-app.
Наконец, мы почти в корне проблемы здесь. Последняя часть головоломки — выяснить, что вытягивает jsr311-api-1.1.1.jar и удаляет его. Так как мы фактически начали с решения, вы уже знаете, что зависимость contacts-app-api от raml-jaxrs-codegen-core вызывает jsr311-api-1.1.1.jar — но, если вы этого не сделали, как бы вы поступили об этом?
То, что я сделал, просто вывел вывод mvn dependency: tree во временный файл: mvn dependency: tree >> dependency-tree и поищем jsr311 (или, может быть, grep для него с некоторым контекстом, например mvn dependency: tree | grep -C 20 jsr311, если вы не хотите создавать файл). В любом случае, мы получаем необходимую нам информацию:
[INFO] +- com.ricston:contacts-app-api:jar:1.0.0-SNAPSHOT:compile [INFO] | \- org.raml:raml-jaxrs-codegen-core:jar:1.0.OA-SNAPSHOT:compile [INFO] | +- org.raml:raml-parser:jar:0.8.7:compile [INFO] | | +- org.yaml:snakeyaml:jar:1.13:compile [INFO] | | +- commons-validator:commons-validator:jar:1.3.1:compile [INFO] | | | +- commons-beanutils:commons-beanutils:jar:1.7.0:compile [INFO] | | | +- commons-digester:commons-digester:jar:1.6:compile [INFO] | | | | +- commons-collections:commons-collections:jar:2.1:compile [INFO] | | | | \- xml-apis:xml-apis:jar:1.0.b2:compile [INFO] | | | \- commons-logging:commons-logging:jar:1.0.4:compile [INFO] | | \- org.kitchen-eel:json-schema-validator:jar:1.2.2:compile [INFO] | | \- org.mozilla:rhino:jar:1.7R4:compile [INFO] | +- javax.ws.rs:jsr311-api:jar:1.1.1:compile
Теперь я хотел бы быстро продемонстрировать, как использовать JHades в других сценариях. Давайте повторим проблему, которую мы имеем здесь, в контексте модуля War Maven, где у нас нет такой роскоши, как просто добавить некоторый код для распечатки перекрывающихся jar-файлов.
Итак, давайте продолжим и добавим еще один модуль Maven — «contacts-app-server». Теперь структура нашего проекта выглядит примерно так:
── contacts-app │ └── pom.xml │ ├── contacts-app-server │ ├── pom.xml │ ├── src ├── contacts-app-api │ ├── pom.xml │ └── src └── contacts-app-impl └── src
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>contacts-app-server</display-name> <!-- REST API servlet: --> <servlet> <servlet-name>restService</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>com.ricston.contacts.app.ContactsApp</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>restService</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> </web-app>
Если вы попробуете запустить contacts-app-server с помощью mvn clean tomcat7: run, вы получите эту ошибку:
java.lang.ClassCastException: org.glassfish.jersey.servlet.ServletContainer cannot be cast to javax.servlet.Servlet
На самом деле это ошибка, которая не имеет ничего общего с тем, что нас интересует. Я не совсем уверен, почему, но причины Jetty, которые мы имеем в contacts-app-impl, вызывают это. Я могу открыть ServletContainer в своей среде IDE и увидеть, что расширяемый HttpServlet извлекается из банки с координатами Maven: org.eclipse.jetty.orbit: javax.servlet: 3.0.0.v201112011016 — (выглядит как один сумасшедший номер версии).
В любом случае, мы не будем использовать Main.java для размещения нашего веб-приложения, поэтому удалите его вместе с зависимостями Jetty в pom.xml contacts-app-impl. Тогда вы сможете снова запустить tomcat из maven и получить NoSuchMethodError.
Чтобы получить необходимую нам информацию, у нас есть два варианта (по крайней мере, я знаю 2 варианта). Мы можем либо опустить зависимость от JHades, как мы делали раньше, и добавить следующее в наш web.xml (в начале):
<listener> <listener-class> org.jhades.JHadesServletListener</listener-class> </listener>
Кроме того, вы можете получить jardes-standalone-report jar (либо загрузите его вручную, либо полагайтесь на него, и позвольте Maven установить его в вашем локальном репо). Это позволяет нам запустить следующую команду на выходе нашего проекта (файл war):
java -jar /Users/justin/.m2/repository/org/jhades/jhades-standalone-report/1.0.4/jhades-standalone-report-1.0.4.jar ./target/contacts-app-server-1.0. 0-SNAPSHOT.war
Примечание: при использовании mvn tomcat7: run, похоже, удаляет файл war из целевого каталога, поэтому вам придется снова пакет mvn.
В любом случае вы сможете прийти к тому же выводу, что и раньше, с выводом JHades.
В конце концов я перейду к написанию поста в блоге, который я изначально имел в виду (этот вид был просто разработан при попытке чего-то другого). Настройтесь снова в следующий раз для еще одного модуля, который мы добавим к этому проекту.
В следующий раз мы будем настраивать интерфейсный модуль с помощью Yeoman (используя магистральный генератор) — создадим наш проект с Grunt из Maven и будем зависеть от нашего блестящего нового интерфейсного модуля с нашего сервера контактов-app-server.
До скорого