12 августа 2011 в 00:36

Быстрая разработка веб-приложений на Java tutorial

Как вы разрабатываете веб-приложение на Java?
После каждого изменения, как вы его запускаете и проверяете? Сколько времени занимает редеплой приложения и рестарт контейнера?

Мне довелось видеть разные варианты: от полной пересборки WAR-файла до использования плагинов для IDE типа MyEclipse, WTP и «коннекторов» для сервлет-контерйнеров. У некоторых из них есть явные недостатки, другие вполне работают — но есть способ проще!

Запускалка


Этот способ разработки позволяет максимально просто и гибко настроить приложение с минимальным временем редеплоя. Вам надо всего лишь написать один простенький Java-класс с main-методом, который запустит сервер Jetty сразу с нужными приложениями (т.н. Embedded Mode).



Вот как выглядит запускалка в минимальной комплектации:
import org.mortbay.jetty.*;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppContext;

public class Launcher {
  public static void main(String[] args) throws Exception {
    Server server = new Server();

    Connector connector = new SelectChannelConnector();
    connector.setPort(8080);
    server.addConnector(connector);

    WebAppContext root = new WebAppContext("root/src/main/webapp", "/");
    WebAppContext reports = new WebAppContext("reports/src/main/webapp", "/reports");
    WebAppContext petclinic = new WebAppContext("petclinic/src/main/webapp", "/petclinic");
    server.setHandlers(new Handler[]{root, reports, petclinic});

    server.start();
  }
}


Этот код запускает сервер (сервлет-контейнер), слушающий порт 8080, с тремя веб-приложениями, код для которых берётся прямо из папок проекта (root/src/main/webapp, reports/src/main/webapp и petclinic/src/main/webapp), то есть любые изменения в файлах вступают в силу сразу, без необходимости что-то пересобирать и передеплоить.

Понятное дело, при добавлении новых методов придётся рестартовать, но и в этом случае рестарт происходит максимально быстро, буквально в течение секунды (конечно, если ваши приложения не делают чего-то сложного при запуске). Если к этому ещё и прикрутить JRebel, будет вообще шоколадно.

Вот и вся хитрость.
Наверняка в продакшине вы используете не Jetty, а что-нибудь типа Tomcat, JBoss или WebLogic. Неважно, мы ведь сейчас говорим о разработке, где скорость, стабильность и т.д. неважны, а важна лёгкость настройки, скорость запуска и редеплоя. И тут Embedded Jetty — то, что доктор прописал.

Вы можете просто запускать этот main-класс из своей любимой IDE, отлаживать, тестировать, рестартовать; и не нужны никакие плагины, не нужно искать файлы конфигурации, не нужно копаться в XML'ах. Все настройки под рукой. Вот это жизнь!

Для запускали в минимальной комплектации достаточно всего трёх jar-файлов: servlet-api.jar, jetty.jar, jetty-util.jar.


[UPD] Как подсказывает kblcuk, приведённый код запускалки работает только для версии Jetty 6. Начиная с седьмой версии Jetty, названия пакетов и классов были поменяны с org.mortbay.jetty.* на org.eclipse.jetty.*, так что придётся подправить import.

Тюнинг


Рассмотрим некоторые дополнительные возможности, которые могут пригодиться в зависимости от проекта.

Загрузчики классов


Приведённый выше код загружает классы и зависимости всех веб-приложений в одном загрузчике (ClassLoader). Если по какой-то причине вам важно, чтобы у веб-приложений были разные наборы классов и зависимостей (jar'ов), это тоже можно сделать, дописав немножко кода:

    WebAppContext petclinic = new WebAppContext("petclinic/src/main/webapp", "/petclinic");
    WebAppClassLoader classLoader = new WebAppClassLoader(petclinic);
    classLoader.addClassPath("petclinic-core/target/classes");
    classLoader.addClassPath("petclinic-services/target/classes");
    petclinic.setClassLoader(classLoader);


Я сам активно это использовал, когда мне надо было запускать сразу много веб-приложений, каждое со своими (конфликтующими) зависимостями. Очень просто, очень удобно.

JSP


Старожилы помнят, что в незабвенные времена для формирования HTML часто использовалась такая штука, как JSP. Если в вашем проекте JSP тоже используется, то придётся добавить ещё несколько зависимостей: eclipse-jdtcore.jar, jsp-api-2.1-glassfish.jar, jsp-2.1-glassfish.jar

Конфигурация JDBC-ресурсов



Возможно, есть решение попроще, но вот такой вариант у меня заработал:
import import javax.naming.InitialContext;
import com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean;

static void setupDataSources() { 
    System.setProperty("java.naming.factory.initial", "org.mortbay.naming.InitialContextFactory");
    AtomikosNonXADataSourceBean dataSource = new AtomikosNonXADataSourceBean();
    dataSource.setUniqueResourceName("jdbc/portal");
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
    dataSource.setUser("myusername");
    dataSource.setPassword("mypassword");
    dataSource.setMaxPoolSize(10);

    new InitialContext().createSubcontext("jdbc").bind("portal", dataSource);
}


Для этого вам дополнительно потребуется ещё немножко зависимостей: transactions.jar, jetty-naming.jar, transactions-api.jar, transactions-jta.jar, transactions-jdbc.jar, atomikos-util.jar


Нельзя ли это ещё немножко автоматизировать?


Можно. Но только если нужно.
Если адрес или пароль базы данных у вас часто меняется, возможно, вы захотите считывать их из какого-нибудь файла. Это можно. Но стоит ли? Главное, что все настройки сосредоточены в одном файле, а что это за файл — java-класс или *.properties — не всё ли равно?

Если список веб-приложений постоянно меняется, возможно, имеет смысл написать плагин для Eclipse или IDEA, который бы запускал Jetty со всеми проектами, которые есть в данный момент в Eclipse. Один такой плагин описан здесь — он находит все проекты, в которых есть файл web.xml, а остальные проекты добавляет к ним в classpath.

Минусы


Объективности ради попробую назвать некоторые подводные камни, таящиеся в этом подходе.
  • (Мнимый минус) Контейнер Jetty уступает в производительности Tomcat, JBoss, Resin и др.
    Ерунда. Мы сейчас говорим о разработке — тут производительность совершенно не важна.
    Кстати, это ещё вопрос, уступает ли.

  • (Мнимый минус) Контейнер Jetty нестабилен / иногда грохается / содержит утечки памяти.
    Ерунда. См. предыдущий пункт.

  • Если ваше приложение использует какие-то специфические особенности какого-то конкретного контейнера (особенно это касается WebLogic или Oracle AS), то на Jetty оно работать, естественно, не будет.
    Наверное, тут ничего не поделаешь. Могу только посоветовать не использовать такие возможности, чтобы ваше приложение не зависело от контейнера.

  • Иногда случается, что ваше приложение прекрасно работает на вашем компьютере в режиме Embedded Jetty, но грохается, будучи собранным в war и установленным на другой сервер.
    Это хоть и редко, но случается. В частости, может возникнуть ошибка из-за загрузчиков классов или версий jar'ов.


Альтернативы


Ради интереса я перечислю альтернативные способы разработки веб-приложений на Java со своей субъективной оценкой.
  • Полная пересборка WAR-файла и копирование в Tomcat/webapps.
    Самое надёжное и самое медленное решение. Представьте себе, ждать 1-15 минут после каждого изменения CSS! Нет уж, увольте.
    Минус: ужасно долго.
    Минус: дебаг придётся отдельно настраивать (remote debug). Пусть это не очень сложно, но всё-таки лишние усилия.
  • MyEclipse
    Платная IDE на базе Eclipse. При компиляции проекта копирует все ресурсы в отдельную папку — у меня это работало ужасно долго. Также у меня остались неприятные воспоминания от заморочек с настройкой.
  • Eclipse WTP (Web Tools Platform) — бесплатный, но тяжёлый плагин для Eclipse
    Я несколько раз пытался, но так и не смог настроить его так, как мне надо.
    Плюс: умеет работать с несколькими разными контейнерами (Tomcat, JBoss и т.д.)
    [UPD] Впрочем, вот тут Akvel рассказывает, как он безболезненно использует Eclipse WTP.
  • Application Server integrations
    аналог Eclipse WTP, доступный только в платной версии Intellij IDEA.
    Минус: платный.
  • JRebel
    Помимо своей основной задачи, JRebel позволяет конфигурировать ClassPath приложений прямо из проектов.
    Минус: JRebel платный.
    Плюс: если ваш работодатель на него раскошелится, JRebel может сильно упростить и ускорить работу разработчика.
  • Play! framework
    Умеет запускать приложение прямо из проекта и перегружать изменённый код на лету. Очень удобно.
    Минус: вы ограничены одним фреймворком, у которого есть свои особенности и ограничения.
  • Run Jetty Run
    Плагин для Eclipse, позволяющий запускать Jetty с приложением прямо из проекта. По сути, он делает в точности то же, что описано в этой статье.
    Плюс: содержит в себе все необходимые jar'ы, вам не придётся ничего дополнительно скачивать, кроме самого плагина.
    Минус: умеет запускать только один проект.
    Минус: не умеет настраивать соединение с базой данных. Придётся по-любому делать это руками.
  • [UPD] Jetty Maven plugin
    Как подсказывает 1nd1go, если в вашем проекте используется Maven, вы можете запускать веб-приложения командой "mvn jetty:run".
    Плюс: если используете плагин для Maven в своей IDE, можете запускать эту команду прямо из IDE; при этом можно и дебажить, т.к. jetty плагин запускается в том же процессе, что и мавен.
    Минус: вы привязаны к системе сборки (maven) и к IDE.
  • [UPD] Jetty Gradle plugin
    Если в вашем проекте используется Gradle, вы можете запускать веб-приложения командой "gradle jettyRun".
    Плюс: эту команду прямо из IDE (даже без всяких плагинов), при этом можно и дебажить.
    Минус: вы привязаны к системе сборки (gradle).
  • [UPD] Sysdeo Eclipse Tomcat Launcher plugin
    Как подсказывает helions8, для Eclipse есть хороший плагин Sysdeo Tomcat launcher, который позволяет совместить контекст томката и проекта, потому не нужно собирание war-файла и подкладывание его контейнеру, всякий css, html и прочий js правится сразу, на горячую. В большинстве случаев работает hot replace. По сути, этот плагин делает то же самое, что и наш Launcher
    Минус: вы привязаны к Eclipse.
    Минус: требует больше суеты. Надо и Tomcat самому скачать, и какой-то новый «tomcat project» создать — в общем, вполне неплохое средство, но запускалка проще.
  • [UPD] Spring source tool suite
    Как подсказывает alexeygrigorev, для Eclipse есть плагин Spring source tool suite, который имеет встроенный «навороченный томкат», умеющий редеплоить при сохранении файлов
    Минус: вы привязаны к Eclipse и STS.


Остаётся добавить, что в embedded режиме можно запускать не только Jetty, но и Tomcat, Glassfish и др. Конкретный контейнер неважен, важен принцип: никакой длительной пересборки, установки, распаковки и настройки. Всё запускается быстро и просто из одного класса.

Я сам разрабатываю веб-приложения таким образом уже несколько лет и всем остальным советую. Так вы будете тратить меньше времени на суету вокруг сборки и инсталляции и сможете сосредоточиться на собственно разработке.

Запускай!

Андрей Солнцев @asolntsev
карма
101,0
рейтинг 0,0
Пользователь
Похожие публикации
Самое читаемое Разработка

Комментарии (43)

  • +2
    странно, почему вы думаете, что Jetty тормознутый — его очень многие используют, и как раз потому, что достаточно быстрый и расширяемый, с другой стороны небольшой. Уж по крайней мере по сравнению с приводимыми Tomcat и jBoss
    • +3
      Да не думаю я так, просто часто слышал подобные высказывания.
      Я это не подтверждаю и не опровергаю, я говорю, что в данном контексте это неважно.
      • 0
        Jetty используют в tankionline.com для выдачи статичного контента (конфигов, в частности).
        Так что не думаю, что такой уж тормознутый, учитывая специфику указанного ресурса и посещаемость.
        • 0
          Статика отдается nginx. Конфиги отдаются jetty, но кешируются тоже nginx.
          Jetty умер бы на таком трафике.
    • 0
      Да не думаю я так. Мне просто доводилось слышать подобные высказывания.
      Я их не подтверждаю и не опровергаю, я говорю, что в данном контексте это неважно.
  • +4
    >Контейнер Jetty уступает в производительности Tomcat, JBoss, Resin и др.
    >Контейнер Jetty нестабилен / иногда грохается

    Извините за оффтоп, но откуда вы это взяли? :) У нас Jetty использует в продакшне под нагрузкой. Все устраивает.
    • +1
      И не только у вас.
      Google App Engine использует Jetty.
  • +1
    Единственный способ разрабатывать быстро веб приложения на JAVA это использование PLAY! framework.
    • +1
      Holy war? Способы бывают разные.
      Tapestry 5 неплохой современный framework. И кстати, умеет самостоятельно перезагружать классы приложения.
      • 0
        вы использовали Play? из коробки CRUD интерфейс, авторизация?? и много другое
  • 0
    а OSGI?
    • 0
      А что OSGi. OSGi тоже будет либо с jetty либо с tomcat бандлом. Ну или с чем то еще.
  • 0
    У меня один раз получилось настроить Eclipse WTP для hot redeploy. Но как то он жил своей жизнью, при смене лунной фазы все переставало работать.
  • +1
    А почему вы не приводите в пример maven, в частности jetty-maven-plugin? Всякие IDE-зависимые вещи, как показывает опыт, очень сильно мешают в переспективе — настройка environment'а слишком затягивается для новых сотрудников или после восстановление системы, плюс всякие несовместимости плагинов и т.п.
    • 0
      Да, есть и такой способ. Кстати, не только Maven — например, в Gradle это очень удобно сделано. Добавлю.

      Ну да, этим Launcher и хорош, что не зависит от IDE.
      • 0
        Gradle — это, емнип, для grails. Поэтому не очень для дженеричной java. А Launcher хорош, но имхо, maven он более православный путь в том смысле, что уже есть велосипед :)
        • +1
          Ничего подобного, Gradle — инструмент для сборки любых проектов, и в частности, для Java он прекрасно подходит. Например, проект Hibernate перешёл на него год или два назад. Мы тоже его используем, очень удобно.

          Если ты запускаешь приложение из Maven, как его дебажить? Есть, конечно, remote debug, но ведь его ещё надо настроить. Запускать дебаг одной кнопкой по-любому проще.
          • 0
            Про Gradle, ok. Не знал.

            На счет дебага — эмебеддед jetty плагин запускается в том же процессе, что и мавен. Соответственно, когда ты запускаешь мавен таску из IDE в дебаге, соответственно он и код дебажит, поэтому все одной кнопкой.
  • 0
    в силу особенностей апп использую Red5 server но процедура запуска у меня аналогичная.
  • 0
    Добавлю еще что самый-самый правильный способ разработки, это когда результат проверяется юнит-тестами. Во-первых юнит-тесты обычно быстро запускаются из IDE, так как IDE и так совершает инкрементальную компиляцию + проверка работоспособности автоматизируется и происходит быстрее.
    • 0
      UI юнит-тестами нормально не покроешь — все равно глазами пробежаться придется
      • 0
        Вот по этому я люблю разделять разработку логики и UI. Вначале сервисы можно тестами покрывать. А UI да, ручками протыкивать.
  • 0
    в список альтернатив можно было ещё grails добавить
  • +1
    Скажи пожалуйста, а c приложениеями, которые активно используют CDI, JPA, JSF и прочие JEE оно наверно не взлетит?
    • 0
      С JPA и JSF взлетит спокойно, почему нет.
      Насчёт CDI сказать не могу, не пробовал. Насчёт остальных JEE сложно сказать, надо рассматривать каждый в отдельности.
      • 0
        значит для каждой используемой плюшки нужно будет просто подсовывать нужные jar-файлы, так?
        • 0
          Ну да, видимо, так.
          • 0
            вся эта кухня с Jetty — просто жестко связываетя, то что Sun сознательно разделила. Вы бы попробовали тот же JPA на нормальном JEE контейнере — совсем другая песня, а с Jetty — тот-еще гемор с конфигами.
  • 0
    JRebel вам в помощь. Правда, он не бесплатный.
  • 0
    Мы пользуемся Sysdeo (для Tomcat) для Eclipse, который позволяет совместить контекст томката и проекта, потому не нужно собирание war-файла и подкладывание его контейнеру, всякий css, html и прочий js правится сразу, на горячую. В большинстве случаев работает hot replace.
    • 0
      Да, его я тоже пробовал. В общем, хорошая штука, делает примерно то же самое.

      Но мне кажется, он требует больше телодвижений. И Томкат надо поставить, и какой-то новый «tomcat project» создать… А что, если я не хочу новый проект создавать? И структура этого проекта должна быть строго определённая…
      И ещё он меняет сам Томкат (server.xml). В общем, хорошо, но можно проще.
      • 0
        Да, существующую кодовую базу сложно привести в соответствие с требованиями, накладываемыми плагином. Но можно, и делалось. server.xml насколько я помню не меняется, добавляется только описание нового контекста, но я могу ошибаться. В любом случае — это томкат локальный, если что — его не жалко. Ну и я написал по поводу Sysdeo наверное больше потому, что по-хорошему его бы тоже добавить в список альтернативных решений. Оно все же лучше, когда используется один и тот же контейнер на машине разработчика, и на сервере.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Не знаю, важно ли, но с седьмой версии Jetty названия пакетов и классов были поменяны с org.mortbay.jetty.* на org.eclipse.jetty.* — подробнее в Eclipse Wiki.
    Соответственно, ваш код с более новыми версиями Jetty работать не будет (что, правда, пока не так сильно важно, поскольку и 6-ая и 7-ая версия поддерживаются; впрочем, сами разработчики Jetty рекомендуют для новых проектов использовать седьмую версию).
  • 0
    А я Spring source tool suite использую — их навороченный томкэт умеет редеплоить при сохранении файлов.
  • 0
    Если в проекте используется Maven, то все проблемы решаются Embeded Jetty плагином.
    Eclispe и Idea умеют запускать MVN-задачи, а редеплоится Jetty будет автоматом в таком режиме (от этого правда больше негатива, чем позитива).
  • 0
    Спасибо за статью. Настроил все так же как вы и описывали. Однако приложение почему-то не подхватывает автоматически свежескомпиллированный код. Пользуюсь Intellij Idea (создал линк с out к WEB-INF/classes), работаю с javax.ws + org.restlet. Нужно ли мне что-то еще настраивать? Или это спицифика выбранных фреймворков?
    • 0
      Кстати, статика отдается нормально, проверил скомпилленные классы — меняется время последнего изменения, но все равно не подхватывается
    • 0
      Нет, ничего настраивать не надо. IDEA умеет сама подхватывать свежескомпилированные классы. Правда, есть ограничение, накладываемой явой: перекомпилированный класс подхватится, только если изменения внутри тела метода. А если изменился API (т.е. сигнатура метода, имя класса), то не подхватится. Но в обоих случаях IDEA явно говорит об этом пользователю: после компиляции в левом нижнем углу вслывает окошечко с надписью a'la «classes reloaded» или «classes could not be reloaded».

      Кстити, в IDEA эту функциональность можно принудительно отключить. Может, она у вас отключена?
      • 0
        Хм. Заработало только в режими дебага, но в обычном режиме не работает. Но это меня устраивает. Спасибо за труды
  • 0
    IDEA c Tomcat подхватывает изменения css/html на лету, если запускаться как Tomcat Server local или через tomcat7-maven-plugin.
    • –1
      Из IDEA я не пробовал запускать Tomcat. Пробовал из Eclipse (точнее, MyEclipse). Во-первых, интерфейс был очень сложным. А во-вторых, скорость ужасающая. По каждому чиху MyEclipse копировала все статические ресурсы и java-классы в tomcat. Приложение большое, ресурсов было много — копирование тормозило жутко.
    • 0
      Правка: подхватывает если
      1. деплоить не war а war exploded
      2. в окне Debug у Server Deployment нажата кнопка Update Resources on Frame Deactivation

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.