Статьи

Веб-приложения Java без войны

Вы когда-нибудь задумывались над тем, почему в Java мы упаковываем веб-приложения в файлы WAR (или структуры каталогов WAR)? Это, безусловно, удобный способ перемещения приложения и его зависимостей из одного места в другое. Но разве не было бы неплохо, если бы все могло оставаться на прежнем месте и не было бы перемещения файлов? Разве не было бы неплохо, если бы вы указали требуемую версию Jetty или Tomcat, как и в случае с любой другой зависимостью? Подход без WAR — это тот, который завоевывает популярность в таких веб-фреймворках Java, как Play! угробить файлы WAR. С помощью стандартных веб-приложений на Java мы также можем отказаться от файлов WAR, просто запустив встроенный сервер Jetty или Tomcat. Давайте попробуем и посмотрим, как все пойдет.

Для этого эксперимента я собираюсь использовать Maven и Jetty. Он по-прежнему будет использовать ту же стандартную исходную структуру для файла WAR ( src / main / java , src / main / webapp и т. Д.). Основное отличие состоит в том, что я на самом деле запускаю Jetty, используя старый добрый статический void main . Это похоже на использование цели jetty: запустить цель, но позволит нам иметь одинаковую точную настройку при разработке и производстве. Статический материал будет в src / main / webapp , скомпилированные классы будут в target / classes, и зависимости будут правильными, если Maven загрузит их. Во-первых, вот небольшой класс Java ( src / main / java / foo / Main.java ), который устанавливает сервер Jetty и запускает его:

package foo;
 
import java.io.File;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
 
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.*;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.webapp.WebAppContext;
 
public class Main
{
 
  public static void main(String[] args) throws Exception
  {
    String webappDirLocation = "src/main/webapp/";
 
    Server server = new Server(8080);
    WebAppContext root = new WebAppContext();
 
    root.setContextPath("/");
    root.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
    root.setResourceBase(webappDirLocation);
 
    root.setParentLoaderPriority(true);
 
    server.setHandler(root);
 
    server.start();
    server.join();
  }
}

Как видите, Main просто ссылается на каталог webapp, поэтому мне не нужно копировать материал оттуда в другое место. Далее у меня есть небольшой тестовый сервлет ( src / main / java / foo / HelloServlet.java ):

package foo;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.*;
 
public class HelloServlet extends HttpServlet
{   
 
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  {
    PrintWriter out = resp.getWriter();
    out.println("hello, world");
    out.close();
  }
}

А теперь файл web.xml ( src / main / webapp / WEB-INF / web.xml ):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  version="2.5">
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>foo.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

И, наконец, файл pom.xml, который определяет Jetty как зависимость и предоставляет простой способ запуска класса Main:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.jamesward</groupId>
  <version>1.0-SNAPSHOT</version>
  <name>warless_java_web_app</name>
  <artifactId>warless_java_web_app</artifactId>
  <packaging>jar</packaging>
 
  <properties>
    <jettyVersion>7.3.1.v20110307</jettyVersion>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-webapp</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.2</version>
        <configuration>
          <mainClass>foo.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

А теперь просто запустите:

mvn compile exec:java

Maven компилирует мои классы Java в target / classes, а затем цель exec: java запускает Main, который находит другие активы WAR в каталоге src / main / webapp . Если вы следили за этим, сделайте запрос на http: // localhost: 8080 /, чтобы убедиться, что он работает (что должно).

Есть две альтернативы запуску Jetty от Maven. Вы можете использовать плагин Maven appassembler для создания сценариев запуска, содержащих правильные ссылки CLASSPATH, а затем запустить основной класс, используя сгенерированные сценарии. Или вы можете использовать сборку Maven или плагин shade для создания JAR-файла, содержащего приложение и все его зависимости.

Вот пример раздела файла pom.xml для использования плагина appassembler:

  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <version>1.1.1</version>
    <configuration>
      <assembleDirectory>target</assembleDirectory> 
      <generateRepository>false</generateRepository>
      <programs>
        <program>
          <mainClass>foo.Main</mainClass>
          <name>main</name>
        </program>
      </programs>
    </configuration>
    <executions>
      <execution>
        <phase>package</phase>
        <goals>
          <goal>assemble</goal>
        </goals>
      </execution>          
    </executions>
  </plugin>

Для генерации стартовых скриптов просто запустите:

mvn install

Затем для запуска сценария установите переменную среды REPO в свой репозиторий Maven:

export REPO=~/.m2/repository

А затем просто запустите скрипт:

sh target/bin/main

Весь код для этого примера находится на github.com:
https://github.com/jamesward/warless_java_web_apps

Чтобы сделать все это еще проще, у Jetty есть архетип Maven для генерации всего для вас. Чтобы создать новый проект, содержащий эту настройку, выполните:

mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=7.5.0-SNAPSHOT

И теперь вы готовы создать веб-приложение Java без войны!

Эта настройка действительно необходимый минимум для обработки запросов к веб-ресурсам и сервлетам, вам нужно будет проделать немного больше работы, если вы хотите добавить поддержку JSP. Узнайте больше об этом в документации Jetty .

Итак … Что вы думаете о веб-приложениях Java без файлов WAR и упаковки WAR? Я хотел бы услышать ваши мысли!