Consistently return absolute path from ClassPathResource#getPath

Prior to this commit, the Javadoc for the getPath() method in
ClassPathResource stated the following.

> Return the path for this resource (as resource path within the class path).

That implied the returned path was an "absolute path" within the class
path; however, that was not always true.

If the resource was created using ClassPathResource(String) or
ClassPathResource(String, ClassLoader), the returned path was a cleaned
version of the ABSOLUTE PATH supplied to the constructor, WITHOUT a
leading slash.

If the resource was created using ClassPathResource(String, Class) with
an absolute path, the returned path was a cleaned version of the
ABSOLUTE PATH supplied to the constructor, WITH a leading slash.

If the resource was created using ClassPathResource(String, Class) with
a relative path, the returned path was a cleaned version of the
RELATIVE PATH supplied to the constructor.

In addition, ClassPathResource does not provide public access the Class
passed to the ClassPathResource(String, Class) constructor.

Consequently, the path returned by getPath() could not be reliably used
with ClassLoader.getResource(String) or with the recently introduced
registerResource(Resource) method in ResourceHints.

This commit addresses this issue by ensuring that getPath()
consistently returns the absolute path within the class path without a
leading slash.

See gh-29083
Reverts gh-29094
Closes gh-29099
This commit is contained in:
Sam Brannen
2022-09-12 16:35:56 +02:00
parent 0dba729823
commit 138f9c64be
3 changed files with 16 additions and 41 deletions

View File

@@ -118,11 +118,11 @@ public class ResourceHints {
* @throws IllegalArgumentException if the supplied resource is not a
* {@link ClassPathResource} or does not {@linkplain Resource#exists() exist}
* @see #registerPattern(String)
* @see ClassPathResource#getAbsolutePath()
* @see ClassPathResource#getPath()
*/
public void registerResource(Resource resource) {
if (resource instanceof ClassPathResource classPathResource && classPathResource.exists()) {
registerPattern(classPathResource.getAbsolutePath());
registerPattern(classPathResource.getPath());
}
else {
throw new IllegalArgumentException("Resource must be a ClassPathResource that exists: " + resource);

View File

@@ -45,6 +45,10 @@ import org.springframework.util.StringUtils;
*/
public class ClassPathResource extends AbstractFileResolvingResource {
/**
* Internal representation of the original path supplied by the user,
* used for creating relative paths and resolving URLs and InputStreams.
*/
private final String path;
private final String absolutePath;
@@ -119,44 +123,13 @@ public class ClassPathResource extends AbstractFileResolvingResource {
/**
* Return the path for this resource.
* <p>If this resource was created using
* {@link ClassPathResource#ClassPathResource(String) ClassPathResource(String)}
* or {@link ClassPathResource#ClassPathResource(String, ClassLoader)
* ClassPathResource(String, ClassLoader)}, the returned path is a
* {@linkplain StringUtils#cleanPath(String) cleaned} version of the
* <em>absolute path</em> supplied to the constructor, <strong>without</strong>
* a leading slash.
* <p>If this resource was created using
* {@link ClassPathResource#ClassPathResource(String, Class)
* ClassPathResource(String, Class)} with an absolute path, the returned path
* is a {@linkplain StringUtils#cleanPath(String) cleaned} version of the
* <em>absolute path</em> supplied to the constructor, <strong>with</strong>
* a leading slash.
* <p>If this resource was created using
* {@link ClassPathResource#ClassPathResource(String, Class)
* ClassPathResource(String, Class)} with a relative path, the returned path
* is a {@linkplain StringUtils#cleanPath(String) cleaned} version of the
* <em>relative path</em> supplied to the constructor.
* <p>The path returned by this method cannot be reliably used with
* {@link ClassLoader#getResource(String)}.
* <p>If you consistently need the <em>absolute path</em>, use
* {@link #getAbsolutePath()} instead.
* @see #getAbsolutePath()
* Return the <em>absolute path</em> for this resource, as a
* {@linkplain StringUtils#cleanPath(String) cleaned} resource path within
* the class path.
* <p>The path returned by this method does not have a leading slash and is
* suitable for use with {@link ClassLoader#getResource(String)}.
*/
public final String getPath() {
return this.path;
}
/**
* Return the <em>absolute path</em> for this resource, as a resource path
* within the class path without a leading slash.
* <p>The path returned by this method is suitable for use with
* {@link ClassLoader#getResource(String)}.
* @since 6.0
* @see #getPath()
*/
public final String getAbsolutePath() {
return this.absolutePath;
}

View File

@@ -103,9 +103,11 @@ class ClassPathResourceTests {
}
@Test
void preserveLeadingSlashForClassRelativeAccess() {
assertThat(new ClassPathResource("/test.html", getClass()).getPath()).isEqualTo("/test.html");
assertThat(((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath()).isEqualTo("/test.html");
void convertToAbsolutePathForClassRelativeAccess() {
assertThat(new ClassPathResource("/test.html", getClass()).getPath()).isEqualTo("test.html");
assertThat(new ClassPathResource("", getClass()).getPath()).isEqualTo(PACKAGE_PATH + "/");
assertThat(((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath()).isEqualTo("test.html");
assertThat(((ClassPathResource) new ClassPathResource("", getClass()).createRelative("test.html")).getPath()).isEqualTo(PACKAGE_PATH + "/test.html");
}
@Test