Skip to content
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

Rewrite nested jar support code and remove Java 8 support #37668

Closed
Tracked by #36135
philwebb opened this issue Oct 3, 2023 · 9 comments
Closed
Tracked by #36135

Rewrite nested jar support code and remove Java 8 support #37668

philwebb opened this issue Oct 3, 2023 · 9 comments
Assignees
Labels
status: noteworthy A noteworthy issue to call out in the release notes type: enhancement A general enhancement
Milestone

Comments

@philwebb
Copy link
Member

philwebb commented Oct 3, 2023

Our current nested jar support is pretty complicated due to support for earlier versions of Java. There are also a lot of bugs that we're unable to fix with the current implementation. Rewriting our implementation would help us a great deal going forward.

@philwebb philwebb self-assigned this Oct 3, 2023
@philwebb philwebb added the type: enhancement A general enhancement label Oct 3, 2023
@philwebb philwebb added this to the 3.2.x milestone Oct 3, 2023
philwebb added a commit to philwebb/spring-boot that referenced this issue Oct 4, 2023
Rewrite nested jar code to better align with the implementations
provided in Java 17. This update makes two fundamental changes to
the previous implementation:

- Resource cleanup is now handled using the `java.lang.ref.Cleaner`

- Jar URLs now use the form `jar:nested:/my.jar/!nested.jar!/entry`

Unlike the previous `jar:jar:/my,jar!/nested.jar!/entry` URL format,
the new format is compatible with Java's default Jar URL handler.
Specifically, it now only uses a single `jar:` prefix and it no longer
includes multiple `!/` separators.

In addition to the changes above, many of the ancillary classes have
also been refactored and updated to create cleaner APIs.

Closes spring-projectsgh-37668
@philwebb philwebb modified the milestones: 3.2.x, 3.2.0-RC1 Oct 4, 2023
philwebb added a commit that referenced this issue Oct 4, 2023
Attempt to fix CI failures caused by timezone differences and different
JDK versions.

See gh-37668
@philwebb philwebb reopened this Oct 6, 2023
@philwebb
Copy link
Member Author

philwebb commented Oct 6, 2023

Reopening due to Windows build issues:

java.io.IOException: Failed to delete temp directory C:\Windows\TEMP\junit17935825065843241783. The following paths could not be deleted (see suppressed exceptions for details): , test.jar

philwebb added a commit that referenced this issue Oct 7, 2023
Update `NestedJarFile.close()` to call `super.close()` so that the outer
jar file is closed and files can hopefully be deleted on Windows.

See gh-37668
philwebb added a commit that referenced this issue Oct 7, 2023
Update `DefaultCleanerTracking` and `@AssertFileChannelDataBlocksClosed`
to capture and store the source object if it is a `Cleanable` so that
it can be released later.

Although the real cleaner cannot keep a reference to `obj`, it is safe
for us to do so in tests since we are in control of the object lifecycle
and we don't need it to be garbage collected.

This commit also updates the `UrlJarFile` to call the cleaner so that
it can be tracked.

See gh-37668
@philwebb
Copy link
Member Author

philwebb commented Oct 8, 2023

The Windows build is now fixed.

@philwebb philwebb closed this as completed Oct 8, 2023
@philwebb philwebb added the status: noteworthy A noteworthy issue to call out in the release notes label Oct 15, 2023
philwebb added a commit that referenced this issue Oct 19, 2023
Refactor `NestedLocation` so that it holds a `Path` rather than a
`File`.

See gh-37668
wilkinsona added a commit to wilkinsona/spring-boot that referenced this issue Oct 20, 2023
@harveycggit
Copy link

harveycggit commented Jan 18, 2024

Hi, I was using org.springframework.boot.loader.jar.JarFile to access nested jars so I could do some introspection, etc. The class has been removed. Can you advise what replaces it?

ApplicationHome home = new ApplicationHome(SomeClassOfMine.class);
JarFile JarFile = new JarFile(home.getSource())
Enumeration<JarEntry> jarEntries =  jarFile.entries();
while (jarEntries.hasMoreElements()) {
// bunch of code omitted
	 boolean isJar = jarEntry.getName().endsWith(".jar");
	 // recurse to get get any further nested entries
	 if (isJar) {
	           classesFromSpringFatJar(jarFile.getNestedJarFile(jarEntry), upennClassesFromfatJar, packageName);
        }
}

etc etc.

What I need it a handle to all of the jars.

Thanks!
Charles Harvey

@philwebb
Copy link
Member Author

@harveycggit

The new class you want to use is probably org.springframework.boot.loader.jar.NestedJarFile. You can do new NestedJarFile(home.getSource(), jarEntry.getName()).

@harveycggit
Copy link

harveycggit commented Jan 18, 2024

Thank you so much for the quick response @philwebb ! So I can use new NestedJarFile(home.getSource(), jarEntry.getName()) even if the JarEntry is nested in a chain of jars inside home.getSource()? It is unclear to me whether ZipContent.open behaves in a recursive fashion.

@philwebb
Copy link
Member Author

@harveycggit Nesting can only be one level deep. You can't have a nested jar inside a nested jar, you can only have a nested jar inside a regular jar.

@harveycggit
Copy link

@philwebb thank you again for the response. The NestedJarFileConstructor says it will take null for a resource name:

/**
 * Creates a new {@link NestedJarFile} instance to read from the specific
 * {@code File}.
 * @param file the jar file to be opened for reading
 * @param nestedEntryName the nested entry name to open or {@code null}
 * @throws IOException on I/O error
 */
public NestedJarFile(File file, String nestedEntryName) throws IOException {
	this(file, nestedEntryName, null, true, Cleaner.instance);
}

However, it then calls a constructor that throws the following error if you pass null:
if (onlyNestedJars && (nestedEntryName == null || nestedEntryName.isEmpty())) {
throw new IllegalArgumentException("nestedEntryName must not be empty");
}

So, when I am initially loading the resources I am not looking for a particular entry; I need to iterate through all jars and look for classes that implement some interfaces and so on. What should I be passing with the initial load?

new NestedJarFile(home.getSource(), ???)

Thanks again!
Charles

@philwebb
Copy link
Member Author

That's a mistake in the Javadoc I think. I originally supported null but then changed my mind about it. I'd recommend using the regular JDK JarFile class for non-nested jars. I've opened #39252 to fix the docs.

@qsLI
Copy link

qsLI commented Oct 18, 2024

Do you have any performance test data?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: noteworthy A noteworthy issue to call out in the release notes type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants