Spring boot WAR runs from maven, not from command line?

12,809

Solution 1

Upon exploring what the actual spring-boot:run goal of the plugin does, it turns out it rebuilds the project and starts the application from the compiled classes in the target/classes folder. It doesn't appear to use the WAR file at all:

[INFO] --- spring-boot-maven-plugin:1.5.3.RELEASE:run (default-cli) @ spring-boot-app ---
11:58:55.526 [main] INFO app.MyApplication - Beginning run() of MyApplication
2017-05-22 11:58:55.791 DEBUG 4140 --- [           main] .b.l.ClasspathLoggingApplicationListener : Application started with classpath: [file:/C:/work/myapp/target/classes/....[SNIP]

The reason that the WAR wouldn't run is that the internally-contained application WAR file was not being extracted and written to the embedded Tomcat context. Manually extracting the "inside WAR" and writing it to the context location solved the problem:

/**
 * Utility method which exports and unpacks the WAR file into the Tomcat context directory
 * @param warName
 * @param contextPath
 * @return
 * @throws IOException
 * @throws URISyntaxException
 */
private static String exportWar(String warName, String contextPath) throws IOException, URISyntaxException {

    log.info("Beginning export WAR");
    try {
        UnzipUtility unzipUtility = new UnzipUtility();
        unzipUtility.unzip(warName, contextPath);
    } catch (IOException ex) {
        throw ex;
    }
    return contextPath + warName;
}

Here's the UnzipUtility, based on an example from codejava.net:

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * This utility extracts files and directories of a standard zip file to
 * a destination directory.
 * @author www.codejava.net
 *
 */
public class UnzipUtility {
    /**
     * Size of the buffer to read/write data
     */
    private static final int BUFFER_SIZE = 4096;

    public void unzip(String zipFileName, String destDirectory) throws IOException {
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
        ZipInputStream zipIn = new ZipInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream(zipFileName));
        ZipEntry entry = zipIn.getNextEntry();
        // iterates over entries in the zip file
        while (entry != null) {
            String filePath = destDirectory + File.separator + entry.getName();
            if (!entry.isDirectory()) {
                // if the entry is a file, extracts it
                ensureParentExists(filePath);
                extractFile(zipIn, filePath);
            } else {
                // if the entry is a directory, make the directory
                File dir = new File(filePath);
                dir.mkdir();
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }
    /**
     * Extracts a zip entry (file entry)
     * @param zipIn
     * @param filePath
     * @throws IOException
     */
    private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    }

    private void ensureParentExists(String filePath) {
        File parent = new File(filePath).getParentFile();
        if ( parent != null && !parent.exists()) {
            // parent of parent - recursive
            ensureParentExists(parent.getPath());
            // make this dir
            parent.mkdir();
        }
    }
}

Solution 2

For spring boot application, you can run with mvn spring-boot:run, with java -jar thePackage.war or put the war package in tomcat webapps. Each way should work to run you app.

So I think there is some problem in you project. Make sure you have spring-boot-maven-plugin in your pom file. Then when you use mvn package, you should see some log about repackage. If you un-package the war file, you should see some dir like: 'META-INFO' and 'BOOT-INF'.

Share:
12,809

Related videos on Youtube

user1071914
Author by

user1071914

Moody loner

Updated on May 25, 2022

Comments

  • user1071914
    user1071914 almost 2 years

    I checked this question, but the answer (while interesting) basically says "don't use WAR packaging" which is not an option here.

    I have a Spring Boot repackaged WAR which uses embedded Tomcat (Spring Boot 1.5.3.RELEASE, embedded Tomcat 8.5.14). The repackaged app runs fine using the mvn spring-boot:run command but when I try to run it using `java -jar target\mywar.war' I get this:

    java.io.FileNotFoundException: file:\C:\work\spring-boot\target\spring-boot-mywar.war!\WEB-INF\classes!\mywar.war (The filename, directory name, or volume label syntax is incorrect)
    

    This is caused when Spring Boot tries to load the context for the "wrapped" war:

    Context context = tomcat.addWebapp("/mywar", Thread.currentThread().getContextClassLoader().getResource("mywar.war").getPath());
    

    The actual error takes place inside the embedded Tomcat class:

    private URL getWebappConfigFileFromJar(File docBase, String contextName) {
        URL result = null;
        try (JarFile jar = new JarFile(docBase)) {
            JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
            if (entry != null) {
                result = UriUtil.buildJarUrl(docBase, Constants.ApplicationContextXml);
            }
        } catch (IOException e) {
            Logger.getLogger(getLoggerName(getHost(), contextName)).log(Level.WARNING,
                    "Unable to determine web application context.xml " + docBase, e);
        }
        return result;
    }
    

    The operation new JarFile(docBase) throws the FileNotFoundException.

    This works fine when the Maven spring-boot:run goal is used, so I feel that the basic structure is sound - I think there's some classpath issue or something that's keeping it from working.

    Does anyone have any suggestion for duplicating the environment of spring-boot:run on the command line when using WAR packaging?