Search code examples
mavenspring-bootspring-boot-maven-plugin

Custom Launcher ClassNotFound exception when using custom layout


Spring Boot Maven Plugin. LayoutFactory new feature. Available since 1.5.0.M1.

I am having problems when I define a custom launcher in the Custom Layout:

@Override public String getLauncherClassName() { return "com.mycompany.CustomLauncher"; }

If I include my custom launcher in the boot application sources, it gets repackaged to BOOT-INF/classes, and when I try to run the JAR it fails with a ClassNotFound exception.

I have been reading the Repackager code but I cannot find any hook that allows to selectively exclude a given class from the repackaging sequence. If I override the getRepackagedClassesLocation method in my layout then the boot main class is loaded by a different classloader and it fails on SpringBoot ClassNotFound.

Is there any way that I can force the launcher out of the BOOT-INF/classes repackaging?

UPDATE 1

@Override
public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException
{
    String name = PropertiesLauncherInternal.class.getName().replaceAll("\\.", "\\\\") + ".class";
    InputStream inputStream = PropertiesLauncherInternal.class.getResourceAsStream(PropertiesLauncherInternal.class.getSimpleName() + ".class");
    writer.writeEntry(name, inputStream);
    writer.writeLoaderClasses();
}

In order to see that the code is accessible I have added this test:

    JarInputStream is = new JarInputStream(new FileInputStream(new File("c:/git/dev/framework/boot/target/boot-current-SNAPSHOT.jar")), true);
    JarEntry entry = null;
    while (null != (entry = is.getNextJarEntry()))
    {
        System.out.println(entry.getName() + "-" + entry.getCrc());
    }

    URL url = new File("c:/git/dev/framework/boot/target/boot-current-SNAPSHOT.jar").toURL();
    URL[] urls = new URL[] { url };
    ClassLoader cl = new URLClassLoader(urls);
    Class cls = cl.loadClass("com.test.Boot");
    Class cls = cl.loadClass("com.launcher.PropertiesLauncherInternal");

For the first loop I get the following log:

BOOT-INF/--1
BOOT-INF/classes/--1
BOOT-INF/classes/com/-0
BOOT-INF/classes/com/test/-0
BOOT-INF/classes/com/test/Boot.class-2405822989
...
com\launcher\PropertiesLauncherInternal.class--1

The number next to the class names are the CRC. I am not sure whether is relevant, but the CRC-32 is unknown.

When using the classloader, I am able to load com.test.Boot but it fails on ClassNotFoundException for the PropertiesLauncherInternal.class


Solution

  • Rather than having it alongside your application code, your launcher code should be in a separate module that's declared as a dependency of Spring Boot's Maven plugin. This separate module should use Maven's standard jar packaging and shouldn't be repackaged using Spring Boot's Maven plugin.

    There's a sample that shows how to set things up.