Using Maven to create “runnable WAR”

两盒软妹~` 提交于 2021-01-28 12:35:32

问题


Okay, I'm trying to create a war file that is runnable from the command line using Maven.

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <warName>${project.artifactId}-${project.version}</warName>
        <warSourceDirectory>src\main\java\META-INF\webapp\WEB-INF</warSourceDirectory>
        <webXml>src\main\java\META-INF\webapp\WEB-INF\web.xml</webXml>
        <archive>
            <manifest>
                <mainClass>classes\ReportToolRunner</mainClass>
                <addClasspath>true</addClasspath>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>default-war</id>
            <phase>package</phase>
            <goals>
                <goal>war</goal>
            </goals>
        </execution>
    </executions>
</plugin>

When I run the compiled war file, I get "Error: Could not find or load main class classes\ReportToolRunner", I've tried all manner of different paths for the <\mainClass> tag.

I don't want to run the war using tomcat or anything like that, I just want to be able to run it like:

java -jar reportTool.war

I'm using Jetty for my web server.


回答1:


It is possible, if you embed a servlet container like Jetty: Embedded Jetty Executable War.

Note: A runnable war is not very common. (For example Jenkins did this - It allows the user to decide whether to run the application standealone - maybe for some product evaluation, where no further infrastructure should be installed - or to deploy it on a (shared) servlet container, which gets managed and monitored)

Solution: The following steps are necessary and can be achieved with standard Maven plugins:

  1. Write a Main class, that starts the Jetty server and adds the webapp context
  2. At prepare-package phase move the Main class and all classes used for the server start from ${project.build.directory}/classes/ to the target war directory to fulfill the jar layout (in a war file the classes are in a "classes" folder, but in a jar file the classes are in the root folder)
<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <id>classes-copy</id>
      <phase>prepare-package</phase>
      <configuration>
        <tasks>
          <move todir="${project.build.directory}/${project.artifactId}-${project.version}/">
            <fileset dir="${project.build.directory}/classes/">
              <include name="your.package.Main.class" />
            </fileset>
          </move>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>
  1. Unpackage all dependencies, which are necessary for the Jetty server startup, so those classes are a part of your executable war. (You can skip this, but then those dependencies must be available on the classpath, when the war is executed as jar: java -cp <your classpath> -jar <your war>. (Please note: The dependency list, depends on your Jetty server implementation)
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <version>2.3</version>
  <executions>
    <execution>
      <id>jetty-classpath</id>
      <phase>prepare-package</phase>
      <goals>
        <goal>unpack-dependencies</goal>
      </goals>
      <configuration>
        <includeGroupIds>org.eclipse.jetty,javax.servlet</includeGroupIds>
        <outputDirectory>
          ${project.build.directory}/${project.artifactId}-${project.version}
        </outputDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>
  1. Specify the main class within the manifest, so that the war file can be executed as a jar. (Please note: I also specified the war name. This name is used in the previous plugin configurations as part of the "move todir" and the "outputDirectory")
<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.1.1</version>
  <configuration>
    <warName>${project.artifactId}-${project.version}</warName>
    <archive>
      <manifest>
        <mainClass>your.package.Main</mainClass>
      </manifest>
    </archive>
  </configuration>
  <executions>
    <execution>
      <id>default-war</id>
      <phase>package</phase>
      <goals>
        <goal>war</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Here is the server code (Jetty version 8.1.0.v20120127) I used. It configures a new Jetty server and adds a webapp context (see last code snippet) - if configured correctly the server can be started and stopped with server.start() / server.stop():

// Create connector
SocketConnector connector = new SocketConnector();
connector.setMaxIdleTime(1000 * 60 * 60);
connector.setSoLingerTime(-1);
connector.setPort(8080);

// Create handler collection
ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.setHandlers(new Handler[] { contextHandlerCollection });

// Add webapp context
context.setServer(server);
contextHandlerCollection.addHandler(context);

server.setConnectors(new Connector[] { connector });
server.setHandler(handlerCollection);

And finally the webapp context code:

public class ServerContextImpl extends WebAppContext {

  private static final Logger LOGGER = Logger.getLogger(ServerContextImpl.class);

  protected static final String[] JETTY_PLUS_CONFIGURATION_CLASSES;

  static {
    JETTY_PLUS_CONFIGURATION_CLASSES = new String[7];
    JETTY_PLUS_CONFIGURATION_CLASSES[0] = "org.eclipse.jetty.webapp.WebInfConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[1] = "org.eclipse.jetty.webapp.WebXmlConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[2] = "org.eclipse.jetty.webapp.MetaInfConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[3] = "org.eclipse.jetty.webapp.FragmentConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[4] = "org.eclipse.jetty.plus.webapp.EnvConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[5] = "org.eclipse.jetty.plus.webapp.PlusConfiguration";
    JETTY_PLUS_CONFIGURATION_CLASSES[6] = "org.eclipse.jetty.webapp.JettyWebXmlConfiguration";
  }

  ServerContextImpl() {
    setConfigurationClasses(JETTY_PLUS_CONFIGURATION_CLASSES);
    setContextPath("/");
    setWar(getWarLocation());
  }

  /**
   * Returns the location of the war (a trick, which is necessary for executable
   * wars please see: <a target="_blank" href=
   * "http://uguptablog.blogspot.de/2012/09/embedded-jetty-executable-war-with.html"
   * >Embedded Jetty with executable WAR</a>).
   * 
   * @return The war location.
   */
  protected String getWarLocation() {
    ProtectionDomain protectionDomain = ServerImpl.class.getProtectionDomain();
    URL location = protectionDomain.getCodeSource().getLocation();
    return location.toExternalForm();
  }
}

Please note the getWarLocation() method. It uses the packaged war itself as location.




回答2:


I did some research into this recently, and found flaws with most approaches (either to slow to build (shade, assembly, jar-with-dependencies)), too verbose, or not self-contained.

I found that a great way of doing this, which gave the best result (fast build, self-contained, simple setup) was the spring-boot-maven-plugin. It's as simple as this:

<packaging>war</packaging>

<build>
  <outputDirectory>${basedir}/src/main/webapp/WEB-INF/classes</outputDirectory>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>1.4.3.RELEASE</version>
      <executions>
        <execution>
          <goals>
            <goal>repackage</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <mainClass>com.MyMainClass</mainClass>
      </configuration>
    </plugin>
  </plugins>
</build>

With some simple tweaks it can even build a war that can be deployed, as well as executed.

This plugin can be used even though your project doesn't use Spring Boot.




回答3:


I think you are not right using <mainClass>

From: http://maven.apache.org/shared/maven-archiver/examples/classpath.html

If you want to create an executable jar file, you need to configure Maven Archiver accordingly. You need to tell it which main class to use. This is done with the configuration element. Here is a sample pom.xml configured to add the classpath and use the class fully.qualified.MainClass as the main class:

 <project>   ...   <build>
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
         ...
         <configuration>
           <archive>
             <manifest>
               <addClasspath>true</addClasspath>
               <mainClass>fully.qualified.MainClass</mainClass>
             </manifest>
           </archive>
         </configuration>
         ...
       </plugin>
     </plugins>   </build>   ...   <dependencies>
     <dependency>
       <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
       <version>2.1</version>
     </dependency>
     <dependency>
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-utils</artifactId>
       <version>1.1</version>
     </dependency>   </dependencies>   ... </project>

The manifest produced using the above configuration would look like this:

Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven ${maven.version} Built-By: ${user.name} Build-Jdk: ${java.version} Main-Class: fully.qualified.MainClass Class-Path: plexus-utils-1.1.jar commons-lang-2.1.jar



来源:https://stackoverflow.com/questions/29061346/using-maven-to-create-runnable-war

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!