SpringExtensions to resources

Resource

  • ResourceAbstract class diagram, originaljavaOnly toUrlResources are loaded.SpringThe file,Url, resources under the classpath for integration abstraction.

Because the Java standard URL protocol and its extension are relatively complex, and the API itself lacks the ability to determine the existence of related resources, Spring introduces its own Resource abstraction.

In my opinion, Resource is Spring’s integration of some common types of Resource apis. Give developers a common way to access all kinds of resources, such as FileSystem, Classpath, UrlResource, etc.

As shown in figure

Resource: contains the access mode of unified resources. For different resources, the Resource have similar getFile getUrl to apply different kinds of resources, including FileSystemresource, ClassPathResource is corresponding to the file system and the realization of the classpath.

ResourceLoader

The class diagram is as follows:

And the interface declaration

public interface ResourceLoader {
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // classpath prefix constant
	// Get resources according to different position strings
	Resource getResource(String location);
	// Get class loaders. Why do you need class loaders? The resources of the ClassPath/FileSystem are needed.
	ClassLoader getClassLoader(a);
}
Copy the code
Interface Default implementationDefaultResoueceLoader
/** * Gets Resource * by default@paramLocation Resource path *@return Resource
	 */
	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");

		// Use a protocol parser to parse path parameters
    //1. Implement a ProtocolResolver and add it via DefaultClassLoader#addProtocolResolver()
		for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
			Resource resource = protocolResolver.resolve(location, this);
			if(resource ! =null) {
				returnresource; }}/ / 2. If the '/' at the beginning, then returned through the different application context ClassPathContextResource/FileSystemContextResource/ServletContextResource
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}

		// If it starts with 'classpath:' the ClassPathResource is returned.
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				// FileUrlResource is returned if it is FileUrl, UrlResource is returned if it is not.
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				returngetResourceByPath(location); }}}Copy the code
  • A custom ProtocolResolver is preferred for loading resources

  • Path parameters that start with/go to getResourceByPath(String Location) to get different resources based on the application context

  • Starting with classpath: returns the ClassPathResource

  • If it is a file, use FileUrlResource. If it is not a file, use UrlResource. If it is not a file, use a MalformedURLException. So, get getResourceByPath, eg: test/ a.ml, and that path will go to getResourceByPath

Core methodgetResourceByPath(String location)

The default is implemented by ClassPathContextResource, and methods use other types by subclass override

FileSystemResource, ClassRelativeContextResource, for example

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

ContextResource

Is a secondary enhancement interface to Resource that gets the relative path to the relative context root in an application context such as ServletContext via an additional getPathWithinContext. In a file or classpath, also used. FileSystemContextResource ClassRelativeContextResource etc.

ResourcePatternResolver

Resource template parser that parses multiple resources at once. Parsing is done using an Ant style URL

public interface ResourcePatternResolver extends ResourceLoader {
	// Carrying the prefix will query all jars
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
  // Get the resource set
	Resource[] getResources(String locationPattern) throws IOException;
}
Copy the code
PathMatchingPatternResourceResolver

Is the default implementation class of ResourcePatternResolver

The core is getResources()

public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//1. Whether to start with classpath*
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// 2. Ant style path (multipath matching with *)
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// 2.1 Search resources for pattern matching
				return findPathMatchingResources(locationPattern);
			}
			else {
				Use ClassLoader to return all classpath and jar resources.
				returnfindAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); }}else {
			// Handle prefixes
			int prefixEnd = (locationPattern.startsWith("war:")? locationPattern.indexOf("* /") + 1 :
					locationPattern.indexOf(':') + 1);
			//4. Obtain wildcard resources for non-CLASspath resources
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				return findPathMatchingResources(locationPattern);
			}
			else {
				//5. Obtain a single resource
				return newResource[] {getResourceLoader().getResource(locationPattern)}; }}}Copy the code
Core steps:
  • If the path of the classpath* flag is used, proceedAntIf the pattern matches, the matching resource is searched
  • If there is no wildcard, the ClassLoader loads the path resource
  • If it is not the CLASSPath tag and is ant style, pattern matching is performed
  • If not Ant style, use DefaultResourceLoader for resource loading
There are several core methods
  • findPathMatchingResources(String locationPattern): This method is used to resolve carryAntStyle Url resource parsing
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		String rootDirPath = determineRootDir(locationPattern); Classpath *:/ web-INF /. XML -> classpath*:/ web-INF /
		String subPattern = locationPattern.substring(rootDirPath.length()); // Get the string except the root directory -> *.xml
		Resource[] rootDirResources = getResources(rootDirPath); // Get the resource set in the root directory
		Set<Resource> result = new LinkedHashSet<>(16);
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource); // Give subclasses the opportunity to modify the resource load
			URL rootDirUrl = rootDirResource.getURL();
			// Bundle resources resolve WebSphere
			if(equinoxResolveMethod ! =null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if(resolvedUrl ! =null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}
			// VFS file parsing
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
			}
			// Jar path parsing, parsing all entries in jar
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				// Add the result of parsing to LinkedHashSet
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			//FileSystem Path resolution: Fill the root directory recursively
			else{ result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); }}return result.toArray(new Resource[0]);
	}
Copy the code
  • findAllClassPathResources(String location)This method is used to load specific resources in the classpath
	protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		// use the ClassLoader to load resources in the same path, where if path= "then all jars under the ClassLoader will be loaded
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}
Copy the code

Extensions to the Java URL protocol

Common URL protocols in Java include HTTP, HTTPS, FTP,file and so on. So how to extend the protocol based on java.net.URL?

  • URL#setURLStreamHandlerFactorySet the protocol factory globally
  • implementationHandlerAnd, inSun.net.www.protocol.{protocol name}.handlerUnder fixed position
  • implementationHandler, and through the-Djava.protocol.handler.pkgsSpecify the name of a user-defined protocol package

The relevant implementation code is as follows

Custom protocol factory mode
//1. Customize the protocol named LazyLittle and inherit URLStreamHandlerFactory
public class LazylittleStreamHandlerFactory implements URLStreamHandlerFactory {

    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        return new URLStreamHandler() {
            @Override
            protected URLConnection openConnection(URL u) throws IOException {
                return newLazylittleUrlConnection(u); }}; }/ / 2. Inheritance URLConnection
    static class LazylittleUrlConnection extends URLConnection {
        private ClassPathResource classPathResource;
      	//3. Delegate to ClasspathResource
        protected LazylittleUrlConnection(URL url) {
            super(url);
            if(! url.toString().startsWith("lazylittle")) {
                throw new UnsupportedOperationException("invalid prefix " + url);
            }
            //delegate
            classPathResource = new ClassPathResource(url.getPath());
        }
        @Override
        public InputStream getInputStream(a) throws IOException {
            return classPathResource.getInputStream();
        }
        @Override
        public void connect(a) throws IOException {}}Copy the code
Demo code
        //1. The highest priority and will override the following policy, ClassLoader global!
       URL.setURLStreamHandlerFactory(new LazylittleStreamHandlerFactory());
       URL url = new 
URL("lazylittle:/spring/in/action/resource/custom/LazylittleStreamHandlerFactory.class");
       IoUtil.copy(url.openStream(), System.out); // Output to the console
Copy the code
Fixed package under implementation

This is done by implementing URLStreamHandler and then placing it under sun.net.www.protocol.{custom protocol name}. The relevant implementation looks at the factory implementation above

The specified-DCustomize package name parameters
  • Implemented under defined packagesURLStreamHandler
  • On the startup parameters- Djava. Protocol. The handler. PKGS = a custom package nameCan be