spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.4k stars 40.74k forks source link

Support application images with jlink #24889

Closed nipafx closed 3 years ago

nipafx commented 3 years ago

I'm working on a small Spring Boot app and would like to create a self-contained application image with jlink.

What I tried

I tried several different approaches, but all of them failed.

Proper module-info.java

My first try was to create my own module-info.java, but that gets moved into the fat JAR's root without further changes. That means it still lists the Spring modules as dependencies, but since they're now included in the JAR, they won't be found on any module path and thus jlink refuses to create the image.

There are hacky ways around that (creating empty JARs with the right module names), but I suspect this would lead to the same problems as the next approach.

Using Moditect on fat JAR

With Moditect, I can inject a module declaration into the fat JAR. That's actually pretty easy because there are no external dependencies.

Unfortunately, jlink refuses to create the image because of the BOOT-INF folder:

[INFO] --- moditect-maven-plugin:1.0.0.RC1:create-runtime-image (create-runtime-image) @ calendar ---
[ERROR] Error: java.lang.IllegalArgumentException: BOOT-INF.classes.dev.nipafx.calendar.spring:
        Invalid package name: 'BOOT-INF' is not a Java identifier

(I expect the approach above would fail at this step as well even if the problem with the module descriptor could be solved.)

Using Moditext on all JARs

I considered configuring Spring Boot to not create a fat JAR. My goal was to then use Moditect to create a module descriptor for all (transitive) dependencies of the app and thus use jlink on all of them. This may work, but due to the sheer number of dependencies and my (possibly faulty) impression that I need to configure the declaration for all of them. That's a lot of work.

Conclusion

It seems that there's no (good) way to create an application image that contains a Spring Boot app. That's a shame because that's a pretty nifty feature. It would be nice if Spring Boot offered at least one path to application image heaven. 😁

wilkinsona commented 3 years ago

@nipafx Have you tried using Maven's shade plugin to build the jar and then pointing jlink at that? It would remove the problem created by BOOT-INF while also avoiding the need to deal with all of the dependencies.

nipafx commented 3 years ago

Thanks for the quick reply, I will try that.

I assume there's a reason, why Spring Boot doesn't just shade. Ignoring the module stuff, are there any complications I should expect with this approach? A quick Google search shows a few hits, but no official documentation. If you have an authoritative source for this, it would be greatly appreciated. 😃

wilkinsona commented 3 years ago

There's a little bit in the reference documentation about some limitations of shading.

spring-boot-starter-parent contains some configuration for Maven's Shade plugin that you can use in your app. Here's an example pom that uses this configuration:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>shaded</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shaded</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <start-class>com.example.shaded.ShadedApplication</start-class>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Note the start-class property and the declaration of the maven-shade-plugin. package will now build a shaded jar that can be executed with java -jar.

spring-projects-issues commented 3 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

spring-projects-issues commented 3 years ago

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

dwhitla commented 3 years ago

If we put our heads in the sand Java9+ will just go away .... sigh

wilkinsona commented 3 years ago

@dwhitla Such comments are not helpful. Furthermore, I can't see anything in the above that suggests anyone is putting their head in the sand. We suggested an approach for using jlink that we know works and didn't get a response. If you have something constructive to suggest then we're more than happy to hear it. If you're just looking to vent some frustration, please be considerate and do it elsewhere.

jordanst3wart commented 2 years ago

So this seems to work:

jdeps -s target/$FAT_JAR # look at JVM dependencies

jlink --output app --add-modules java.logging,java.base # found with jdeps -s

# copy app into image
mkdir app/jars
cp target/$FAT_JAR app/jars/app.jar

# launch
app/bin/java -cp app/jars/app.jar org.springframework.boot.loader.JarLauncher

That's from the article by @nipafx https://medium.com/nipafx-news/jlinked-spring-boot-packages-vs-folders-king-kong-7035e6543ec4