可执行的war文件如何在没有maven的情况下启动jetty

52
我正在尝试制作一个“可执行”的war文件(java -jar myWarFile.war),它将启动托管在WAR文件中的webapp的Jetty web服务器。
我找到了一篇描述如何制作这种文件的网页
然而,按照那个建议以及我认为制作可执行jar(war)文件的方式并没有起作用。
我有一个Ant任务来创建一个带有类似以下清单的WAR文件:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.1
Created-By: 1.5.0_18-b02 (Sun Microsystems Inc.)
Main-Class: Start

WAR文件的内容如下:

> Start.class
> jsp
>   build.jsp 
> META-INF  
>   MANIFEST.MF
> WEB-INF
>   lib
>     jetty-6.1.22.jar
>     jetty-util.6.1.22.jar

当我尝试执行WAR文件时,出现错误:

Exception in thread "main" java.lang.NoClassDefFoundError: org/mortbay/jetty/Handler
Caused by: java.lang.ClassNotFoundException: org.mortbay.jetty.Handler
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: Start. Program will exit.

这里似乎出现了两个错误:一个是 JAR 文件无法找到,另一个是 Start 类无法找到。

要解决第一个问题,我将 Jetty JAR 文件放在 WAR 文件的根目录下,再试了一次,但仍然出现相同的错误。我还尝试将 WEB-INF/lib/<specific-JAR-files> 添加到清单文件的 Class-Path 属性中,但也没有起作用。

是否有人能够解释一下我做得对/错的地方,并告诉我如何使这个可执行的 WAR 文件运行起来呢?


4
您是否必须拥有一个.war文件?为什么不使用一个包含.war文件、jetty分发文件和能够以程序方式启动jetty并将您的web应用程序加载到上下文中的主类的.jar文件呢? - whaley
9个回答

50
您在问题中提供的链接已经提供了大部分所需信息。但是,除此之外还需要完成一些其他工作。
任何Jetty启动所需的类文件都需要位于打包为war文件时的根目录下。我们可以在<war>文件之前利用Ant来完成这项工作。该war文件的清单文件还需要一个Main-Class属性以执行服务器。
以下是逐步操作:

创建Jetty服务器类:

这是从您提供的链接中改编而来。

package com.mycompany.myapp;

import java.io.File;
import java.net.URL;
import java.security.ProtectionDomain;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;

public final class EmbeddedJettyServer
{
    public static void main(String[] args) throws Exception
    {
        int port = Integer.parseInt(System.getProperty("port", "8080"));
        Server server = new Server(port);

        ProtectionDomain domain = EmbeddedJettyServer.class.getProtectionDomain();
        URL location = domain.getCodeSource().getLocation();

        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
        webapp.setServer(server);
        webapp.setWar(location.toExternalForm());

        // (Optional) Set the directory the war will extract to.
        // If not set, java.io.tmpdir will be used, which can cause problems
        // if the temp directory gets cleaned periodically.
        // Your build scripts should remove this directory between deployments
        webapp.setTempDirectory(new File("/path/to/webapp-directory"));

        server.setHandler(webapp);
        server.start();
        server.join();
    }
}

要查看您可以在此处配置的所有内容,请参阅Jetty API文档

使用Ant构建war包:

这将使用一个暂存目录将必要的类文件解压缩到war包的根目录中,以便在执行war包时可以访问它们。

<target name="war" description="--> Creates self-executing war">
  <property name="staging.dir" location="${basedir}/staging"/>
  <property name="webapp.dir" location="${basedir}/src/webapp"/>

  <mkdir dir="${staging.dir}"/>

  <!-- assumes you have all of your war content (excluding classes and libraries) already structured in a directory called src/webapp -->
  <!-- e.g. -->
  <!-- src/webapp/index.html -->
  <!-- src/webapp/WEB-INF/web.xml -->
  <!-- src/webapp/WEB-INF/classes/my.properties -->
  <!-- etc ... -->
  <copy todir="${staging.dir}">
    <fileset dir="${webapp.dir}" includes="**/*"/>
  </copy>

  <unjar dest="${staging.dir}">
    <!-- you'll have to locate these jars or appropriate versions; note that these include JSP support -->
    <!-- you might find some of them in the downloaded Jetty .tgz -->
    <fileset dir="path/to/jetty/jars">
      <include name="ant-1.6.5.jar"/>
      <include name="core-3.1.1.jar"/>
      <include name="jetty-6.1.24.jar"/>
      <include name="jsp-2.1-glassfish-2.1.v20091210.jar"/><!-- your JSP implementation may vary -->
      <include name="jsp-api-2.1-glassfish-2.1.v20091210.jar"/><!-- your JSP implementation may vary -->
      <include name="servlet-api-2.5-20081211.jar"/><!-- your Servlet API implementation may vary -->
    </fileset>
    <patternset><!-- to exclude some of the stuff we don't really need -->
      <exclude name="META-INF/**/*"/>
      <exclude name="images/**/*"/>
      <exclude name=".options"/>
      <exclude name="about.html"/>
      <exclude name="jdtCompilerAdapter.jar"/>
      <exclude name="plugin*"/>
    </patternset>
  </unjar>

  <!-- copy in the class file built from the above EmbeddedJettyServer.java -->
  <copy todir="${staging.dir}">
    <fileset dir="path/to/classes/dir" includes="com/mycompany/myapp/EmbeddedJettyServer.class"/>
  </copy>

  <war destfile="myapp.war" webxml="${webapp.dir}/WEB-INF/web.xml">
    <fileset dir="${staging.dir}" includes="**/*"/>
    <classes dir="path/to/classes/dir"/><!-- your application classes -->
    <lib dir="path/to/lib/dir"/><!-- application dependency jars -->
    <manifest>
      <!-- add the Main-Class attribute that will execute our server class -->
      <attribute name="Main-Class" value="com.mycompany.myapp.EmbeddedJettyServer"/>
    </manifest>
  </war>

  <delete dir="${staging.dir}"/>
</target>

执行这个war包:

如果上述一切设置正确,您应该能够执行以下操作:

java -jar myapp.war

// or if you want to configure the port (since we are using the System property in the code)

java -Dport=8443 -jar myapp.war

非常微小的注释:在从JAR文件中排除文件夹时(在<unjar>中),我们可以使用**/META-INF/**而不是META-INF/**/*来排除实际文件夹以及文件夹内容。否则,根文件夹仍将被包括在内。 - joscarsson
为什么要解压所有依赖的JAR包?它们会被包含在WEB-INF/lib目录中。 - RTF
1
@RTF - 已经有一段时间了,但我认为这是因为那些类是可执行war本身的依赖项(即jetty,EmbeddableJettyServer等),当您执行war时,它无法从嵌入式jar(在其内部)中加载它们-它们必须是打包到war中的类。 - Rob Hruska

45

这是对@RobHruska答案的Maven适配。它只是复制主类文件并将Jetty JAR文件合并到WAR文件中,没有什么新内容,只是为了简化您的生活,特别是当您像我一样刚接触Maven时:

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
        <execution>
            <id>move-main-class</id>
            <phase>compile</phase>
            <configuration>
                <tasks>
                    <copy todir="${project.build.directory}/${project.build.finalName}">
                        <fileset dir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes/">
                            <include name="main/*.class" />
                        </fileset>
                    </copy>

                    <unjar dest="${project.build.directory}/${project.build.finalName}">
                        <!-- you'll have to locate these jars or appropriate versions; note that these include JSP support -->
                        <!-- you might find some of them in the downloaded Jetty .tgz -->
                        <fileset dir="${project.build.directory}/${project.build.finalName}/WEB-INF/lib/">
                            <include name="ant-1.6.5.jar"/>
                            <!--<include name="core-3.1.1.jar"/>-->
                            <include name="jetty*"/>
                            <include name="servlet-api*"/>
                        </fileset>

                        <patternset><!-- to exclude some of the stuff we don't really need -->
                            <exclude name="META-INF/**/*"/>
                            <exclude name="images/**/*"/>
                            <exclude name=".options"/>
                            <exclude name="about.html"/>
                            <exclude name="jdtCompilerAdapter.jar"/>
                            <exclude name="plugin*"/>
                        </patternset>
                    </unjar>
                </tasks>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin> 
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.2</version>
    <configuration>
        <archiveClasses>true</archiveClasses>
        <archive>
            <manifest>
                <mainClass>main.Main</mainClass> 
            </manifest>
        </archive>
    </configuration>
</plugin>

如何让它执行?我通常使用 mvn clean compile war:war 进行构建,但是 maven-antrun-plugin 从未被执行。 - Ragnar

14
我们通过使用jetty-console-maven-plugin解决了这个问题。每当您运行mvn package时,它会创建另一个war文件,可以与java -jar whateverpackage-runnable.war一起使用。
        <plugin>
            <groupId>org.simplericity.jettyconsole</groupId>
            <artifactId>jetty-console-maven-plugin</artifactId>
            <version>1.45</version>
            <executions>
                <execution>
                    <goals>
                        <goal>createconsole</goal>
                    </goals>
                </execution>
            </executions>

            <configuration>
                <additionalDependencies>
                    <additionalDependency>
                        <artifactId>jetty-console-requestlog-plugin</artifactId>
                    </additionalDependency>
                    <additionalDependency>
                        <artifactId>jetty-console-gzip-plugin</artifactId>
                    </additionalDependency>
                    <additionalDependency>
                        <artifactId>jetty-console-ajp-plugin</artifactId>
                    </additionalDependency>
                    <additionalDependency>
                        <artifactId>jetty-console-startstop-plugin</artifactId>
                    </additionalDependency>
                </additionalDependencies>
            </configuration>
        </plugin>
它还会为您生成init.d脚本和所有其他内容!

1
这个如何工作的文档在哪里可以找到?如何声明启动Jetty的类?最后,这是否要求jetty jars在war的根目录下? - Jose Martinez
它会为你完成所有的工作,你只需要运行“mvn package”命令,它就会为你生成一个war包。 - Rafael Sanches

7

Jetty最终太过令人困惑,我找不到任何在线帮助。试图让我的war包含winstone也证明是有问题的,但是一旦我将我的应用程序放入未解压的winstone源代码中,然后重新打包,就可以顺利进行了。 - twilbrand
Kohsuke已经厌倦了维护Winstone,因此他用Jetty周围的Winstone兼容包替换了它。http://jenkins-ci.361315.n4.nabble.com/Winstone-is-now-powered-by-Jetty-td4678478.html - Thorbjørn Ravn Andersen

6

即使这篇文章有点老了,使用Jetty 8的另一种选择是将Jetty jars作为依赖项包含在您的pom中,并在pom中添加以下内容(而不是解压war并重新打包的ant脚本):

            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>1.4</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <createDependencyReducedPom>true</createDependencyReducedPom>
                        <transformers>
                            <transformer
                                implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>JettyStandaloneMain</mainClass>
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <!-- The main class needs to be in the root of the war in order to be 
            runnable -->
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <id>move-main-class</id>
                    <phase>compile</phase>
                    <configuration>
                        <tasks>
                            <move todir="${project.build.directory}/${project.build.finalName}">
                                <fileset dir="${project.build.directory}/classes/">
                                    <include name="JettyStandaloneMain.class" />
                                </fileset>
                            </move>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

3
我认为你是想要一个可以单独运行而不是使用"mvn jetty:run"的jar文件,而不是完全不使用maven。

我花了很长时间才弄清楚这一点,因为我找到了许多选项,但没有一个是非常简单明了的。最终,我找到了simplericity的这个maven插件。它工作得非常好。


谢谢,但是你知道如何配置它以添加上下文路径吗? - Hunsu

1

这是我的ANT示例提取。想法是解压缩Jetty依赖项,然后像普通的JAR文件一样在本地包含它们:

<!-- Hack: Java doesn't support jars within jars/wars -->
<unjar src="${lib.dir}/container/jetty.jar" dest="${build.dir}/unjar"/>
<unjar src="${lib.dir}/container/jetty-util.jar" dest="${build.dir}/unjar"/>
<unjar src="${lib.dir}/container/servlet-api.jar" dest="${build.dir}/unjar"/>
<unjar src="${lib.dir}/container/jsp-api.jar" dest="${build.dir}/unjar"/>

<!-- Build war file as normal, just including the compiled and unjar'ed files -->
<war destfile="${war.file}" webxml="${config.dir}/web.xml">
    <fileset dir="${build.dir}/classes"/>
    <fileset dir="${build.dir}/unjar"/>
    <fileset dir="${resources.dir}" excludes="*.swp"/>
    <lib dir="${lib.dir}/runtime"/>
    <manifest>
        <attribute name="Main-Class" value="Start"/>
    </manifest>
</war>

注意:
WEB-INF/lib目录用于存放Web应用程序的依赖项。在这种情况下,我们将WAR文件打包成正常的Jetty JAR文件,以便在启动时正常运行。

0
  • 将 .jars 放在 .war 文件根目录中不起作用
  • 将 .jars 放在 WEB-INF/lib 中也无法帮助 JVM 找到 Jetty 文件,甚至无法开始启动你的 .war。把它们放在那里已经“太晚”了。
  • 将 .jars 放在清单 Class-Path 中只适用于外部 .jar 文件,而不适用于包含在 .jar 中的文件

那么该怎么办呢?

  • 使用构建脚本将所有所需的 .jar 文件合并到 .war 文件中。这需要一些额外的工作。它也有点丑陋,因为编译后的代码是 .war 中可服务的文件的一部分
  • 使用“java -cp jetty.jar:... ...”将依赖的 .jars 添加到 JVM 的类路径中。虽然这样做有点违背一个独立的 .war 的初衷,但它确实有效

@Sean他并没有把JAR文件放在.war文件根目录下,而是放置了类文件,这是完全有效的(正如他的链接所指示的那样)。 - Kannan Ekanath
他的原始帖子显示.jar 文件在 WEB-INF/lib 中的 .war 文件中。然后他提到将 .jar 放在 .war 的基础上。你的跟进帖子显示 lib/ 目录中的 .jar,那么你的意思是什么?我可能错了,因为我没有亲自尝试过,但是在这种情况下 JVM 如何找到 Jetty 类呢?你提到的帖子显示 Maven 将依赖项一起构建,我见过的所有 Maven 输出都将 .class 文件放在一个 .jar/.war 文件中,这就是我的第一个答案。 - Sean Owen

0

我以前做过类似的事情,但你是用"java -jar xxx.war"启动应用程序吗?你只有两个jar文件,我觉得这不够。另外,尝试使用Jetty 7.0.0M1(这是最新版本)。当我将jetty-server和jetty-webapp作为两个依赖项添加时(它们来自org.eclipse.jetty),我在lib目录中获得了以下jar文件。FYI,org.mortbay.jetty.Handler在jetty-server * .jar中。

  • jetty-continuation-7.0.0.M1.jar
  • jetty-http-7.0.0.M1.jar
  • jetty-io-7.0.0.M1.jar
  • jetty-security-7.0.0.M1.jar
  • jetty-server-7.0.0.M1.jar
  • jetty-servlet-7.0.0.M1.jar
  • jetty-util-7.0.0.M1.jar
  • jetty-webapp-7.0.0.M1.jar
  • jetty-xml-7.0.0.M1.jar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接