You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
13 KiB
348 lines
13 KiB
[appendix] |
|
[[executable-jar]] |
|
= The Executable Jar Format |
|
include::attributes.adoc[] |
|
|
|
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 appendix provides some background. |
|
|
|
|
|
|
|
[[executable-jar-nested-jars]] |
|
== Nested JARs |
|
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. |
|
|
|
|
|
|
|
[[executable-jar-jar-file-structure]] |
|
=== The Executable Jar File Structure |
|
Spring Boot Loader-compatible jar files should be structured in the following way: |
|
|
|
[indent=0] |
|
---- |
|
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. |
|
|
|
|
|
|
|
[[executable-jar-war-file-structure]] |
|
=== The Executable War File Structure |
|
Spring Boot Loader-compatible war files should be structured in the following way: |
|
|
|
[indent=0] |
|
---- |
|
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`. |
|
|
|
|
|
|
|
[[executable-jar-war-index-files]] |
|
=== Index Files |
|
Spring Boot Loader-compatible jar and war archives can include additional index files under the `BOOT-INF/` directory. |
|
A `classpath.idx` file can be provided for both jars and wars, and it provides the ordering that jars should be added to the classpath. |
|
The `layers.idx` file can be used only for jars, and it allows a jar to be split into logical layers for Docker/OCI image creation. |
|
|
|
Index files follow a YAML compatible syntax so that they can be easily parsed by third-party tools. |
|
These files, however, are _not_ parsed internally as YAML and they must be written in exactly the formats described below in order to be used. |
|
|
|
|
|
|
|
[[executable-jar-war-index-files-classpath]] |
|
=== Classpath Index |
|
The classpath index file can be provided in `BOOT-INF/classpath.idx`. |
|
It provides a list of jar names (including the directory) in the order that they should be added to the classpath. |
|
Each line must start with dash space (`"-·"`) and names must be in double quotes. |
|
|
|
For example, given the following jar: |
|
|
|
[indent=0] |
|
---- |
|
example.jar |
|
| |
|
+-META-INF |
|
| +-... |
|
+-BOOT-INF |
|
+-classes |
|
| +... |
|
+-lib |
|
+-dependency1.jar |
|
+-dependency2.jar |
|
---- |
|
|
|
The index file would look like this: |
|
|
|
[indent=0] |
|
---- |
|
- "BOOT-INF/lib/dependency2.jar" |
|
- "BOOT-INF/lib/dependency1.jar" |
|
---- |
|
|
|
|
|
|
|
[[executable-jar-war-index-files-layers]] |
|
=== Layer Index |
|
The layers index file can be provided in `BOOT-INF/layers.idx`. |
|
It provides a list of layers and the parts of the jar that should be contained within them. |
|
Layers are written in the order that they should be added to the Docker/OCI image. |
|
Layers names are written as quoted strings prefixed with dash space (`"-·"`) and with a colon (`":"`) suffix. |
|
Layer content is either a file or directory name written as a quoted string prefixed by space space dash space (`"··-·"`). |
|
A directory name ends with `/`, a file name does not. |
|
When a directory name is used it means that all files inside that directory are in the same layer. |
|
|
|
A typical example of a layers index would be: |
|
|
|
[indent=0] |
|
---- |
|
- "dependencies": |
|
- "BOOT-INF/lib/dependency1.jar" |
|
- "BOOT-INF/lib/dependency2.jar" |
|
- "application": |
|
- "BOOT-INF/classes/" |
|
- "META-INF/" |
|
---- |
|
|
|
|
|
|
|
[[executable-jar-jarfile]] |
|
== Spring Boot's "`JarFile`" Class |
|
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: |
|
|
|
[indent=0] |
|
---- |
|
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. |
|
|
|
|
|
|
|
[[executable-jar-jarfile-compatibility]] |
|
=== Compatibility with the Standard Java "`JarFile`" |
|
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`. |
|
|
|
|
|
|
|
[[executable-jar-launching]] |
|
== Launching Executable Jars |
|
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. |
|
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). |
|
|
|
|
|
|
|
[[executable-jar-launcher-manifest]] |
|
=== Launcher Manifest |
|
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: |
|
|
|
[indent=0] |
|
---- |
|
Main-Class: org.springframework.boot.loader.JarLauncher |
|
Start-Class: com.mycompany.project.MyApplication |
|
---- |
|
|
|
For a war file, it would be as follows: |
|
|
|
[indent=0] |
|
---- |
|
Main-Class: org.springframework.boot.loader.WarLauncher |
|
Start-Class: com.mycompany.project.MyApplication |
|
---- |
|
|
|
NOTE: You need not specify `Class-Path` entries in your manifest file. |
|
The classpath is deduced from the nested jars. |
|
|
|
|
|
|
|
[[executable-jar-property-launcher-features]] |
|
== PropertiesLauncher Features |
|
`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 |
|
|
|
| `loader.path` |
|
| Comma-separated Classpath, such as `lib,$\{HOME}/app/lib`. |
|
Earlier entries take precedence, like a regular `-classpath` on the `javac` command line. |
|
|
|
| `loader.home` |
|
| Used to resolve relative paths in `loader.path`. |
|
For example, given `loader.path=lib`, then `${loader.home}/lib` is a classpath location (along with all jar files in that directory). |
|
This property is also used to locate a `loader.properties` file, as in the following example `file:///opt/app` It defaults to `${user.dir}`. |
|
|
|
| `loader.args` |
|
| Default arguments for the main method (space separated). |
|
|
|
| `loader.main` |
|
| Name of main class to launch (for example, `com.app.Application`). |
|
|
|
| `loader.config.name` |
|
| Name of properties file (for example, `launcher`). |
|
It defaults to `loader`. |
|
|
|
| `loader.config.location` |
|
| Path to properties file (for example, `classpath:loader.properties`). |
|
It defaults to `loader.properties`. |
|
|
|
| `loader.system` |
|
| Boolean flag to indicate that all properties should be added to System properties. |
|
It defaults to `false`. |
|
|=== |
|
|
|
When specified as environment variables or manifest entries, the following names should be used: |
|
|
|
|=== |
|
| Key | Manifest entry | Environment variable |
|
|
|
| `loader.path` |
|
| `Loader-Path` |
|
| `LOADER_PATH` |
|
|
|
| `loader.home` |
|
| `Loader-Home` |
|
| `LOADER_HOME` |
|
|
|
| `loader.args` |
|
| `Loader-Args` |
|
| `LOADER_ARGS` |
|
|
|
| `loader.main` |
|
| `Start-Class` |
|
| `LOADER_MAIN` |
|
|
|
| `loader.config.location` |
|
| `Loader-Config-Location` |
|
| `LOADER_CONFIG_LOCATION` |
|
|
|
| `loader.system` |
|
| `Loader-System` |
|
| `LOADER_SYSTEM` |
|
|=== |
|
|
|
TIP: Build plugins automatically move the `Main-Class` attribute to `Start-Class` when the fat jar is built. |
|
If you use that, specify the name of the class to launch by using the `Main-Class` attribute and leaving out `Start-Class`. |
|
|
|
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). |
|
* Placeholder replacement is done from System and environment variables plus the properties file itself on all values before use. |
|
* The search order for properties (where it makes sense to look in more than one place) is environment variables, system properties, `loader.properties`, the exploded archive manifest, and the archive manifest. |
|
|
|
|
|
|
|
[[executable-jar-restrictions]] |
|
== Executable Jar Restrictions |
|
You need to consider the following restrictions when working with a Spring Boot Loader packaged application: |
|
|
|
|
|
|
|
[[executable-jar-zip-entry-compression]] |
|
* Zip entry compression: |
|
The `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. |
|
|
|
|
|
|
|
[[executable-jar-system-classloader]] |
|
* System classLoader: |
|
Launched applications should use `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. |
|
|
|
|
|
|
|
[[executable-jar-alternatives]] |
|
== Alternative Single Jar Solutions |
|
If the preceding restrictions mean that you cannot use Spring Boot Loader, consider the following alternatives: |
|
|
|
* https://maven.apache.org/plugins/maven-shade-plugin/[Maven Shade Plugin] |
|
* http://www.jdotsoft.com/JarClassLoader.php[JarClassLoader] |
|
* https://sourceforge.net/projects/one-jar/[OneJar] |
|
* https://imperceptiblethoughts.com/shadow/[Gradle Shadow Plugin] |
|
|
|
|