The loading of resources in Spring is the same thing!

1. Introduction

In JDK, java.net.URL is applicable to the classes that load resources, but the implementation classes of URL access network resources, and there is no way to obtain files and ServletContext from class path or relative path, Although the new handler can be implemented by customizing the extended URL interface, it is very complex. At the same time, the behavior defined in the URL interface is not very ideal, such as the behavior of detecting the existence of resources. This is also why spring has developed a new resource loading strategy, and it also meets the following characteristics:

  • The principle of single responsibility makes the definition of resources and the model boundary of resource loading very clear
  • The strategy and behavior of high abstraction and uniform resource definition and resource loading are adopted. Resource loading returns Abstract resources to the client, and the client processes them concretely according to the resource behavior definition

2. Resource interface

The purpose of the Resource interface in spring is to become a more powerful interface for abstract access to specific resources. It inherits the org.springframework.core.io.InputStreamSource interface. As the top-level interface of Resource definition, Resource internally defines common methods, and has its subclass AbstractResource to provide a unified default implementation,

Resource interface definition:

//Resource definition interface
public interface Resource extends InputStreamSource {

	/**
	 * Verify that the resource is physically present
	 */
	boolean exists();

	/**
	 * Determine whether the resource is readable
	 */
	default boolean isReadable() {
		return exists();
	}

	/**
	 * Judge whether the resource is open. true means open
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * Determine whether the resource is a file true to yes
	 */
	default boolean isFile() {
		return false;
	}

	/**
	 * Returns the URL handle of the resource
	 */
	URL getURL() throws IOException;

	/**
	 * Returns the URI handle of the resource
	 */
	URI getURI() throws IOException;

	/**
	 * Get the File handle of the resource
	 */
	File getFile() throws IOException;

	/**
	 * Returns a ReadableByteChannel as a read channel in NIO
	 */
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	/**
	 * Get the length of resource content
	 */
	long contentLength() throws IOException;

	/**
	 * Return the last modified timestamp of the resource
	 */
	long lastModified() throws IOException;

	/**
	 * Create a new resource based on its relative path
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * Returns the name of the resource
	 */
	@Nullable
	String getFilename();

	/**
	 * Returns the description of the resource
	 */
	String getDescription();

}

InputStreamSource interface definition:

public interface InputStreamSource {

	/**
	 * Return an {@link InputStream} for the content of an underlying resource.
	 * <p>It is expected that each call creates a <i>fresh</i> stream.
	 * <p>This requirement is particularly important when you consider an API such
	 * as JavaMail, which needs to be able to read the stream multiple times when
	 * creating mail attachments. For such a use case, it is <i>required</i>
	 * that each {@code getInputStream()} call returns a fresh stream.
	 * @return the input stream for the underlying resource (must not be {@code null})
	 * @throws java.io.FileNotFoundException if the underlying resource doesn't exist
	 * @throws IOException if the content stream could not be opened
	 */
	InputStream getInputStream() throws IOException;

}

Some of the most important methods in this Resource:

  • getInputStream(): find and open the resource, and return a resource to be read by InputStream. Each call will return a new InputStream, and the caller is responsible for closing the stream

  • exists(): returns a boolean indicating whether the resource actually exists in physical form.

  • isOpen(): return, boolean indicates whether the resource represents a handle with an open stream. If it is true, InputStream cannot be read multiple times. It must be read only once, and then closed to avoid resource leakage. Return false all common resource implementations except InputStreamResource readable

  • getDescription(): returns a description of this resource for error output when using it. This is usually the standard filename or the actual URL of the resource

**Resource implementation**

  • UrlResource: package a java.net.URL, which can be used to access any object that can usually be accessed through the URL, such as file, HTTP target, ftp target, etc. All URLs have a standardized String representation, so an appropriate standardized prefix can be used to indicate another URL type. For example: File: access to file system path, http: access to resources through HTTP protocol ftp, access to resources through ftp, etc

  • ClassPathResource: this class represents the resource that should be obtained from the classpath. It uses a thread context ClassLoader, a given ClassLoader, or a given class to load resources

  • FileSystemResource: it is a Resource that implements encapsulation of java.io.File and java.nio.file.Path type resources. It supports File and URL, implements WritableResource interface, and starts from Spring Framework 5.0, FileSystemResource uses NIO2 API for read / write interaction

  • ServletContextResource: this ServletContex t resource explains the relative path in the root of the relevant Web application.

  • InputStreamResource: the implementation class that takes the given InputStream as a Resource

  • ByteArrayResource: This is the implementation of the byte array given by the Resource. It creates a ByteArrayInputStream for a given byte array

3. ResourceLoader interface

ResourceLoader is mainly used to return (load) Resource objects. It is mainly defined as:

public interface ResourceLoader {

	/** Pseudo URL prefix for loading from the class path: "classpath:". */
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

	/**
	 * Returns the resource processor of the specified path
	 * You must support a fully qualified web address: "file:C:/test.dat"
	 * The URL of ClassPath must be supported: "ClassPath: test. Dat"
	 * Relative path must be supported: "WEB-INF/test.dat"
	 * It does not guarantee the physical existence of resources. You need to check the existence by yourself
	 * All application contexts in spring implement this interface, which can load resources
	 */
	Resource getResource(String location);

	/**
	 * Returns the ClassLoader object of the current class
	 */
	@Nullable
	ClassLoader getClassLoader();

}
  • Application context means that the container has an interface to implement ResourceLoader. All contexts can be used to get Resource instance objects

  • We can get a specific type of Resource instance through getResource() in a specific application context, but we can ensure that the location path has no special prefix, such as classpatch:, etc. if there is a specific prefix that is slow, the corresponding Resource type will be forced to be used, regardless of the context.

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml Load from classpath
file: file:///data/config.xml Load from file system as URL
http: https://myserver/logo.png Load as URL
(none) /data/config.xml Depends on application context

Subclass structure of ResourceLoader:

3.1 DefaultResourceLoader

This class is the default implementation class of ResourceLoader, just like AbstractResource of Resource interface,

3.1.1. Constructor

  • Provide constructors with and without parameters. The constructors with parameters accept ClassLoader type. If there is no parameter, use the default ClassLoader, thread. Currentthread() ාgetcontextclassloader()

Core code code, part of which is omitted:

public class DefaultResourceLoader implements ResourceLoader {

	@Nullable
	private ClassLoader classLoader;

	private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

	private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);

	/**
	 * non-parameter constructor 
	 * @see java.lang.Thread#getContextClassLoader()
	 */
	public DefaultResourceLoader() {
		this.classLoader = ClassUtils.getDefaultClassLoader();
	}

	/**
	 * Parameterized constructor with ClassLoader
	 */
	public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	/**
     * Set ClassLoader
	 */
	public void setClassLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	/**
	 * Return the ClassLoader to load class path resources with.,
	 * @see ClassPathResource
	 */
	@Override
	@Nullable
	public ClassLoader getClassLoader() {
		return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
	}

	/**
	 * Obtain a cache for the given value type, keyed by {@link Resource}.
	 * @param valueType the value type, e.g. an ASM {@code MetadataReader}
	 * @return the cache {@link Map}, shared at the {@code ResourceLoader} level
	 * @since 5.0
	 */
	@SuppressWarnings("unchecked")
	public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
		return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
	}

	/**
	 * Clear all resource caches in this resource loader.
	 * @since 5.0
	 * @see #getResourceCache
	 */
	public void clearResourceCaches() {
		this.resourceCaches.clear();
	}
}

3.1.2 getResource() core method

It is the most core method in ResourceLoader. It returns the corresponding Resource according to the location passed in. DefaultResourceLoader implements it in the core, and the subclass does not cover the method. Therefore, we can conclude that the core strategy of ResourceLoader to load resources is in DefaultResourceLoader

Core code:

    //DefaultResourceLoader.java
    @Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");

		//1. Record resources through the protocol resolver protocol resolver
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}

		//2. If the location starts with /, the resource of ClassPathContextResource type will be returned
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}//3. If it starts with classpath:, the resource of ClassPathResource type is returned
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				//4. If not, determine whether it is a File URL. If not, return FileUrlResource. Otherwise, return UrlResource
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				//5. Finally, return ClassPathContextResource
				return getResourceByPath(location);
			}
		}
	}

The above code specifies the execution process, and the specific implementation code of getResourceByPath(location) is as follows:

	protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}

3.1.3 ProtocolResolver

Fully qualified class name: org.springframework.core.io.ProtocolResolver, an interface for user-defined protocol resource resolution policy, is the SPI of DefaultResourceLoader, which allows processing of custom protocol without implementing the loader (or application context Implementation) as a subclass, that is, it does not need to inherit the subclass DefaultResourceLoader of ResourceLoader, The resource loader can be customized by directly implementing the ProtocolResolver interface

@FunctionalInterface
public interface ProtocolResolver {

	/**
     * Use the specified ResourceLoader to resolve the resources of the location path
	 * Resolve the given location against the given resource loader
	 * if this implementation's protocol matches.
	 * @param location the user-specified resource location
	 * @param resourceLoader the associated resource loader
	 * @return a corresponding {@code Resource} handle if the given location
	 * matches this resolver's protocol, or {@code null} otherwise
	 */
	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);

}

There is no implementation class for this class in spring. It needs to be implemented by the user himself. How can the custom ProtocolResolver be loaded into spring? In our DefaultResourceLoader class, there is a method addProtocolResolver(ProtocolResolver resolver) to add

	/**
	 * Register the given resolver with this resource loader, allowing for
	 * additional protocols to be handled.
	 * <p>Any such resolver will be invoked ahead of this loader's standard
	 * resolution rules. It may therefore also override any default rules.
	 * @since 4.3
	 * @see #getProtocolResolvers()
	 */
	public void addProtocolResolver(ProtocolResolver resolver) {
		Assert.notNull(resolver, "ProtocolResolver must not be null");
		this.protocolResolvers.add(resolver);
	}

3.2 FileSystemResourceLoader

In the DefaultResourceLoader, the getResourceByPath() method directly returns a ClassPathContextResource type resource, which is not perfect. In spring, the FileSystemResourceLoader class inherits the DefaultResourceLoader, rewrites the getResourceByPath() method, uses the standard file system to read in and returns FileSystemContextResource type

public class FileSystemResourceLoader extends DefaultResourceLoader {

	/**
	 * Resolve resource paths as file system paths.
	 * <p>Note: Even if a given path starts with a slash, it will get
	 * interpreted as relative to the current VM working directory.
	 * @param path the path to the resource
	 * @return the corresponding Resource handle
	 * @see FileSystemResource
	 * @see org.springframework.web.context.support.ServletContextResourceLoader#getResourceByPath
	 */
	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemContextResource(path);
	}

	/**
	 * FileSystemResource that explicitly expresses a context-relative path
	 * through implementing the ContextResource interface.
	 */
	private static class FileSystemContextResource extends FileSystemResource implements ContextResource {

		public FileSystemContextResource(String path) {
			super(path);
		}

		@Override
		public String getPathWithinContext() {
			return getPath();
		}
	}

}
  • We can see from the above code that there is a private internal class FileSystemContextResource in the FileSystemResourceLoader, which inherits the FileSystemResource and implements the ContextResource interface

  • FileSystemContextResource calls the constructor of FileSystemResource through the constructor to create the resource definition of FileSystemResource type. At the same time, ContextResource is implemented to implement the getPathWithinContext() method. This method is used to obtain the root path of the following text. The source code reads as follows:

/**

 * Return the path within the enclosing 'context'.  
 * This is typically path relative to a context-specific root directory,  
 * e.g. a ServletContext root or a PortletContext root.  
 */  

3.3 ClassRelativeResourceLoader

The org.springframework.core.io.ClassRelativeResourceLoader class is another implementation subclass of DefaultResourceLoader. Similar to FileSystemResourceLoader, it also rewrites the getResourceByPath() method and internally maintains a private internal class ClassRelativeContextResource. The specific code is as follows:

/**
 * Load resources from given class
 * {@link ResourceLoader} implementation that interprets plain resource paths
 * as relative to a given {@code java.lang.Class}.
 *
 * @author Juergen Hoeller
 * @since 3.0
 * @see Class#getResource(String)
 * @see ClassPathResource#ClassPathResource(String, Class)
 */
public class ClassRelativeResourceLoader extends DefaultResourceLoader {

	private final Class<?> clazz;

	/**
	 * Create a new ClassRelativeResourceLoader for the given class.
	 * @param clazz the class to load resources through
	 */
	public ClassRelativeResourceLoader(Class<?> clazz) {
		Assert.notNull(clazz, "Class must not be null");
		this.clazz = clazz;
		setClassLoader(clazz.getClassLoader());
	}

	/**
	 * Override the getResourceByPath method to return a ClassRelativeContextResource resource type
	 * @param path the path to the resource
	 * @return
	 */
	@Override
	protected Resource getResourceByPath(String path) {
		return new ClassRelativeContextResource(path, this.clazz);
	}

	/**
	 * Inherit ClassPathResource to define resource type, and implement getPathWithinContext method in ContextResource,
	 *
	 * ClassPathResource that explicitly expresses a context-relative path
	 * through implementing the ContextResource interface.
	 */
	private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {

		private final Class<?> clazz;

		/**
		 * Call the parent ClassPathResource to initialize the resource
		 * @param path
		 * @param clazz
		 */
		public ClassRelativeContextResource(String path, Class<?> clazz) {
			super(path, clazz);
			this.clazz = clazz;
		}

		@Override
		public String getPathWithinContext() {
			return getPath();
		}

        /**
		 * Override the method in ClassPathContext to return a ClassRelativeContextResource resource through the given path
		 * @param relativePath the relative path (relative to this resource)
		 * @return
		 */
		@Override
		public Resource createRelative(String relativePath) {
			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
			return new ClassRelativeContextResource(pathToUse, this.clazz);
		}
	}

}

3.4 ResourcePatternResolver

org.springframework.core.io.support.ResourcePatternResolver is an extension of ResourceLoader. When we get Resource instances through getResource method in ResourceLoader, we can only get one Resource through one location, not multiple resources, When we need to load multiple resources, we can only implement it by calling this method multiple times, so spring provides ResourcePatternResolver to extend it, and implements to load multiple resources through location. The definition of the class is as follows:

public interface ResourcePatternResolver extends ResourceLoader {

	/**
	 * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
	 * This differs from ResourceLoader's classpath URL prefix in that it
	 * retrieves all matching resources for a given name (e.g. "/beans.xml"),
	 * for example in the root of all deployed JAR files.
	 * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
	 */
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	/**
	 * Resolve the given location pattern into Resource objects.
	 * <p>Overlapping resource entries that point to the same physical
	 * resource should be avoided, as far as possible. The result should
	 * have set semantics.
	 * @param locationPattern the location pattern to resolve
	 * @return the corresponding Resource objects
	 * @throws IOException in case of I/O errors
	 */
	Resource[] getResources(String locationPattern) throws IOException;

}
  • You can see that ResourcePatternResolver adds a new method getResources to return a Resource array

  • Here we should note that ResourcePatternResolver adds a new protocol prefix, classpath *:. See if you can remember that we often write classpath: and classpath *:, so their differences are here. Their resource loading methods are different

3.5 PathMatchingResourcePatternResolver

org.springframework.core.io.support.PathMatchingResourcePatternResolver is a main implementation class of ResourcePatternResolver, and it also uses many implementation classes. We can see that it mainly implements the resolution of new prefixes, and also supports the path matching mode of Ant grid (such as: * * / *. xml)

3.5.1 constructor

Pathmatchingresourcepratternresolver provides three constructors:

	/**
	 * Built in resource location loader
	 */
	private final ResourceLoader resourceLoader;

	/**
	 * Ant Path matcher
	 */
	private PathMatcher pathMatcher = new AntPathMatcher();

	/**
	 * No parameter constructor, DefaultResourceLoader is the default when internal loader type is not specified
	 * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
	 * <p>ClassLoader access will happen via the thread context class loader.
	 * @see org.springframework.core.io.DefaultResourceLoader
	 */
	public PathMatchingResourcePatternResolver() {
		this.resourceLoader = new DefaultResourceLoader();
	}

	/**
	 * Specify a specific resource location loader
	 * Create a new PathMatchingResourcePatternResolver.
	 * <p>ClassLoader access will happen via the thread context class loader.
	 * @param resourceLoader the ResourceLoader to load root directories and
	 * actual resources with
	 */
	public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
		Assert.notNull(resourceLoader, "ResourceLoader must not be null");
		this.resourceLoader = resourceLoader;
	}

	/**
	 * Use the default resource loader, but pass in classLoader to load with a specific class
	 * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
	 * @param classLoader the ClassLoader to load classpath resources with,
	 * or {@code null} for using the thread context class loader
	 * at the time of actual resource access
	 * @see org.springframework.core.io.DefaultResourceLoader
	 */
	public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
		this.resourceLoader = new DefaultResourceLoader(classLoader);
	}
  • We can see that when the constructor does not provide ResourceLoader, the default is DefaultResourceLoader

3.5.2 getResource

The implementation of getResource method in pathmatchingresourcepratternresolver is to call the incoming ResourceLoader or default ResourceLoader. The specific code implementation is as follows:

/**
	 * Call getResourceLoader to get the current ResourceLoader 
	 * @param location the resource location
	 * @return
	 */
	@Override
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
    }
    
    	/**
	 * Return the ResourceLoader that this pattern resolver works with.
	 */
	public ResourceLoader getResourceLoader() {
		return this.resourceLoader;
	}

3.5.3 getResources

The getResources method of ResourcePatternResolver is implemented. Multiple resources can be loaded through location for classification processing. If there is no classpath *: prefix and does not contain wildcard characters, the ResourceLoader of the current class is directly called for processing. The other methods are handled according to specific conditions, mainly involving two methods: findpathmatching resources (...) and #findAllClassPathResources(...)

	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//1. Judge whether it starts with classpath *
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			//1.1. Check whether there are wildcards in path matching
			// a class path resource (multiple resources for same name possible)
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// a class path resource pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				//1.2 no wildcards
				// all class path resources with the given name
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// 2. Not at the beginning of the classpath prefix
			// Generally only look for a pattern after a prefix here,
			// and on Tomcat only after the "*/" separator for its "war:" protocol.
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			//2.1 check whether wildcards are included
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				//2.2 does not contain wildcards. Using internal ResourceLoader to load resources is defaultresourceloader by default
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

3.5.4 findPathMatchingResources

In the above code, we can see that when there are wildcards, the ා findpathmatchresources (...) method will be executed. Let's take a look at the definition of the method:

	/**
	 * Using ant parser to parse and match all fuzzy resources in a given path
	 * Support jar and zip and file resources in the system
	 * Find all resources that match the given location pattern via the
	 * Ant-style PathMatcher. Supports resources in jar files and zip files
	 * and in the file system.
	 * @param locationPattern the location pattern to match
	 * @return the result as Resource array
	 * @throws IOException in case of I/O errors
	 * @see #doFindPathMatchingJarResources
	 * @see #doFindPathMatchingFileResources
	 * @see org.springframework.util.PathMatcher
	 */
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		//Resolve root path
		String rootDirPath = determineRootDir(locationPattern);
		//Resolve to subpath
		String subPattern = locationPattern.substring(rootDirPath.length());
		//Get resource of root path
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<>(16);
		//ergodic
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirUrl = rootDirResource.getURL();
			//Determine whether the resource is a bundle type
			if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if (resolvedUrl != null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}
			//Determine whether the resource is of vfs type
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
			}
			//Judge whether it is in the form of jar
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			//If not
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		//Convert to array return
		return result.toArray(new Resource[0]);
	}
  • In spring, many methods for real operations are named starting with do. From the above, we can see that the core methods, doFindPathMatchingFileResources(...), and doFindPathMatchingJarResources(...), have the same basic knowledge of parsing unknown file types, and another method, determineroutdir (...) Method implements path parsing. Let's take a look at these two implementations.
3.5.4.1 determineRootDir(... )

The determineroutdir method is mainly used to obtain the root path. It resolves the wildcard characters in the path. The code is as follows:

	/**
	 * Get the root path through the given path
	 * Determine the root directory for the given location.
	 * <p>Used for determining the starting point for file matching,
	 * resolving the root directory location to a {@code java.io.File}
	 * and passing it into {@code retrieveMatchingFiles}, with the
	 * remainder of the location as pattern.
	 * <p>Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",
	 * for example.
	 * @param location the location to check
	 * @return the part of the location that denotes the root directory
	 * @see #retrieveMatchingFiles
	 */
	protected String determineRootDir(String location) {
		//1. Find the index + 1 of in the last path. Note that our path is similar to: classpath *: / Web inf / *. XML
		int prefixEnd = location.indexOf(':') + 1;
		//2. Get path length
		int rootDirEnd = location.length();
		//3. Judge whether the path after the colon contains wildcards. If so, the last part divided by "/" will be truncated.
		while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
		}
		//
		if (rootDirEnd == 0) {
			rootDirEnd = prefixEnd;
		}
		return location.substring(0, rootDirEnd);
	}

For example:

Original path Get follow path
classpath*:/test/aa*/app-*.xml classpath*:/test/
classpath*:/test/aa/app-*.xml classpath*:/test/aa
3.5.4.2 doFindPathMatchingFileResources(... )

#The methods of doFindPathMatchingFileResources(...) and "doFindPathMatchingJarResources(...) are basically the same internally. They only parse different types of files. We only look at one of them here. You can compare the differences between them by yourself.

  • Let's follow the method "doFindPathMatchingFileResources(...). The internal call of the method is deep, so I'm going to post the code below. I've commented it, and I believe it can be understood

  • #doFindPathMatchingFileResources(...) code:

 	/**
	 * Find the resource whose file system conforms to the given location and whose path conforms to ant style wildcard
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDirResource the root directory as Resource
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		File rootDir;
		try {
			//Get the file directory corresponding to the absolute path
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		}
		catch (FileNotFoundException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Cannot search for matching files underneath " + rootDirResource +
						" in the file system: " + ex.getMessage());
			}
			return Collections.emptySet();
		}
		catch (Exception ex) {
			if (logger.isInfoEnabled()) {
				logger.info("Failed to resolve " + rootDirResource + " in the file system: " + ex);
			}
			return Collections.emptySet();
		}
		//Call true processing method
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}
  • In the above method, the core process, doFindMatchingFileSystemResources(...), is called, and the code is as follows:
 /**
	 * Obtain all resources in the file system through the combination of ant wildcard's subPattern and the root directory rootDir already obtained
	 * For example: our original url: 'classpath*:/test/aa/app-*.xml'
	 * So here's rootdir: classpath *: / test / AA / subpattern: app - *. XML
	 * Find all resources in the file system that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDir the root directory in the file system
	 * @param subPattern the sub pattern to match (below the root directory)
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException in case of I/O errors
	 * @see #retrieveMatchingFiles
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		//Call the real processing method to get the File of set collection
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
		//Convert the obtained File to a FileSystemResource and add it to the result result set at the same time
		for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
		}
		return result;
	}

The above method is mainly to convert the obtained set < File > results and convert the resource type to FileSystemResource. The core method of the above method is to retrieve matching files (...)

  • #The retrieveMatchingFiles(...) code is as follows:
 	/**
	 *
	 * Retrieve files that match the given path pattern,
	 * checking the given directory and its subdirectories.
	 * @param rootDir the directory to start from
	 * @param pattern the pattern to match against,
	 * relative to the root directory
	 * @return a mutable Set of matching Resource instances
	 * @throws IOException if directory contents could not be retrieved
	 */
	protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
		//1. No empty set returned directly
		if (!rootDir.exists()) {
			// Silently skip non-existing directories.
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
			}
			return Collections.emptySet();
		}
		//2. No directory directly returns null
		if (!rootDir.isDirectory()) {
			// Complain louder if it exists but is no directory.
			if (logger.isInfoEnabled()) {
				logger.info("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
			}
			return Collections.emptySet();
		}
		//3 / judge whether it is readable
		if (!rootDir.canRead()) {
			if (logger.isInfoEnabled()) {
				logger.info("Skipping search for matching files underneath directory [" + rootDir.getAbsolutePath() +
						"] because the application is not allowed to read the directory");
			}
			return Collections.emptySet();
		}
		//4. Convert all system dividers to/
		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
		//5. If there is no / at the beginning of the child path, the parent path should be added last/
		if (!pattern.startsWith("/")) {
			fullPattern += "/";
		}
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File> result = new LinkedHashSet<>(8);
		//How to deal with truth
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return result;
	}

We can see that there are mainly some checksums and transformations in the methods. The real processing is to call the method of doretrivematchingfiles (...),

  • #Doretrivematchingfiles (...) method definition:
 	/**
	 * Recursively traversing the dir directory and combining with fullpattern to match the path, putting all the matching resources into the result
	 * Recursively retrieve files that match the given pattern,
	 * adding them to the given result list.
	 * @param fullPattern the pattern to match against,
	 * with prepended root directory path
	 * @param dir the current directory
	 * @param result the Set of matching File instances to add to
	 * @throws IOException if directory contents could not be retrieved
	 */
	protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		//Traverse directory
		for (File content : listDirectory(dir)) {
			//Get the path of the current file / directory and replace all the separators with/
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			//If directory matches fullPattern at the same time, recursive
			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
				if (!content.canRead()) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
								"] because the application is not allowed to read the directory");
					}
				}
				else {
					doRetrieveMatchingFiles(fullPattern, content, result);
				}
			}
			//Match if file
			if (getPathMatcher().match(fullPattern, currPath)) {
				result.add(content);
			}
		}
	}

3.5.5 findAllClassPathResources

The above analyzes the method call process when there is a wildcard, so here we analyze the method call when there is no wildcard

  • #findAllClassPathResources(...) method code:
	/**
	 * Load all class location s through ClassLoader
	 * Find all class location resources with the given location via the ClassLoader.
	 * Delegates to {@link #doFindAllClassPathResources(String)}.
	 * @param location the absolute path within the classpath
	 * @return the result as Resource array
	 * @throws IOException in case of I/O errors
	 * @see java.lang.ClassLoader#getResources
	 * @see #convertClassLoaderURL
	 */
	protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		//Real processing method to get resource result set
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}
  • #doFindAllClassPathResources(...) method code:
	/**
	 * Find all class location resources with the given path via the ClassLoader.
	 * Called by {@link #findAllClassPathResources(String)}.
	 * @param path the absolute path within the classpath (never a leading slash)
	 * @return a mutable Set of matching Resource instances
	 * @since 4.1.1
	 */
	protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<>(16);
		ClassLoader cl = getClassLoader();
		//1. Get all URLs through ClassLoader
		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		while (resourceUrls.hasMoreElements()) {
			//Convert URLs to urlresources
			URL url = resourceUrls.nextElement();
			result.add(convertClassLoaderURL(url));
		}
		if ("".equals(path)) {
			// The above result is likely to be incomplete, i.e. only containing file system references.
			// We need to have pointers to each of the jar files on the classpath as well...
			//Add all jar packages
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}

The method is relatively simple. It mainly loads jar resources under the directory through ClassLoader. The details will not be pasted out any more. You can view them by yourself

This article is published by AnonyStar and can be reproduced, but the original source shall be declared.
Admire "the art of elegant coding", firmly believe that practice makes perfect, and strive to change life
Welcome to wechat public account: coder for more quality articles
More articles on my blog: IT short code

Keywords: Java xml Spring ftp

Added by isam4m on Thu, 07 May 2020 16:15:51 +0300