-
Notifications
You must be signed in to change notification settings - Fork 40.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make it easier to package certain content in the root of a fat jar #6626
Comments
in spring boot 1.4 we could have a deployable be a dependency to another project. Now it can't cause being a dependency maven isn't able to find the classes because they are located at |
@10168852 this is unrelated to this issue and I frankly consider this to be an improvement. Using a fat jar as a dependency means that you take the complete dependency tree with it. That deployable of yours must be quite large. Please have a look to this stackoverflow thread. |
#2268 is somewhat related to this |
@wilkinsona We are facing the issue you mentioned above as we currently use the fat jar for Newrelic's javaagent. We recently upgraded to 1.4 version of Spring Boot and due to the different structure, it's not working anymore. Could you please elaborate a bit more on "You can work around it by using a separate module to consume the fat jar and add the agent to it"? I know it's probably unrelated here, but it would be great if you can provide the alternative while you are discussing the issue. Thanks! |
Here's an example with Gradle that doesn't require a separate module. The buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.4.1.RELEASE'
}
}
repositories {
mavenCentral()
}
apply plugin: 'spring-boot'
springBoot {
mainClass 'com.example.Main'
}
configurations {
newrelic
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
newrelic 'com.newrelic.agent.java:newrelic-agent:3.12.1'
}
task extractManifest(type: Copy) {
dependsOn bootRepackage
from(zipTree(jar.outputs.files.singleFile))
include 'META-INF/MANIFEST.MF'
into 'build/extracted'
}
task jarWithAgent(type: Jar) {
dependsOn extractManifest
classifier 'with-agent'
entryCompression ZipEntryCompression.STORED
from(
zipTree(jar.outputs.files.singleFile),
zipTree(configurations.newrelic.singleFile)
)
manifest {
from new File(extractManifest.outputs.files.singleFile, '/META-INF/MANIFEST.MF')
attributes([
'Premain-Class': 'com.newrelic.bootstrap.BootstrapAgent',
'Can-Redefine-Classes': 'true',
'Can-Retransform-Classes': 'true'
])
}
} |
@wilkinsona is there at tip to achieve same thing using maven? |
Another use case for this is Cloud Foundry's pre-runtime hooks that need to be placed in the root of an application's directory. When you're pushing a fat jar, that means they need to go in the root of the archive. That's trickier in 1.4 as anything that was in |
Here's an example for the New Relic agent based on a prototype for this issue: configurations {
newrelic
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
newrelic 'com.newrelic.agent.java:newrelic-agent:3.12.1'
}
jar {
// Include the contents of the New Relic jar
from(zipTree(configurations.newrelic.singleFile))
// Set the manifest attributes that the agent needs
manifest {
attributes([
'Premain-Class': 'com.newrelic.bootstrap.BootstrapAgent',
'Can-Redefine-Classes': 'true',
'Can-Retransform-Classes': 'true'
])
}
}
bootRepackage {
// rootEntries are those that should not be repackaged. They're set here by
// collecting the names of all the entries in the New Relic jar. They are matched
// using startsWith, for example "com/newrelic" would keep "com/newrelic" and
// anything beneath it in the root of the jar. Using the names of all the entries
// means the configuration is more concise.
rootEntries = new java.util.jar.JarFile(configurations.newrelic.singleFile).entries().collect { it.name }
} |
Upon closer inspection, I'm confused about the Cloud Foundry side of this. @kelapure My understanding is that you're pushing a jar with a My confusion arises because the Cloud Foundry documentation says
And
So either files in @nebhale Can you clarify things from a CF build pack perspective please? |
A more accurate way of writing "does not support" is that their usage is undefined when using the Java Buildpack. Because these hooks have to ability to change the environment of a running application, they can also invalidate assumptions that the buildpack makes about the environment. Therefore, if you choose to use them, any strange behavior in the buildpack or at runtime is your responsibility. The actual functionality of executing these hooks is owned by the container, not the buildpacks though, so they will get executed no matter what. That being said, we publicly and strongly discourage the use of these hooks as they encourage including environment-specific functionality within an application, violating one of the major tenants of 12-Factor applications. For example, using the The Java Buildpack views and encourages integrations such as these to be orthogonal and provides extension points to facilitate that behavior. In fact, New Relic was the very first orthogonal integration we did (it works for any JVM-based application, with no application-specific configuration) and we used it to prove out this idea and design. |
My specific use-case for a From a cloud foundry perspective my view is that runtime hooks should only be relied on specific instances of debugging and troubleshooting and should not be the norm. There are several instances of older frameworks/jars/apps that package content at the root of the jar. Boot should have a provision 1. to allow the packaging of these resources at the root say a .profile.d directory and 2. configure boot to add them to the classpath. I agree this issue is an enhancement not a trivial one though since .profile.d is just one of the use cases of packaging resources at the top of the jar. |
Thanks, @nebhale and @kelapure. From a Cloud Foundry perspective, I'm specifically interested in the
My concern about packaging the
Anything packaged in the root of the jar is automatically on the class path. Specifically, it's on the class path of the system class loader which is the parent of Boot's class loader that's created by the launcher. Note that this means that the technique that's currently being used with the In summary, there are two separate use cases here:
Both use cases can be satisfied by the same solution, however exactly how we might name and document that solution may differ quite a lot depending on which use case the user is interested in. The first use case is sound and the solution can easily be described in terms of marking things that should be visible to the system class loader. The second use case is less sound as it feels like it's using the jar as something of a trojan horse to smuggle the |
@wilkinsona Actually, the buildpack doesn't unpack archives, Cloud Foundry does it. The contract for staging an application is that the buildpack is presented with an exploded archive no matter what. So if you push a folder full of Ruby application, the CLI zips it, sends it over and the server unzips that in a container and presents it to the buildpacks. If you push a JAR file, the CLI just sends it as-is (it actually removes some pieces that it already knows about, but that's an optimization) and the server unzips that in a container and presents it to the buildpacks. My main problem with this whole thing is that the design of the |
Thanks, @nebhale. So, to summarise:
That convinces me that this enhancement should be described solely in terms of packaging entries in the jar such that they are on the class path of the system class loader. |
Another twist on this is that |
I see this is still open for discussion and wondering if anything is going to happen??? I have been searching the forums for a few days as I am keen to upgrade from 1.3.x. The case we use is that we bundle the agent jar with our application as there is a dependency between the two (in our case the Jetty Http2 libs). Tried many different gradle hacks from updating the repackaged jar to defining custom layouts, all seem a bit overkill and brittle. Starting to think that updating the provisioning process to pull in the agent is an easier option, but would have been nice to have the option to include a "custom system classpath" on the repackaged jar. |
Support to exclude some of those resources moving into BOOT-INF/classes is also needed when someone aims to place JSP files under root jar path /META-INF/resources (According to JSP spec, you are allowed to place JSPs under that folder in your jar file). Currently files placed within src/main/resources/META-INF/resources are all moved into BOOT-INF/classes which causes embedded tomcat to fail those JSP files loaded. |
This is a very common use case in replatforming legacy apps where we need to package files outside the app itself for instance when the app relies on the location of a certain file in the container. |
Right now, for those for whom this is important, I would recommend using Gradle rather than Maven to build your application. Alternatively, you can achieve this in a few different ways using Maven as shown in the comments above such as this one from @gabhardt.
As described by @nebhale above, neither the Boot team nor the CF Java Buildpack team recommend using this mechanism to achieve that:
This reasoning extends beyond |
In my case (bundling custom Charset implementations wired via SPI in a boot jar), there's a simple solution:
Hope this helps someone) |
Bumping this because now Cloud Native Buildpacks are in the picture and Spring Boot can build OCI images, which it couldn't back when the issue was first raised. Putting a Procfile at the root of a jar is explicitly supported by the Paketo buildpacks. It's not a common use case, but sometimes you just don't have any other way of tweaking the app to do what you want it to. In my case I wanted to add a new entrypoint to the image. The |
It will work with Gradle as it uses the output of the
It may be impossible with Maven if you're using CNBs at the moment so perhaps this issue needs to be retitled. We wondered a few times if we should rework the Maven CNB support to use the output of repackage as Gradle uses the output of |
For discussion, there is a proposal to address this with an enhancement to the Paketo CNB buildpacks: paketo-buildpacks/procfile#45. |
In light of the build packs enhancement, no changes are need in Boot for Procfile support. |
Any clue to include Procfile in Fatjar for buildpack? Now I use following commands to build Docker image with buildpack.
|
@linux-china The recommended approach is to use a binding to provide the Procfile. See the README for a bit more info. |
Hi, will this finally be fixed/solved so I can use my Spring Boot JAR as a maven dependency to use its public classes? @gebhardt This worked for me:
It merges the two source JARs into one. But mvn deploy will still upload only one JAR and I think it is the one still missing the public classes in the top level directory of the JAR. I want my Spring Boot JAR to be used as dependency and hence I need it to be published with maven deploy. I do not want to create a second project with all the public classes as a library. |
That's not the goal of this issue. The goal of this issue is to make it easier to package additional new content in the root of the jar, not to copy the contents of
The duplication will cause problems with classloading.
You can configure one project to produce two different jars, as described in the documentation. |
Still having issues with the packaging done by spring. Created minimal project to reproduce issue Specifically, I can trigger the issue when the JDK triggers logging before spring is started - for instance by enabling JMX. Java 17, Spring 3.2.2 |
Hi all! If you are running your applications as executable JARs on JRE 9+ (Spring Boot ones or other types), probably Agent Embedder Maven Plugin is what you want. It enables you to embed a java agent in your executable JAR and have it started automatically using the Unique features added by this plugin and unavailable via the original JVM mechanism: You can
Spoiler: I am the author of the plugin and also happen to be the current maintainer of AspectJ. Edit: I forgot to mention, that in the case of the AspectJ weaver, as the JVM starts the agent very early, weaving will be active without extra Spring configuration and should work for all classloaders - no more |
Any news on this? Getting a custom charset loaded in close to impossible with newest versions of java |
@alpapad01 any news would appear in this issue. The current recommendation remains to use Gradle to build your application and an approach such as this one. |
Ok, we ended up with a custom maven plugin to do the merge... Would be nice to have the maven SB plugin have such a feature, being able to merge jars into to the boot jar |
Spring Boot 1.4 has made using this technique for including a Java agent in a fat jar more difficult. The problem is that the agent's classes get repackaged into
BOOT-INF/classes
but they need to stay in the root of the jar.You can work around it by using a separate module to consume the fat jar and add the agent to it but it'd be nice if users didn't have to jump through that extra hoop. One solution would be to add something to the build plugins that allow a user to mark certain classes as having to stay in the root of the jar.
The text was updated successfully, but these errors were encountered: