The spring-boot-loader
modules lets Spring Boot support executable jar and
war files. If you use the Maven plugin or the Gradle plugin, executable jars are
automatically generated, and you generally do not need to know the details of how
they work.
If you need to create executable jars from a different build system or if you are just curious about the underlying technology, this section provides some background.
Java does not provide any standard way to load nested jar files (that is, jar files that are themselves contained within a jar). This can be problematic if you need to distribute a self-contained application that can be run from the command line without unpacking.
To solve this problem, many developers use “shaded” jars. A shaded jar packages all classes, from all jars, into a single “uber jar”. The problem with shaded jars is that it becomes hard to see which libraries are actually in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars. Spring Boot takes a different approach and lets you actually nest jars directly.
Spring Boot Loader-compatible jar files should be structured in the following way:
example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar
Application classes should be placed in a nested BOOT-INF/classes
directory.
Dependencies should be placed in a nested BOOT-INF/lib
directory.
Spring Boot Loader-compatible war files should be structured in the following way:
example.war | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-WEB-INF +-classes | +-com | +-mycompany | +-project | +-YourClasses.class +-lib | +-dependency1.jar | +-dependency2.jar +-lib-provided +-servlet-api.jar +-dependency3.jar
Dependencies should be placed in a nested WEB-INF/lib
directory. Any dependencies
that are required when running embedded but are not required when deploying to
a traditional web container should be placed in WEB-INF/lib-provided
.
The core class used to support loading nested jars is
org.springframework.boot.loader.jar.JarFile
. It lets you load jar
content from a standard jar file or from nested child jar data. When first loaded, the
location of each JarEntry
is mapped to a physical file offset of the outer jar, as
shown in the following example:
myapp.jar +-------------------+-------------------------+ | /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar | |+-----------------+||+-----------+----------+| || A.class ||| B.class | C.class || |+-----------------+||+-----------+----------+| +-------------------+-------------------------+ ^ ^ ^ 0063 3452 3980
The preceding example shows how A.class
can be found in /BOOT-INF/classes
in
myapp.jar
at position 0063
. B.class
from the nested jar can actually be found in
myapp.jar
at position 3452
, and C.class
is at position 3980
.
Armed with this information, we can load specific nested entries by seeking to the appropriate part of the outer jar. We do not need to unpack the archive, and we do not need to read all entry data into memory.
Spring Boot Loader strives to remain compatible with existing code and libraries.
org.springframework.boot.loader.jar.JarFile
extends from java.util.jar.JarFile
and
should work as a drop-in replacement. The getURL()
method returns a URL
that
opens a connection compatible with java.net.JarURLConnection
and can be used with Java’s
URLClassLoader
.
The org.springframework.boot.loader.Launcher
class is a special bootstrap class that
is used as an executable jar’s main entry point. It is the actual Main-Class
in your jar
file, and it is used to setup an appropriate URLClassLoader
and ultimately call your
main()
method.
There are three launcher subclasses (JarLauncher
, WarLauncher
, and
PropertiesLauncher
). Their purpose is to load resources (.class
files and so on.) from
nested jar files or war files in directories (as opposed to those explicitly on the
classpath). In the case of JarLauncher
and WarLauncher
, the nested paths are fixed.
JarLauncher
looks in BOOT-INF/lib/
, and WarLauncher
looks in WEB-INF/lib/
and
WEB-INF/lib-provided/
. You can add extra jars in those locations if you want more. The
PropertiesLauncher
looks in BOOT-INF/lib/
in your application archive by default, but
you can add additional locations by setting an environment variable called LOADER_PATH
or loader.path
in loader.properties
(which is a comma-separated list of directories,
archives, or directories within archives).
You need to specify an appropriate Launcher
as the Main-Class
attribute of
META-INF/MANIFEST.MF
. The actual class that you want to launch (that is, the class that
contains a main
method) should be specified in the Start-Class
attribute.
The following example shows a typical MANIFEST.MF
for an executable jar file:
Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.mycompany.project.MyApplication
For a war file, it would be as follows:
Main-Class: org.springframework.boot.loader.WarLauncher Start-Class: com.mycompany.project.MyApplication
Note | |
---|---|
You need not specify |
PropertiesLauncher
has a few special features that can be enabled with external
properties (System properties, environment variables, manifest entries, or
loader.properties
). The following table describes these properties:
Key | Purpose |
---|---|
| Comma-separated Classpath, such as |
| Used to resolve relative paths in |
| Default arguments for the main method (space separated). |
| Name of main class to launch (for example, |
| Name of properties file (for example, |
| Path to properties file (for example, |
| Boolean flag to indicate that all properties should be added to System properties
It defaults to |
When specified as environment variables or manifest entries, the following names should be used:
Key | Manifest entry | Environment variable |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Tip | |
---|---|
Build plugins automatically move the |
The following rules apply to working with PropertiesLauncher
:
loader.properties
is searched for in loader.home
, then in the root of the
classpath, and then in classpath:/BOOT-INF/classes
. The first location where a file
with that name exists is used.loader.home
is the directory location of an additional properties file
(overriding the default) only when loader.config.location
is not specified.loader.path
can contain directories (which are scanned recursively for jar and zip
files), archive paths, a directory within an archive that is scanned for jar files (for
example, dependencies.jar!/lib
), or wildcard patterns (for the default JVM behavior).
Archive paths can be relative to loader.home
or anywhere in the file system with a
jar:file:
prefix.loader.path
(if empty) defaults to BOOT-INF/lib
(meaning a local directory or a
nested one if running from an archive). Because of this, PropertiesLauncher
behaves
the same as JarLauncher
when no additional configuration is provided.loader.path
can not be used to configure the location of loader.properties
(the
classpath used to search for the latter is the JVM classpath when PropertiesLauncher
is launched).loader.properties
, the exploded archive
manifest, and the archive manifest.You need to consider the following restrictions when working with a Spring Boot Loader packaged application:
ZipEntry
for a nested jar must be saved by using the ZipEntry.STORED
method. This
is required so that we can seek directly to individual content within the nested jar.
The content of the nested jar file itself can still be compressed, as can any other
entries in the outer jar.Thread.getContextClassLoader()
when loading classes
(most libraries and frameworks do so by default). Trying to load nested jar
classes with ClassLoader.getSystemClassLoader()
fails.
java.util.Logging
always uses the system classloader. For this reason, you should
consider a different logging implementation.If the preceding restrictions mean that you cannot use Spring Boot Loader, consider the following alternatives: