6

I have a Spring Boot 1.2 app packaged as a WAR because I need to be able to deploy the app in an app server.

I also want to configure an external path which will contain jars to be added to the classpath. After reading the Launcher documentation, I configured the build to use PropertiesLauncher to this end :

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    ...
    <layout>ZIP</layout>
  </configuration>
</plugin>

I tried to start the app with various combinations of this additional system property : -Dloader.path=lib/,lib-provided/,WEB-INF/classes,<my additional path>

But I always end up with this error :

java.lang.IllegalArgumentException: Invalid source folder C:\<path to my war>\<my war>.war
    at org.springframework.boot.loader.archive.ExplodedArchive.<init> ExplodedArchive.java:78)
    at org.springframework.boot.loader.archive.ExplodedArchive.<init>(ExplodedArchive.java:66)
    at org.springframework.boot.loader.PropertiesLauncher.addParentClassLoaderEntries(PropertiesLauncher.java:530)
    at org.springframework.boot.loader.PropertiesLauncher.getClassPathArchives(PropertiesLauncher.java:451)
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
    at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:609)

I looked at the source code and it seems that PropertiesLauncher can only handle jar archives (ending with ".jar" or ".zip") and "exploded archives" (not ending with the former)

Is it possible to do achieve what I want ? Am I doing it wrong ?

If it's not possible, which alternative is there ?

gregfqt
  • 242
  • 3
  • 14
  • There are different launchers for different packaging formats: http://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#executable-jar-launching - I don't think you can combine the properties (jar) launcher with a war packaged application. Do you want a standalone version in addition to a war or do you try to add something to a war that doesn't come from the regular maven dependencies? – zapl Nov 27 '15 at 12:09
  • I want at runtime to add jars which do not come from maven dependencies, which I cannot do with [Jar|War]Launcher AFAIK. And I _also_ need both standalone + war version – gregfqt Nov 27 '15 at 13:12
  • There is the concept of provided dependencies (in maven etc). In a war environment everything that your server provides. I don't know how you would add dependencies at runtime to a war application (that aren't in the war) other than adding them to the server directly. I also don't think that you can / should specify paths where the war expects dependencies from inside the war, as that would break the whole application encapsulation. – zapl Nov 27 '15 at 13:23
  • I can't use "provided" scope because I do not know in advance which jars will be added to the classpath (these are plugins, in fact). For example, Tomcat offers the possibility to configure [external classpath resources](http://stackoverflow.com/questions/23143697/adding-external-resources-to-class-path-in-tomcat-8) which are added to a webapp's classpath – gregfqt Nov 27 '15 at 13:37
  • Isn't that more a case for standard [SPI](https://parijatmishra.wordpress.com/2012/05/07/realizing-a-service-provider-framework-with-java-ee-cdi/) architecture rather than a problem of packaging? http://stackoverflow.com/questions/24248025/is-there-an-analogue-of-serviceloader-in-spring-and-how-to-use-it maybe – zapl Nov 27 '15 at 13:46

3 Answers3

5

If somebody end up here this might be useful:

java -cp yourSpringBootWebApp.war -Dloader.path=yourSpringBootWebApp.war!/WEB-INF/classes/,yourSpringBootWebApp.war!/WEB-INF/,externalLib.jar org.springframework.boot.loader.PropertiesLauncher

(Spring-Boot 1.5.9)

https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/executable-jar.html#executable-jar-launching

ivsud
  • 51
  • 1
  • 2
  • What if you start your server using command "mvn spring-boot: run" how you pass the path – rahul Feb 04 '19 at 18:14
  • tried to do it. Failed to get the correct `loader.path`. Tried MANY other solutions. Found your answer. Get back to first idea. Spend few hours. Finally get something working. Thank you guy! – Arthur Vaïsse Apr 08 '19 at 14:41
  • This also works on Spring Boot 2.7.5 – Bert Mar 21 '23 at 15:56
1

In Spring Boot 1.2, PropertiesLauncher handles .jar and .zip files as "jar archives" and everything else as "exploded archives" (unzipped jars). It does not properly handles .war

Here's the alternative I found :

I eventually switched back to the regular war launcher and I managed to configure a folder which jar contents are added to the classpath using a SpringApplicationRunListener such as this (pseudo-code for concision) :

public class ClasspathExtender implements SpringApplicationRunListener {

    public void contextPrepared(ConfigurableApplicationContext context) {

        // read jars folder path from environment
        String path = context.getEnvironment().getProperty("my.jars-folder");

        // enumerate jars in the folder
        File[] files = new File(path).listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) { return name.endsWith(".jar"); }
        });

        URL[] urls = // convert files array to urls array

        // create a new classloader which contains the jars...   
        ClassLoader extendedClassloader = new URLClassLoader(urls, context.getClassLoader());

        // and replace the context's classloader
        ((DefaultResourceLoader) context).setClassLoader(extendedClassloader);
    }

    // other methods are empty
}

This listener is instanciated by declaring it in a META-INF/spring.factories file :

org.springframework.boot.SpringApplicationRunListener=my.ClasspathExtender
gregfqt
  • 242
  • 3
  • 14
  • I haven't tried this solution, but reading up on the approach, it seems to me that won't the SpringApplicationRunListener directive in spring.factories not step on the existing listeners? In other words, how will other RunListeners (like EventPublishingRunListener) be configured? Finally, does this approach actually load up all classes in WEB-INF/lib and the lib-provided directories and does it actually work? – Pankaj Tandon Jul 07 '17 at 15:42
  • It did work at the time I wrote the answer without any noticed side effect. It did correctly load the webapp with its dependences from `WEB-INF/lib`. If you want to have a look at a working example, I used this solution in Squash TM 1.13 (www.squashtest.org) – gregfqt Jul 31 '17 at 12:31
-1

This worked for me (Spring Boot 1.3.2)

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    ...
    <layout>WAR</layout>
  </configuration>
</plugin>
Nahla
  • 1
  • I cannot use 1.3 for now but out of curiosity, how did you configure the path where you put extra jars ? – gregfqt Mar 03 '16 at 12:52
  • I tried this option, and the application did not get my external properties. Have you set anything more? What kind of pom have you created such as JAR or WAR? – Carlos Alberto May 26 '16 at 15:13