mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Support "classpath*:" prefix for ResourceLoader#getResource
Introduces a general-purpose consumeContent method on Resource and EncodedResource with special behavior for multi-content resources. Regular getInputStream/getReader calls will expose the merged content of all same-named resources in the classpath. Closes gh-36415
This commit is contained in:
+8
-6
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -194,30 +195,31 @@ public abstract class YamlProcessor {
|
||||
}
|
||||
|
||||
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
|
||||
int count = 0;
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading from YAML: " + resource);
|
||||
}
|
||||
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
|
||||
resource.consumeContent(inputStream -> {
|
||||
Reader reader = new UnicodeReader(inputStream);
|
||||
for (Object object : yaml.loadAll(reader)) {
|
||||
if (object != null && process(asMap(object), callback)) {
|
||||
count++;
|
||||
count.incrementAndGet();
|
||||
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") +
|
||||
logger.debug("Loaded " + count + " document" + (count.get() > 1 ? "s" : "") +
|
||||
" from YAML resource: " + resource);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException ex) {
|
||||
handleProcessError(resource, ex);
|
||||
}
|
||||
return (count > 0);
|
||||
return (count.get() > 0);
|
||||
}
|
||||
|
||||
private void handleProcessError(Resource resource, IOException ex) {
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
@@ -256,14 +255,14 @@ public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader
|
||||
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
try (InputStream is = encodedResource.getResource().getInputStream()) {
|
||||
encodedResource.getResource().consumeContent(is -> {
|
||||
if (encodedResource.getEncoding() != null) {
|
||||
getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding()));
|
||||
}
|
||||
else {
|
||||
getPropertiesPersister().load(props, is);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
int count = registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription());
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
+11
-6
@@ -21,6 +21,7 @@ import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
@@ -337,12 +338,16 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
|
||||
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
|
||||
InputSource inputSource = new InputSource(inputStream);
|
||||
if (encodedResource.getEncoding() != null) {
|
||||
inputSource.setEncoding(encodedResource.getEncoding());
|
||||
}
|
||||
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
|
||||
try {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
encodedResource.getResource().consumeContent(inputStream -> {
|
||||
InputSource inputSource = new InputSource(inputStream);
|
||||
if (encodedResource.getEncoding() != null) {
|
||||
inputSource.setEncoding(encodedResource.getEncoding());
|
||||
}
|
||||
count.addAndGet(doLoadBeanDefinitions(inputSource, encodedResource.getResource()));
|
||||
});
|
||||
return count.get();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
|
||||
@@ -16,10 +16,19 @@
|
||||
|
||||
package org.springframework.core.io;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -30,6 +39,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.IOConsumer;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link ResourceLoader} interface.
|
||||
@@ -158,6 +168,9 @@ public class DefaultResourceLoader implements ResourceLoader {
|
||||
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
|
||||
}
|
||||
else if (location.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
|
||||
return new ClassPathAllResource(location.substring(CLASSPATH_ALL_URL_PREFIX.length()), getClassLoader());
|
||||
}
|
||||
else {
|
||||
try {
|
||||
// Try to parse the location as a URL...
|
||||
@@ -187,6 +200,96 @@ public class DefaultResourceLoader implements ResourceLoader {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A multi-content ClassPathResource handle that can expose the content
|
||||
* from all matching resources in the classpath.
|
||||
* @since 7.1
|
||||
*/
|
||||
protected static class ClassPathAllResource extends ClassPathResource {
|
||||
|
||||
public ClassPathAllResource(String path, @Nullable ClassLoader classLoader) {
|
||||
super(path, classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() throws IOException {
|
||||
throw new FileNotFoundException(
|
||||
getDescription() + " cannot be resolved to single URL or File - use 'classpath:' instead");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
long combinedLength = 0;
|
||||
ClassLoader cl = getClassLoader();
|
||||
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
|
||||
while (urls.hasMoreElements()) {
|
||||
URLConnection con = urls.nextElement().openConnection();
|
||||
long length = con.getContentLengthLong();
|
||||
if (length < 0) {
|
||||
return -1;
|
||||
}
|
||||
combinedLength += length;
|
||||
}
|
||||
return combinedLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
List<InputStream> streams = new ArrayList<>();
|
||||
ClassLoader cl = getClassLoader();
|
||||
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
|
||||
while (urls.hasMoreElements()) {
|
||||
try {
|
||||
streams.add(urls.nextElement().openStream());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
streams.forEach(stream -> {
|
||||
try {
|
||||
stream.close();
|
||||
}
|
||||
catch (IOException ex2) {
|
||||
ex.addSuppressed(ex2);
|
||||
}
|
||||
});
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return switch (streams.size()) {
|
||||
case 0 -> InputStream.nullInputStream();
|
||||
case 1 -> streams.get(0);
|
||||
default -> new SequenceInputStream(Collections.enumeration(streams));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeContent(IOConsumer<InputStream> consumer) throws IOException {
|
||||
ClassLoader cl = getClassLoader();
|
||||
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
|
||||
while (urls.hasMoreElements()) {
|
||||
try (InputStream inputStream = urls.nextElement().openStream()) {
|
||||
consumer.accept(inputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource createRelative(String relativePath) {
|
||||
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
|
||||
return new ClassPathAllResource(pathToUse, getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "'classpath*:' resource [" + getPath() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ClassPathResource that explicitly expresses a context-relative path
|
||||
* through implementing the ContextResource interface.
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.nio.file.Path;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.function.IOConsumer;
|
||||
|
||||
/**
|
||||
* Interface for a resource descriptor that abstracts from the actual
|
||||
@@ -156,6 +157,26 @@ public interface Resource extends InputStreamSource {
|
||||
return Channels.newChannel(getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the contents of this resource through the given consumer callback.
|
||||
* <p>The given consumer will be invoked a single time by default - but may
|
||||
* also be invoked multiple times in case of a multi-content resource handle,
|
||||
* for example returned from a
|
||||
* {@link ResourceLoader#getResource getResource("classpath*:..."} call.
|
||||
* While {@link #getInputStream()} returns a merged sequence of content
|
||||
* in such a case, this method performs one callback per file content.
|
||||
* @param consumer a consumer for each InputStream
|
||||
* @throws IOException in case of general resolution/reading failures
|
||||
* @since 7.1
|
||||
* @see #getInputStream()
|
||||
* @see ResourceLoader#CLASSPATH_ALL_URL_PREFIX
|
||||
*/
|
||||
default void consumeContent(IOConsumer<InputStream> consumer) throws IOException {
|
||||
try (InputStream inputStream = getInputStream()) {
|
||||
consumer.accept(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of this resource as a byte array.
|
||||
* @return the contents of this resource as byte array
|
||||
|
||||
@@ -42,18 +42,47 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public interface ResourceLoader {
|
||||
|
||||
/** Pseudo URL prefix for loading from the class path: "classpath:". */
|
||||
/**
|
||||
* Pseudo URL prefix for loading from the class path: "classpath:".
|
||||
* This retrieves the "nearest" matching resource in the classpath.
|
||||
* @see ClassLoader#getResource
|
||||
*/
|
||||
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
|
||||
|
||||
/**
|
||||
* Pseudo URL prefix for all matching resources from the class path: {@code "classpath*:"}.
|
||||
* <p>This differs from the common {@link #CLASSPATH_URL_PREFIX "classpath:"} prefix
|
||||
* in that it retrieves all matching resources for a given path. For example, to
|
||||
* locate all "messages.properties" files in the root of all deployed JAR files
|
||||
* you can use the location pattern {@code "classpath*:/messages.properties"}.
|
||||
* <p>As of Spring Framework 6.0, the semantics for the {@code "classpath*:"}
|
||||
* prefix have been expanded to include the module path as well as the class path.
|
||||
* <p>As of Spring Framework 7.1, this prefix is supported for {@link #getResource}
|
||||
* calls as well (exposing a multi-content resource handle), rather than just for
|
||||
* {@link org.springframework.core.io.support.ResourcePatternResolver#getResources}.
|
||||
* @since 7.1 (previously only declared on the
|
||||
* {@link org.springframework.core.io.support.ResourcePatternResolver} sub-interface)
|
||||
* @see ClassLoader#getResources
|
||||
* @see Resource#consumeContent
|
||||
*/
|
||||
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@code Resource} handle for the specified resource location.
|
||||
* <p>The handle should always be a reusable resource descriptor,
|
||||
* allowing for multiple {@link Resource#getInputStream()} calls.
|
||||
* <p><ul>
|
||||
* <li>Must support fully qualified URLs, for example, "file:C:/test.dat".
|
||||
* <li>Must support classpath pseudo-URLs, for example, "classpath:test.dat".
|
||||
* <li>Should support relative file paths, for example, "WEB-INF/test.dat".
|
||||
* <li>Must support fully qualified URLs, for example, "file:C:/test.properties".
|
||||
* <li>Must support classpath pseudo-URLs, for example, "classpath:test.properties".
|
||||
* (Exposing the "nearest" resource in the classpath; see {@link ClassLoader#getResource}.)
|
||||
* <li>Should support classpath-all URLs, for example, "classpath*:test.properties".
|
||||
* (If supported, the returned {@code Resource} needs to expose the entire content of
|
||||
* all same-named resources in the classpath through {@link Resource#consumeContent};
|
||||
* see {@link ClassLoader#getResources}.
|
||||
* For individual access to each such matching resource in the classpath, use
|
||||
* {@link org.springframework.core.io.support.ResourcePatternResolver#getResources}.)
|
||||
* <li>Should support relative file paths, for example, "WEB-INF/test.properties".
|
||||
* (This will be implementation-specific, typically provided by an
|
||||
* ApplicationContext implementation.)
|
||||
* </ul>
|
||||
@@ -63,7 +92,7 @@ public interface ResourceLoader {
|
||||
* @return a corresponding {@code Resource} handle (never {@code null})
|
||||
* @see #CLASSPATH_URL_PREFIX
|
||||
* @see Resource#exists()
|
||||
* @see Resource#getInputStream()
|
||||
* @see Resource#consumeContent
|
||||
*/
|
||||
Resource getResource(String location);
|
||||
|
||||
|
||||
+43
-20
@@ -26,8 +26,10 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.function.IOConsumer;
|
||||
|
||||
/**
|
||||
* Holder that combines a {@link Resource} descriptor with a specific encoding
|
||||
@@ -125,26 +127,6 @@ public class EncodedResource implements InputStreamSource {
|
||||
return (this.encoding != null || this.charset != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a {@code java.io.Reader} for the specified resource, using the specified
|
||||
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
|
||||
* (if any).
|
||||
* @throws IOException if opening the Reader failed
|
||||
* @see #requiresReader()
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
public Reader getReader() throws IOException {
|
||||
if (this.charset != null) {
|
||||
return new InputStreamReader(this.resource.getInputStream(), this.charset);
|
||||
}
|
||||
else if (this.encoding != null) {
|
||||
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
|
||||
}
|
||||
else {
|
||||
return new InputStreamReader(this.resource.getInputStream());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an {@code InputStream} for the specified resource, ignoring any specified
|
||||
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}.
|
||||
@@ -157,6 +139,47 @@ public class EncodedResource implements InputStreamSource {
|
||||
return this.resource.getInputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a {@code java.io.Reader} for the specified resource, using the specified
|
||||
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
|
||||
* (if any).
|
||||
* @throws IOException if opening the Reader failed
|
||||
* @see #requiresReader()
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
public Reader getReader() throws IOException {
|
||||
return getReader(this.resource.getInputStream());
|
||||
}
|
||||
|
||||
private Reader getReader(InputStream inputStream) throws IOException {
|
||||
if (this.charset != null) {
|
||||
return new InputStreamReader(inputStream, this.charset);
|
||||
}
|
||||
else if (this.encoding != null) {
|
||||
return new InputStreamReader(inputStream, this.encoding);
|
||||
}
|
||||
else {
|
||||
return new InputStreamReader(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the contents of this resource through the given consumer callback.
|
||||
* <p>The given consumer will be invoked a single time by default - but may
|
||||
* also be invoked multiple times in case of a multi-content resource handle,
|
||||
* for example returned from a
|
||||
* {@link ResourceLoader#getResource getResource("classpath*:..."} call.
|
||||
* While {@link #getReader()} returns a merged sequence of content
|
||||
* in such a case, this method performs one callback per file content.
|
||||
* @param consumer a consumer for each Reader
|
||||
* @throws IOException in case of general resolution/reading failures
|
||||
* @since 7.1
|
||||
* @see Resource#consumeContent
|
||||
*/
|
||||
public void consumeContent(IOConsumer<Reader> consumer) throws IOException {
|
||||
this.resource.consumeContent(inputStream -> consumer.accept(getReader(inputStream)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of the specified resource as a string, using the specified
|
||||
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} (if any).
|
||||
|
||||
-12
@@ -57,18 +57,6 @@ import org.springframework.core.io.ResourceLoader;
|
||||
*/
|
||||
public interface ResourcePatternResolver extends ResourceLoader {
|
||||
|
||||
/**
|
||||
* Pseudo URL prefix for all matching resources from the class path: {@code "classpath*:"}.
|
||||
* <p>This differs from ResourceLoader's {@code "classpath:"} URL prefix in
|
||||
* that it retrieves all matching resources for a given path — for
|
||||
* example, to locate all "beans.xml" files in the root of all deployed JAR
|
||||
* files you can use the location pattern {@code "classpath*:/beans.xml"}.
|
||||
* <p>As of Spring Framework 6.0, the semantics for the {@code "classpath*:"}
|
||||
* prefix have been expanded to include the module path as well as the class path.
|
||||
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
|
||||
*/
|
||||
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
|
||||
|
||||
/**
|
||||
* Resolve the given location pattern into {@code Resource} objects.
|
||||
* <p>Overlapping resource entries that point to the same physical
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.util.function;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Common functional interface for I/O content consumption, for example
|
||||
* consuming an {@link java.io.InputStream} or a {@link java.io.Reader}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.1
|
||||
* @param <C> the type of stream/reader
|
||||
* @see Resource#consumeContent
|
||||
* @see org.springframework.core.io.support.EncodedResource#consumeContent
|
||||
* @see ThrowingConsumer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IOConsumer<C> extends Consumer<C> {
|
||||
|
||||
/**
|
||||
* Performs this operation on the given argument, possibly throwing
|
||||
* an {@link IOException}.
|
||||
* @param content the stream/reader
|
||||
* @throws IOException on error
|
||||
*/
|
||||
void acceptWithException(C content) throws IOException;
|
||||
|
||||
/**
|
||||
* Default {@link Consumer#accept(Object)} that wraps any thrown
|
||||
* {@link IOException} in an {@link UncheckedIOException}.
|
||||
* @see java.util.function.Consumer#accept(Object)
|
||||
*/
|
||||
default void accept(C input) {
|
||||
try {
|
||||
acceptWithException(input);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -21,6 +21,8 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -55,6 +57,7 @@ import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
@@ -100,6 +103,48 @@ class PathMatchingResourcePatternResolverTests {
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class ClassPathAll {
|
||||
|
||||
@Test
|
||||
void getResourcesVsGetResource() throws IOException {
|
||||
StringBuilder content1 = new StringBuilder(200000);
|
||||
long length1 = 0;
|
||||
Resource[] resources1 = resolver.getResources("classpath*:META-INF/MANIFEST.MF");
|
||||
for (Resource resource : resources1) {
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.isReadable()).isTrue();
|
||||
assertThat(resource.isFile()).isFalse();
|
||||
length1 += resource.contentLength();
|
||||
resource.consumeContent(inputStream ->
|
||||
content1.append(FileCopyUtils.copyToString(new InputStreamReader(inputStream))));
|
||||
}
|
||||
String content = content1.toString();
|
||||
|
||||
StringBuilder content2 = new StringBuilder(200000);
|
||||
Resource resource2 = resolver.getResource("classpath*:META-INF/MANIFEST.MF");
|
||||
assertThat(resource2.exists()).isTrue();
|
||||
assertThat(resource2.isReadable()).isTrue();
|
||||
assertThat(resource2.isFile()).isFalse();
|
||||
resource2.consumeContent(inputStream ->
|
||||
content2.append(FileCopyUtils.copyToString(new InputStreamReader(inputStream))));
|
||||
assertThat(content.contentEquals(content2)).isTrue();
|
||||
assertThat(resource2.contentLength()).isEqualTo(length1);
|
||||
|
||||
String content3 = FileCopyUtils.copyToString(new InputStreamReader(resource2.getInputStream()));
|
||||
assertThat(content.contentEquals(content3)).isTrue();
|
||||
|
||||
String content4 = new EncodedResource(resource2).getContentAsString();
|
||||
assertThat(content.contentEquals(content4)).isTrue();
|
||||
|
||||
StringWriter content5 = new StringWriter(200000);
|
||||
EncodedResource resource5 = new EncodedResource(resource2);
|
||||
resource5.consumeContent(reader -> FileCopyUtils.copy(reader, content5));
|
||||
assertThat(content.contentEquals(content5.getBuffer())).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class FileSystemResources {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user