Статьи

WAR-файлы и Java-приложения со встроенными серверами

Большинство серверных Java-приложений (например, веб-или сервис-ориентированных) предназначены для работы в контейнере. Традиционный способ упаковки этих приложений для распространения — это объединение их в файл WAR. Это не более чем ZIP-архив со стандартной компоновкой каталогов, содержащий все библиотеки и зависимости уровня приложения, необходимые во время выполнения. Этот формат в основном совместим и может быть развернут на любой серверный контейнер, который вам нравится, Tomcat или Jetty, JBoss или GlassFish и т. Д.

встроенные Java-сервер-300x229- Однако есть еще одна школа мысли, которая полностью переворачивает эту модель с ног на голову. При таком подходе приложения Java упаковываются для выполнения из командной строки, как любое обычное приложение. Вместо того, чтобы развертываться в контейнере, встроенный контейнер развертывается внутри самого приложения!

Это паритет для курса со многими языками. Среда Django для Python включает в себя встроенный сервер для разработки и тестирования, а Ruby on Rails поставляется со встроенным сервером, который также используется в производстве. Эта концепция уже давно существует в Java, и Jetty специализируется на встраиваемой нише. Однако это далеко от нормы, и стандарт де-факто по-прежнему является файлом WAR, который можно развернуть в Tomcat .

Шум растет, хотя. На прошлогодней конференции DevNexus я посетил сессию Джеймса Уорда , который в то время был «евангелистом-разработчиком» в Heroku . Создание собственного контейнера — это рекомендуемый подход для развертывания приложений на облачной платформе Heroku, и Джеймс является большим сторонником .

Его сессия была посвящена платформе Play для Java и Scala, которая встраивает Netty аналогично серверу Rails. В отличие от Grails , в котором для разработки используется сервер в стиле Django, а затем он поставляется в виде файла WAR, Play предназначен для использования своего собственного сервера вплоть до производства. Джеймс защищал этот подход во всех приложениях Java.

Встроенное приключение

Зимует-поиск-переплет-242x300
Я сделал хотя бы глоток Kool-Aid. Когда я начал писать свою книгу, Hibernate Search by Example , я хотел сосредоточиться на Hibernate Search, а не на каких-либо других фреймворках или проблемах с сервером. Поэтому я отказался от Spring и написал пример приложения для книги, используя подход Vanilla Servlet 3.0.

Обычно я использую Eclipse в своей собственной среде разработки и указываю на локальный экземпляр Tomcat для тестирования веб-приложений. Однако я хотел поддержать читателей, которым было удобнее использовать IntelliJ, Netbeans или вообще не использовать IDE. Поэтому я решил, что мой скрипт сборки должен встраивать тестовый сервер, чтобы читатели могли запускать примеры без установки или настройки чего-либо.

Использование встроенного сервера с Maven

Моей первой целью было просто запустить сервер из сценариев сборки Maven, чтобы читателям не пришлось устанавливать сервер или интегрировать его в свою среду IDE. Я видел, как это было сделано раньше, и было просто добавить Jetty Maven Plugin в POM проекта. Читатели должны иметь возможность создать пример приложения и запустить его за один шаг с помощью команды:

1
mvn clean jetty:run

встроенный по-Maven-300x99 Эх, не так быстро. Предполагается, что вы сможете вносить изменения в статический контент и видеть, как эти изменения немедленно вступают в силу во время работы сервера. Однако я столкнулся с ошибками из-за блокировки моих файлов. Потратив некоторое время на исследования, я обнаружил, что настройки Jetty по умолчанию не подходят для блокировки файлов Windows. Это можно исправить, переключив одно свойство в одном файле конфигурации .

Однако вам нужно взломать файл Jetty JAR, чтобы получить правильную копию этого файла конфигурации. Во-первых, вам нужно покопаться в вашем локальном репозитории Maven и выяснить, какой JAR-файл взломать (оказывается, это jetty-webapp, а не jetty-server). После того, как вы получите копию файла webdefault.xml и переключите параметр useFileMappedBuffer, вам нужно сохранить свою копию где-нибудь в вашем проекте и обновить POM Maven, чтобы он смотрел там, а не внутри Jetty JAR:

01
02
03
04
05
06
07
08
09
10
<plugin>
   <groupId>org.mortbay.jetty</groupId>
   <artifactId>jetty-maven-plugin</artifactId>
   <version>8.1.7.v20120910</version>
   <configuration>
      <webAppConfig>
         <defaultsDescriptor>${basedir}/src/main/webapp/WEB-INF/webdefault.xml</defaultsDescriptor>
      </webAppConfig>
   </configuration>
</plugin>

Хорошо … немного больше хлопот, чем я ожидал, но я могу с этим справиться.

Использование встроенного сервера с другими системами сборки

Я знаю, что многие Java-разработчики ненавидят Maven. Поэтому я хотел предоставить версию примера приложения моей книги, созданного с использованием Ant, чтобы проиллюстрировать, как можно адаптировать концепции по умолчанию. Итак, какую строку мне добавить в build.xml, чтобы Ant использовал Jetty?

муравей Эх, не так быстро. Есть интеграция с Ant для Jetty , но она еще более громоздка, чем Maven. Даже если вы используете систему управления зависимостями, такую ​​как Ivy, ваш Ant-скрипт не сможет загрузить и управлять встроенным сервером за вас. Вместо этого вы должны загрузить полностью автономный сервер Jetty и вручную скопировать его в свой проект. Кто не хочет, чтобы 6 мегабайт исполняемых файлов были переданы в систему контроля версий?

После того, как вы скопируете JAR-файлы Jetty-сервера, вам нужно вручную добавить еще один JAR-файл для интеграции с Ant. К моему удивлению, я обнаружил, что самой последней поддерживаемой версией была Jetty 7, реализующая спецификацию Servlet 2.5, которой почти восемь лет .

Я вижу, что они наконец добавили Jetty 8 в прошлом месяце, но это не помогло мне, когда я писал книгу прошлой осенью. Мне пришлось переписать эту версию моего примера приложения для Servlet 2.5 вместо 3.0, и я начал задаваться вопросом, действительно ли это того стоило.

Использование встроенного сервера из кода Matrix-Code-150x150

В последней главе моей книги рассказывается о приложениях Hibernate Search, работающих в среде кластерного сервера. Плагин Maven предназначен только для одного экземпляра, поэтому я решил написать небольшой класс начальной загрузки, который бы программно запускал два экземпляра Jetty на разных портах. Структурируя этот класс как тест JUnit, я мог бы заставить Maven запускать его автоматически следующим образом:

1
mvn clean compile war:exploded test

Эх, не так быстро. Сервлеты, слушатели и службы RESTful моего приложения не регистрировались при запуске. После гораздо больше времени, потраченного на исследования, я обнаружил, что в Jetty доступны разные «разновидности» , с функциями Servlet 3.0 (такими как аннотации), включенными или отключенными по умолчанию.

Честно говоря, я до сих пор не совсем понимаю, как определить разницу между «высоким» и «не высоким». Все, что я могу вам сказать, это то, что мне пришлось добавить этот кусок кода в мой класс начальной загрузки, чтобы аннотации работали:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
...
masterContext.setConfigurations(
new Configuration[] {
new WebInfConfiguration(),
new WebXmlConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration(),
new PlusConfiguration(),
new AnnotationConfiguration(),
new JettyWebXmlConfiguration(),
new TagLibConfiguration()
}
);
...

Намного проще и понятнее, чем сбросить файл WAR в папку Tomcat / webapps, верно?

Использование встроенного сервера из консоли и облака

Закончив книгу, я хотел, чтобы демо-версия примера кода была отправлена ​​на GitHub и развернута на Heroku . Теоретически, Heroku может запустить любое приложение, которое вы можете запустить локально из командной строки. Если Heroku обнаружит POM Maven, он запустит пакет mvn clean и выполнит любую команду запуска, которую вы поместили в скрипт с именем Procfile.

терминал-иконка-150x150 Мой программный лаунчер Jetty работал нормально в контексте запуска Maven. Однако Maven управлял моими зависимостями classpath во время тестирования, и теперь мне нужна Jetty без этой помощи. Рекомендуемый подход Heroku, используемый в их демонстрационных Java-приложениях , заключается в комплектации вашего приложения версией Tomcat с одним файлом. Круто, я все равно больше знаком с Tomcat!

Эх, не так быстро. Если ваше приложение ожидает, что соединения с базой данных (или что-то еще) будут зарегистрированы как ресурсы JNDI, то вы сами по себе. Связанный с Heroku Tomcat Runner не поддерживает настройку JNDI. Хм … может быть, именно поэтому демонстрация ванильного сервлета Heroku на самом деле ничего не делает, и почему единственное демонстрационное приложение, которое делает что-то, — это Spring-based. Теперь, когда я думаю об этом, Джеймс Уорд покинул Heroku, чтобы работать в TypeSafe в прошлом году, и Heroku не сделал ни одного обновления для своего сайта Java с тех пор, как ушел. Глоток.

облака-150x150
Не беспокойтесь, потому что есть похожий однофайловый Jetty Runner , и он позволяет передавать параметры JNDI в качестве параметров командной строки. Кроме того, мы потратили много времени на решение всех проблем со встроенной Jetty!

Эх, все еще слишком быстро. Если вы используете теги JSTL в своих представлениях JSP (т.е. вы живете в 21-м веке), то Jetty Runner — это беспорядок, который ставит вас в ад пути к классам. При запуске из командной строки вам нужно передать параметры в Java для:

  1. JAR-файл Jetty Runner
  2. Файл WAR вашего веб-приложения (*)
  3. Разобранная версия вашего WAR-файла, созданного во время сборки Maven

(*) Вы правильно прочитали. После всего этого встроенного кошмара Heroku все еще использует файл WAR !!!

Мой профиль Heroku в итоге выглядел так:

1
web: java $JAVA_OPTS -jar target/dependency/jetty-runner-8.1.7.v20120910.jar --lib target/hibernate-search-demo-0.0.1-SNAPSHOT/WEB-INF/lib --port $PORT --jdbc org.apache.commons.dbcp.BasicDataSource "url=jdbc:h2:mem:vaporware;DB_CLOSE_DELAY=-1" "jdbc/vaporwareDB" target/*.war

Здесь работает более одного загрузчика классов, и это позволяет Jetty Runner загружать материал JSTL / taglib из его пути к классам, а не к пути к классам веб-приложения.

Вывод

яблочный пирог-150x150 Нет ничего плохого в концепции встроенного сервера, когда он изначально встроен в среду. Написание приложений Play доставляет удовольствие, и их развертывание на Heroku практически тривиально. На своей повседневной работе я использую коммерческий пакет на основе Spring под названием hybris , чья обширная система сборки включает сервер Tomcat в ваше приложение. Пока вам не нужно слишком сильно настраивать сценарии сборки, это прекрасно работает.

клейкая лента младенец-150x150 С другой стороны, концепция слишком хрупкая и хрупкая для широкого использования общего назначения. Привязка встроенного сервера к обычному Java-приложению — это просто боль. Возможно, вы сможете цепляться за безопасность чужого рабочего примера, но в тот момент, когда ваше приложение делает что-то другое, вы сами можете устранить поломку. Возьмите мое встроенное приключение выше и сопоставьте его с «хлопотами» использования Tomcat:

  1. Загрузите Tomcat и разархивируйте его куда-нибудь
  2. Перенесите файл WAR в подкаталог Tomcat / webapps
  3. Запустить Tomcat

Единственным реальным преимуществом, которое я получил, была возможность запустить демонстрацию на Heroku. Однако поддержка Java от облачных провайдеров улучшается с каждым днем. Jelastic позволяет вам развернуть обычные WAR-файлы в Tomcat 7 или GlassFish 3 прямо сейчас. AppFog поддерживает развертывание в Tomcat 6 , а поддержка Tomcat 7 появится в ближайшее время. Я подозреваю, что в недалеком будущем идея модификации ваших приложений для облачного развертывания будет воспринята как анахронизм.

Таким образом, в двух словах, это зависит от используемой вами платформы. Если встроенные серверы запекаются, они могут быть очень крутыми. Если они приклеены к воздуховоду, они могут быть ужасными. Если бы сегодня я писал Hibernate Search by Example , сценарии построения примера приложения производили бы две вещи: файл WAR и ссылку для загрузки Tomcat.