Spring-ioc unified resource loading policy

1. Unified Resource definition Resource interface

  1. The class diagram structure

Different implementations are provided for different resource types:

  • InputStreamSource: The top-level interface of the resource definition that defines the interface method to get the input stream
    • Resource: a unified abstraction and access interface for all Spring resources
      • AbstractResource: Abstract class, the default implementation of the Resouce interface. Subclasses can implement different types of Resouce based on the actual situation. If you want to customize Resouce, you can directly inherit the class and implement your own methods.
        • FileSystemResource: encapsulation of java.io.file, which allows you to view the constructor of the source code.
        • ByteArrayResource: encapsulates a byte array that can be constructed as a ByteArrayInputStream ByteArrayInputStream
        • UrlResource: A definition of a network resource that encapsulates java.net.url internally
        • ClassPathResource: An implementation of a resource in a class path that uses a given classLoader or clazz object to define the resource
        • InputStreamResource: Encapsulates an InputStream object
AbstractResource Abstract class annotation
/**
 * Convenience base class for {@link Resource} implementations,
 * pre-implementing typical behavior.
 *
 * <p>The "exists" method will check whether a File or InputStream can
 * be opened; "isOpen" will always return false; "getURL" and "getFile"
 * throw an exception; and "toString" will return the description.
 *
 * @noteFor the default implementation of the Resource interface, all other subclasses will override the default implementation for their own needs * *@author Juergen Hoeller
 * @author Sam Brannen
 * @since28.12.2003 * /
public abstract class AbstractResource implements Resource {

	/** * This method is used to verify that the file can be opened
	@Override
	public boolean exists(a) {
		// Verify that the resource is of file type
		if (isFile()) {
			try {
				// Call the Java IO API to determine whether the file exists
				return getFile().exists();
			}
			catch (IOException ex) {
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Could not retrieve File for existence check of "+ getDescription(), ex); }}}// Close the input stream after obtaining the stream. The implementation is operated by subclasses
		try {
			getInputStream().close();
			return true;
		}
		catch (Throwable ex) {
			Log logger = LogFactory.getLog(getClass());
			if (logger.isDebugEnabled()) {
				logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex);
			}
			return false; }}/** * Returns true by default, indicating readable */
	@Override
	public boolean isReadable(a) {
		return exists();
	}

	/** * Whether the resource has been opened. By default, false is returned, indicating that */ is not opened
	@Override
	public boolean isOpen(a) {
		return false;
	}

	/** * returns false by default, indicating that it is not a file */
	@Override
	public boolean isFile(a) {
		return false;
	}

	/** * Throws an exception by default indicating that the file cannot be parsed into a URL, requiring subclasses to implement */
	@Override
	public URL getURL(a) throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/** * Build the URI based on the URL obtained by getURL */
	@Override
	public URI getURI(a) throws IOException {
		URL url = getURL();
		try {
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
			throw new NestedIOException("Invalid URI [" + url + "]", ex); }}/** * returns an exception by default, indicating that the file cannot be parsed
	@Override
	public File getFile(a) throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	/** * Build ReadableByteChannel ** / based on what getInputStream() returns
	@Override
	public ReadableByteChannel readableChannel(a) throws IOException {
		return Channels.newChannel(getInputStream());
	}

	Length of the resource = The length of the byte array after the resource is converted to the input stream */
	@Override
	public long contentLength(a) throws IOException {
		InputStream is = getInputStream();
		try {
			long size = 0;
			byte[] buf = new byte[256];
			int read;
			while((read = is.read(buf)) ! = -1) {
				size += read;
			}
			return size;
		}
		finally {
			try {
				is.close();
			}
			catch (IOException ex) {
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Could not close content-length InputStream for "+ getDescription(), ex); }}}}/** * The last time the resource was modified, returns a value in milliseconds */
	@Override
	public long lastModified(a) throws IOException {
		File fileToCheck = getFileForLastModifiedCheck();
		long lastModified = fileToCheck.lastModified();
		if (lastModified == 0L && !fileToCheck.exists()) {
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		return lastModified;
	}

	/**
	 * Determine the File to use for timestamp checking.
	 * <p>The default implementation delegates to {@link #getFile()}.
	 * @return the File to use for timestamp checking (never {@code null})
	 * @throws FileNotFoundException if the resource cannot be resolved as
	 * an absolute file path, i.e. is not available in a file system
	 * @throws IOException in case of general resolution/reading failures
	 */
	protected File getFileForLastModifiedCheck(a) throws IOException {
		return getFile();
	}

	/** * create a resource based on the relative path, throw an exception by default, and let the subclass implement the method */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/** * get the name of the file. By default, null */ is returned
	@Override
	@Nullable
	public String getFilename(a) {
		return null;
	}


	/**
	 * This implementation compares description strings.
	 * @see #getDescription()
	 */
	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other || (other instanceof Resource &&
				((Resource) other).getDescription().equals(getDescription())));
	}

	/**
	 * This implementation returns the description's hash code.
	 * @see #getDescription()
	 */
	@Override
	public int hashCode(a) {
		return getDescription().hashCode();
	}

	/**
	 * This implementation returns the description of this resource.
	 * @see #getDescription()
	 */
	@Override
	public String toString(a) {
		returngetDescription(); }}Copy the code

2. Load the ResourceLoader interface on all resources

  1. Class diagram

  • ResouceLoader: Unified resource loader
    • DefaultResouceLoader: Implementation of the default resource loader
      • ClassRelativeResourceLoader:
      • FileSystemResouceLoader:
    • ResourcePatternResolver:
    • PathMatchingResoucePatternResolver:
2.1 DefaultResouceLoader (Default implementation of the ResourceLoader interface)
  1. Default implementation of ResourceLoader
  2. The core method is the getResource() method to get the resource
/**
 * Default implementation of the {@link ResourceLoader} interface.
 * Used by {@link ResourceEditor}, and serves as base class for
 * {@link org.springframework.context.support.AbstractApplicationContext}.
 * Can also be used standalone.
 *
 * <p>Will return a {@link UrlResource} if the location value is a URL,
 * and a {@link ClassPathResource} if it is a non-URL path or a
 * "classpath:" pseudo-URL.
 *
 * @author Juergen Hoeller
 * @since 10.03.2004
 * @see FileSystemResourceLoader
 * @seeOrg. Springframework. Context. Support. ClassPathXmlApplicationContext * * the default implementation of ResourceLoader interface * /
public class DefaultResourceLoader implements ResourceLoader {

	@Nullable
	private ClassLoader classLoader;

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

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


	/**
	 * Create a new DefaultResourceLoader.
	 * <p>ClassLoader access will happen using the thread context class loader
	 * at the time of actual resource access (since 5.3). For more control, pass
	 * a specific ClassLoader to {@link #DefaultResourceLoader(ClassLoader)}.
	 * @see java.lang.Thread#getContextClassLoader()
	 */
	public DefaultResourceLoader(a) {}/**
	 * Create a new DefaultResourceLoader.
	 * @param classLoader the ClassLoader to load class path resources with, or {@code null}
	 * for using the thread context class loader at the time of actual resource access
	 */
	public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}


	/**
	 * Specify the ClassLoader to load class path resources with, or {@codenull} * for using the thread context class loader at the time of actual resource access. * <p>The default is that ClassLoader access will happen using the thread context * class loader at the time of actual resource access (since 5.3). * /
	public void setClassLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	/**
	 * Return the ClassLoader to load class path resources with.
	 * <p>Will get passed to ClassPathResource's constructor for all
	 * ClassPathResource objects created by this resource loader.
	 * @see ClassPathResource
	 */
	@Override
	@Nullable
	public ClassLoader getClassLoader(a) {
		return (this.classLoader ! =null ? this.classLoader : ClassUtils.getDefaultClassLoader());
	}

	/**
	 * 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);
	}

	/**
	 * Return the collection of currently registered protocol resolvers,
	 * allowing for introspection as well as modification.
	 * @since4.3 * /
	public Collection<ProtocolResolver> getProtocolResolvers(a) {
		return this.protocolResolvers;
	}

	/**
	 * 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
	 * @since5.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(a) {
		this.resourceCaches.clear();
	}


	/** * Core method *@param location
	 * @return* /
	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");

		// The resource object is obtained by parsing the resource according to the user-defined protocol resource resolver
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if(resource ! =null) {
				returnresource; }}if (location.startsWith("/")) {
			// Returns the ClassPathContextResource object starting with "/"
			return getResourceByPath(location);
		}
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			// Starts with "classpath:" and returns the ClassPathResource object
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// If none of the above conditions is met, the URL object is created, and if the construction fails, the code in the catch block is called
				// Try to parse the location as a URL...
				URL url = new URL(location);
				// Return different Resource objects depending on whether they are of type File or not
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// Create a RESOURCE exception of the URL type, then call the following method, returning the ClassPathContextResource object
				// No URL -> resolve as resource path.
				returngetResourceByPath(location); }}}/**
	 * Return a Resource handle for the resource at the given path.
	 * <p>The default implementation supports class path locations. This should
	 * be appropriate for standalone implementations but can be overridden,
	 * e.g. for implementations targeted at a Servlet container.
	 * @param path the path to the resource
	 * @return the corresponding Resource handle
	 * @see ClassPathResource
	 * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
	 * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
	 */
	protected Resource getResourceByPath(String path) {
		// Call the constructor directly to return
		return new ClassPathContextResource(path, getClassLoader());
	}


	/** * ClassPathResource that explicitly expresses a context-relative path * through implementing the ContextResource interface. */
	protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {

		public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
			super(path, classLoader);
		}

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

		@Override
		public Resource createRelative(String relativePath) {
			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
			return newClassPathContextResource(pathToUse, getClassLoader()); }}}Copy the code
2.2 FileSystemResourceLoader
  1. A subclass of DefaultResorceLoader
  2. Load a resource from a file system and return the FileSystemResource object
/ * * * {@link ResourceLoader} implementation that resolves plain paths as
 * file system resources rather than as class path resources
 * (the latter is {@link DefaultResourceLoader}'s default strategy).
 *
 * <p><b>NOTE:</b> Plain paths will always be interpreted as relative
 * to the current VM working directory, even if they start with a slash.
 * (This is consistent with the semantics in a Servlet container.)
 * <b>Use an explicit "file:" prefix to enforce an absolute file path.</b>
 *
 * <p>{@link org.springframework.context.support.FileSystemXmlApplicationContext}
 * is a full-fledged ApplicationContext implementation that provides
 * the same resource path resolution strategy.
 *
 * @author Juergen Hoeller
 * @since 1.1.3
 * @see DefaultResourceLoader
 * @see org.springframework.context.support.FileSystemXmlApplicationContext
 * @noteLoad the Resource object */ from the file system
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
	 *
	 * @noteAnd a new method to implement */
	@Override
	protected Resource getResourceByPath(String path) {
		// Start with "/" to intercept
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		// Call the constructor of the inner class
		return new FileSystemContextResource(path);
	}


	/** * FileSystemResource that explicitly expresses a context-relative path * through implementing the ContextResource Interface. * * FileSystemContextResource inherited from FileSystemResource, is also a constructor calls FileSystemResource constructor * ContextResource * /
	private static class FileSystemContextResource extends FileSystemResource implements ContextResource {

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

		@Override
		public String getPathWithinContext(a) {
			returngetPath(); }}}Copy the code
2.3 ClassRelativeResourceLoader
  1. A subclass of DefaultResourceLoader
  2. Returns a ClassPathResource object based on the given Class object from the specified path
/ * * * {@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)
 * @noteReturns the specified Resource object */ based on the given class object and its path
public class ClassRelativeResourceLoader extends DefaultResourceLoader {

	private finalClass<? > 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
	protected Resource getResourceByPath(String path) {
		// Call the inner class constructor directly
		return new ClassRelativeContextResource(path, this.clazz);
	}


	/** * ClassPathResource that explicitly expresses a context-relative path * through implementing the ContextResource Interface. * inherits ClassPathResource, implements ContextResource, Constructor is called directly ClassPathResource constructor * FileSystemContextResource inner class of similar FileSystemResourceLoader * /
	private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {

		private finalClass<? > clazz;public ClassRelativeContextResource(String path, Class
        clazz) {
			super(path, clazz);
			this.clazz = clazz;
		}

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

		@Override
		public Resource createRelative(String relativePath) {
			String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
			return new ClassRelativeContextResource(pathToUse, this.clazz); }}}Copy the code
2.4 ResourcePatternResolver
  1. inheritanceResourceLoader
  2. DefaultResourceLoaderResource getResourceByPath(String path)Methods can only return one Resource object. If multiple resources are loaded, they can only be called in a loop
  3. Resource[] getResources(String locationPattern)Method to return multiple Resource objects based on the path matching rules of the Resource
/**
 * Strategy interface for resolving a location pattern (for example,
 * an Ant-style path pattern) into {@link Resource} objects.
 *
 * <p>This is an extension to the {@link org.springframework.core.io.ResourceLoader}
 * interface. A passed-in {@code ResourceLoader} (for example, an
 * {@link org.springframework.context.ApplicationContext} passed in via
 * {@link org.springframework.context.ResourceLoaderAware} when running in a context)
 * can be checked whether it implements this extended interface too.
 *
 * <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
 * that is usable outside an {@code ApplicationContext}, also used by
 * {@link ResourceArrayPropertyEditor} for populating {@codeResource} array bean * properties. * * <p>Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):  * Input patterns have to match the strategy implementation. This interface just * specifies the conversion method rather than a specific pattern format. * * <p>This interface also suggests a new resource prefix "classpath*:" for all *  matching resources from the class path. Note that the resource location is * expected to be a path without placeholders  in this case (e.g. "/beans.xml"); * JAR files or different directories in the class path can contain multiple files * of the same name. * *@author Juergen Hoeller
 * @since 1.0.2
 * @see org.springframework.core.io.Resource
 * @see org.springframework.core.io.ResourceLoader
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ResourceLoaderAware
 * @noteMultiple resources */ can be loaded according to path matching rules
public interface ResourcePatternResolver extends ResourceLoader {

	/** * Pseudo URL prefix for all matching resources from the class path: "classpath*:" * <p>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 {@code 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 {@code Resource} objects
	 * @throwsIOException in case of I/O errors * * Multiple resources can be loaded according to path matching rules */
	Resource[] getResources(String locationPattern) throws IOException;

}

Copy the code
Against 2.4.1 PathMatchingResourcePatternResolver
  1. ResourcePatternResolverThe implementation class
  2. providesDefaultResourceLoaderObject, which can be accessed via Resource getResource(String location)Method to get a single resource
  3. Resource[] getResources(String locationPattern)Method to get multiple resources
  4. An Ant style path resolver is provided
/* * * @author Juergen Hoeller * @author Colin Sampaleanu * @author Marius Bogoevici * @author Costin Leau * @author Phillip Webb * @ since 1.0.2 * @ # see CLASSPATH_ALL_URL_PREFIX * @ see org. Springframework. Util. AntPathMatcher * @ see Org. Springframework. Core. IO. ResourceLoader# getResource (String) * @ see ClassLoader# getResources (String) * @ note in addition to support Classpath * in ResourcePatternResolver: matching rule. AntPathMatcher Ant style path matcher */ is also introduced
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {

	private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);

	@Nullable
	private static Method equinoxResolveMethod;

	static {
		try {
			// Detect Equinox OSGi (e.g. on WebSphere 6.1)Class<? > fileLocatorClass = ClassUtils.forName("org.eclipse.core.runtime.FileLocator",
					PathMatchingResourcePatternResolver.class.getClassLoader());
			equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
			logger.trace("Found Equinox FileLocator for OSGi bundle URL resolution");
		}
		catch (Throwable ex) {
			equinoxResolveMethod = null; }}/** * built-in resource loader */
	private final ResourceLoader resourceLoader;

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


	/** * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader. * <p>ClassLoader access will happen  via the thread context class loader. *@seeOrg. Springframework. Core. IO. * * the default DefaultResourceLoader without structure, DefaultResourceLoader object for the specified resource loader * /
	public PathMatchingResourcePatternResolver(a) {
		this.resourceLoader = new DefaultResourceLoader();
	}

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

	/**
	 * 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
	 * @seeOrg. Springframework. Core. IO. DefaultResourceLoader * specified class loader * /
	public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
		this.resourceLoader = new DefaultResourceLoader(classLoader);
	}


	/** * Return the ResourceLoader that this pattern resolver works with. */
	public ResourceLoader getResourceLoader(a) {
		return this.resourceLoader;
	}

	@Override
	@Nullable
	public ClassLoader getClassLoader(a) {
		return getResourceLoader().getClassLoader();
	}

	/**
	 * Set the PathMatcher implementation to use for this
	 * resource pattern resolver. Default is AntPathMatcher.
	 * @see org.springframework.util.AntPathMatcher
	 */
	public void setPathMatcher(PathMatcher pathMatcher) {
		Assert.notNull(pathMatcher, "PathMatcher must not be null");
		this.pathMatcher = pathMatcher;
	}

	/** * Return the PathMatcher that this resource pattern resolver uses. */
	public PathMatcher getPathMatcher(a) {
		return this.pathMatcher;
	}


	/** * To obtain a single resource, load it * using the resource passed in by the constructor or the default resource@param location
	 * @return* /
	@Override
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
	}

	/** * Obtain multiple resources according to the path rule@param locationPattern the location pattern to resolve
	 * @return
	 * @throws IOException
	 */
	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		// The path starts with classpath*:
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// Check whether the classpath*: contains wildcard characters
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// The call method resolves the wildcard character and returns the resources in the directory that match the condition
				return findPathMatchingResources(locationPattern);
			}
			else {
				// all class path resources with the given name
				returnfindAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); }}else {
			// 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);
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// a single resource with the given name
				return newResource[] {getResourceLoader().getResource(locationPattern)}; }}}/**
	 * 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);
		}
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}

	/**
	 * 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
	 * @since4.4.1 * /
	protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<>(16); ClassLoader cl = getClassLoader(); Enumeration<URL> resourceUrls = (cl ! =null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			result.add(convertClassLoaderURL(url));
		}
		if(! StringUtils.hasLength(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...
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}

	/**
	 * Convert the given URL as returned from the ClassLoader into a {@link Resource}.
	 * <p>The default implementation simply creates a {@link UrlResource} instance.
	 * @param url a URL as returned from the ClassLoader
	 * @return the corresponding Resource object
	 * @see java.lang.ClassLoader#getResources
	 * @see org.springframework.core.io.Resource
	 */
	protected Resource convertClassLoaderURL(URL url) {
		return new UrlResource(url);
	}

	/**
	 * Search all {@linkURLClassLoader} URLs for jar file references and add them to the * given set of resources in the form of pointers to the  root of the jar file content. *@param classLoader the ClassLoader to search (including its ancestors)
	 * @param result the set of resources to add jar roots to
	 * @since4.4.1 * /
	protected void addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set<Resource> result) {
		if (classLoader instanceof URLClassLoader) {
			try {
				for (URL url : ((URLClassLoader) classLoader).getURLs()) {
					try {
						UrlResource jarResource = (ResourceUtils.URL_PROTOCOL_JAR.equals(url.getProtocol()) ?
								new UrlResource(url) :
								new UrlResource(ResourceUtils.JAR_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR));
						if(jarResource.exists()) { result.add(jarResource); }}catch (MalformedURLException ex) {
						if (logger.isDebugEnabled()) {
							logger.debug("Cannot search for matching files underneath [" + url +
									"] because it cannot be converted to a valid 'jar:' URL: "+ ex.getMessage()); }}}}catch (Exception ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader +
							"] does not support 'getURLs()': "+ ex); }}}if (classLoader == ClassLoader.getSystemClassLoader()) {
			// "java.class.path" manifest evaluation...
			addClassPathManifestEntries(result);
		}

		if(classLoader ! =null) {
			try {
				// Hierarchy traversal...
				addAllClassLoaderJarRoots(classLoader.getParent(), result);
			}
			catch (Exception ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader +
							"] does not support 'getParent()': "+ ex); }}}}/**
	 * Determine jar file references from the "java.class.path." manifest property and add them
	 * to the given set of resources in the form of pointers to the root of the jar file content.
	 * @param result the set of resources to add jar roots to
	 * @since4.3 * /
	protected void addClassPathManifestEntries(Set<Resource> result) {
		try {
			String javaClassPathProperty = System.getProperty("java.class.path");
			for (String path : StringUtils.delimitedListToStringArray(
					javaClassPathProperty, System.getProperty("path.separator"))) {
				try {
					String filePath = new File(path).getAbsolutePath();
					int prefixIndex = filePath.indexOf(':');
					if (prefixIndex == 1) {
						// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
						filePath = StringUtils.capitalize(filePath);
					}
					// # can appear in directories/filenames, java.net.URL should not treat it as a fragment
					filePath = StringUtils.replace(filePath, "#"."% 23");
					// Build URL that points to the root of the jar file
					UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
							ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
					// Potentially overlapping with URLClassLoader.getURLs() result above!
					if(! result.contains(jarResource) && ! hasDuplicate(filePath, result) && jarResource.exists()) { result.add(jarResource); }}catch (MalformedURLException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Cannot search for matching files underneath [" + path +
								"] because it cannot be converted to a valid 'jar:' URL: "+ ex.getMessage()); }}}}catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to evaluate 'java.class.path' manifest entries: "+ ex); }}}/**
	 * Check whether the given file path has a duplicate but differently structured entry
	 * in the existing result, i.e. with or without a leading slash.
	 * @param filePath the file path (with or without a leading slash)
	 * @param result the current result
	 * @return {@code true} if there is a duplicate (i.e. to ignore the given file path),
	 * {@code false} to proceed with adding a corresponding resource to the current result
	 */
	private boolean hasDuplicate(String filePath, Set<Resource> result) {
		if (result.isEmpty()) {
			return false;
		}
		String duplicatePath = (filePath.startsWith("/")? filePath.substring(1) : "/" + filePath);
		try {
			return result.contains(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX +
					duplicatePath + ResourceUtils.JAR_URL_SEPARATOR));
		}
		catch (MalformedURLException ex) {
			// Ignore: just for testing against duplicate.
			return false; }}/**
	 * 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
	 * @seeOrg. Springframework. Util. PathMatcher query matching rules in line with the path all the * * * * / resources
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		// Get the root directory
		String rootDirPath = determineRootDir(locationPattern);
		String subPattern = locationPattern.substring(rootDirPath.length());
		// Obtain all resources in the root path
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<>(16);
		// Iterate over resources and call different methods to obtain the corresponding resources according to the different types of resources
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirUrl = rootDirResource.getURL();
			/ / the bundle of resources
			if(equinoxResolveMethod ! =null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if(resolvedUrl ! =null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}
			// Type of the VFS resource
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
			}
			// Jar type resources
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			// Other resource types
			else{ result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); }}if (logger.isTraceEnabled()) {
			logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}

	/**
	 * 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 * * Get the root path of the path with wildcards * * Similar to classpath*:test/cc* ----> classpath*:test/ * */
	protected String determineRootDir(String location) {
		int prefixEnd = location.indexOf(':') + 1;
		int rootDirEnd = location.length();
		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);
	}

	/**
	 * Resolve the specified resource for path matching.
	 * <p>By default, Equinox OSGi "bundleresource:" / "bundleentry:" URL will be
	 * resolved into a standard jar file URL that be traversed using Spring's
	 * standard jar file traversal algorithm. For any preceding custom resolution,
	 * override this method and replace the resource handle accordingly.
	 * @param original the resource to resolve
	 * @return the resolved resource (may be identical to the passed-in resource)
	 * @throws IOException in case of resolution failure
	 */
	protected Resource resolveRootDirResource(Resource original) throws IOException {
		return original;
	}

	/**
	 * Return whether the given resource handle indicates a jar resource
	 * that the {@code doFindPathMatchingJarResources} method can handle.
	 * <p>By default, the URL protocols "jar", "zip", "vfszip and "wsjar"
	 * will be treated as jar resources. This template method allows for
	 * detecting further kinds of jar-like resources, e.g. through
	 * {@code instanceof} checks on the resource handle type.
	 * @param resource the resource handle to check
	 * (usually the root directory to start path matching from)
	 * @see #doFindPathMatchingJarResources
	 * @see org.springframework.util.ResourceUtils#isJarURL
	 */
	protected boolean isJarResource(Resource resource) throws IOException {
		return false;
	}

	/**
	 * Find all resources in jar files that match the given location pattern
	 * via the Ant-style PathMatcher.
	 * @param rootDirResource the root directory as Resource
	 * @param rootDirURL the pre-resolved root directory URL
	 * @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
	 * @since 4.3
	 * @see java.net.JarURLConnection
	 * @see org.springframework.util.PathMatcher
	 */
	protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
			throws IOException {

		URLConnection con = rootDirURL.openConnection();
		JarFile jarFile;
		String jarFileUrl;
		String rootEntryPath;
		boolean closeJarFile;

		if (con instanceof JarURLConnection) {
			// Should usually be the case for traditional JAR files.JarURLConnection jarCon = (JarURLConnection) con; ResourceUtils.useCachesIfNecessary(jarCon); jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); JarEntry jarEntry = jarCon.getJarEntry(); rootEntryPath = (jarEntry ! =null ? jarEntry.getName() : ""); closeJarFile = ! jarCon.getUseCaches(); }else {
			// No JarURLConnection -> need to resort to URL file parsing.
			// We'll assume URLs of the format "jar:path! /entry", with the protocol
			// being arbitrary as long as following the entry format.
			// We'll also handle paths with and without leading "file:" prefix.
			String urlFile = rootDirURL.getFile();
			try {
				int separatorIndex = urlFile.indexOf(ResourceUtils.WAR_URL_SEPARATOR);
				if (separatorIndex == -1) {
					separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
				}
				if(separatorIndex ! = -1) {
					jarFileUrl = urlFile.substring(0, separatorIndex);
					rootEntryPath = urlFile.substring(separatorIndex + 2);  // both separators are 2 chars
					jarFile = getJarFile(jarFileUrl);
				}
				else {
					jarFile = new JarFile(urlFile);
					jarFileUrl = urlFile;
					rootEntryPath = "";
				}
				closeJarFile = true;
			}
			catch (ZipException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping invalid jar classpath entry [" + urlFile + "]");
				}
				returnCollections.emptySet(); }}try {
			if (logger.isTraceEnabled()) {
				logger.trace("Looking for matching resources in jar file [" + jarFileUrl + "]");
			}
			if(StringUtils.hasLength(rootEntryPath) && ! rootEntryPath.endsWith("/")) {
				// Root entry path must end with slash to allow for proper matching.
				// The Sun JRE does not return a slash here, but BEA JRockit does.
				rootEntryPath = rootEntryPath + "/";
			}
			Set<Resource> result = new LinkedHashSet<>(8);
			for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
				JarEntry entry = entries.nextElement();
				String entryPath = entry.getName();
				if (entryPath.startsWith(rootEntryPath)) {
					String relativePath = entryPath.substring(rootEntryPath.length());
					if(getPathMatcher().match(subPattern, relativePath)) { result.add(rootDirResource.createRelative(relativePath)); }}}return result;
		}
		finally {
			if(closeJarFile) { jarFile.close(); }}}/** * Resolve the given jar file URL into a JarFile object. */
	protected JarFile getJarFile(String jarFileUrl) throws IOException {
		if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
			try {
				return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
			}
			catch (URISyntaxException ex) {
				// Fallback for URLs that are not valid URIs (should hardly ever happen).
				return newJarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length())); }}else {
			return newJarFile(jarFileUrl); }}/**
	 * 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 {
			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();
		}
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}

	/**
	 * 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() + "]");
		}
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
		for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
		}
		return result;
	}

	/**
	 * 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 {
		if(! rootDir.exists()) {// Silently skip non-existing directories.
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
			}
			return Collections.emptySet();
		}
		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();
		}
		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();
		}
		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
		if(! pattern.startsWith("/")) {
			fullPattern += "/";
		}
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File> result = new LinkedHashSet<>(8);
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return 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 + "]");
		}
		for (File content : listDirectory(dir)) {
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			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); }}if(getPathMatcher().match(fullPattern, currPath)) { result.add(content); }}}/**
	 * Determine a sorted list of files in the given directory.
	 * @param dir the directory to introspect
	 * @return the sorted list of files (by default in alphabetical order)
	 * @since 5.1
	 * @see File#listFiles()
	 */
	protected File[] listDirectory(File dir) {
		File[] files = dir.listFiles();
		if (files == null) {
			if (logger.isInfoEnabled()) {
				logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
			}
			return new File[0];
		}
		Arrays.sort(files, Comparator.comparing(File::getName));
		return files;
	}


	/** * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime. */
	private static class VfsResourceMatchingDelegate {

		public static Set<Resource> findMatchingResources( URL rootDirURL, String locationPattern, PathMatcher pathMatcher) throws IOException {

			Object root = VfsPatternUtils.findRoot(rootDirURL);
			PatternVirtualFileVisitor visitor =
					new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
			VfsPatternUtils.visit(root, visitor);
			returnvisitor.getResources(); }}/** * VFS visitor for path matching purposes. */
	@SuppressWarnings("unused")
	private static class PatternVirtualFileVisitor implements InvocationHandler {

		private final String subPattern;

		private final PathMatcher pathMatcher;

		private final String rootPath;

		private final Set<Resource> resources = new LinkedHashSet<>();

		public PatternVirtualFileVisitor(String rootPath, String subPattern, PathMatcher pathMatcher) {
			this.subPattern = subPattern;
			this.pathMatcher = pathMatcher;
			this.rootPath = (rootPath.isEmpty() || rootPath.endsWith("/")? rootPath : rootPath +"/");
		}

		@Override
		@Nullable
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			String methodName = method.getName();
			if (Object.class == method.getDeclaringClass()) {
				if (methodName.equals("equals")) {
					// Only consider equal when proxies are identical.
					return (proxy == args[0]);
				}
				else if (methodName.equals("hashCode")) {
					returnSystem.identityHashCode(proxy); }}else if ("getAttributes".equals(methodName)) {
				return getAttributes();
			}
			else if ("visit".equals(methodName)) {
				visit(args[0]);
				return null;
			}
			else if ("toString".equals(methodName)) {
				return toString();
			}

			throw new IllegalStateException("Unexpected method invocation: " + method);
		}

		public void visit(Object vfsResource) {
			if (this.pathMatcher.match(this.subPattern,
					VfsPatternUtils.getPath(vfsResource).substring(this.rootPath.length()))) {
				this.resources.add(newVfsResource(vfsResource)); }}@Nullable
		public Object getAttributes(a) {
			return VfsPatternUtils.getVisitorAttributes();
		}

		public Set<Resource> getResources(a) {
			return this.resources;
		}

		public int size(a) {
			return this.resources.size();
		}

		@Override
		public String toString(a) {
			return "sub-pattern: " + this.subPattern + ", resources: " + this.resources; }}}Copy the code

Resource[] getResources (String locationPattern)

  1. “Classpath :”, contains wildcard, callsfindPathMatchingResourcesmethods
  2. Starts with “classpath*:”, does not contain wildcards, callsfindAllClassPathResourcesmethods
  3. Not starting with “classpath*:”, contains wildcard, callsfindPathMatchingResourcesmethods
  4. If it does not start with “classpath*:” and does not contain wildcards, useresourceLoaderloading