Java’s standard java.net.URL class and standard handlers for various URL prefixes, unfortunately, are not quite adequate enough for all access to low-level resources. For example, there is no standardized URL implementation that may be used to access a resource that needs to be obtained from the classpath or relative to a ServletContext. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desirable functionality, such as a method to check for the existence of the resource being pointed to.
Spring’s Resource interface is meant to be a more capable interface for abstracting access to low-level resources.
ClassPathResource :class path 类型资源的封装实现类,内部使用给定的 ClassLoader 或者给定的 Class 进行资源加载。
Resource implementation for class path resources. Uses either a given ClassLoader or a given Class for loading resources.
Supports resolution as java.io.File if the class path resource resides in the file system, but not for resources in a JAR. Always supports resolution as URL.
Resource implementation for java.io.File and java.nio.file.Path handles with a file system target. Supports resolution as a File and also as a URL. Implements the extended WritableResource interface.
Note: As of Spring Framework 5.0, this Resource implementation uses NIO.2 API for read/write interactions. As of 5.1, it may be constructed with a Path handle in which case it will perform all file system interactions via NIO.2, only resorting to File on getFile().
Creates a ByteArrayInputStream for the given byte array.
Useful for loading content from any given byte array, without having to resort to a single-use InputStreamResource. Particularly useful for creating mail attachments from local content, where JavaMail needs to be able to read the stream multiple times.
Should only be used if no other specific Resource implementation is applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible.
In contrast to other Resource implementations, this is a descriptor for an already opened resource - therefore returning true from isOpen(). Do not use an InputStreamResource if you need to keep the resource descriptor somewhere, or if you need to read from a stream multiple times.
/** * 检查文件是否存在,或者检查有对应的流 InputStream 存在,此时需要关闭流 */ @Override publicbooleanexists(){ // Try file existence: can we find the file in the file system? // 先判断文件 File 是否存在:基于 File 的判断 if (isFile()) { try { 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); } } } // Fall back to stream existence: can we open the stream? // 其次检查是否是可以打开的流 InputStream:基于 InputStream 的判断 try { getInputStream().close(); returntrue; } catch (Throwable ex) { Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex); } returnfalse; } }
publicinterfaceResourceLoader{ /** Pseudo URL prefix for loading from the class path: "classpath:". */ // 用于从类路径加载的伪URL前缀:“classpath:” String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/** * A resolution strategy for protocol-specific resource handles. * * <p>Used as an SPI for {@link DefaultResourceLoader}, allowing for * custom protocols to be handled without subclassing the loader * implementation (or application context implementation). * * <p>特定协议的资源加载解决策略接口。 * 用作 DefaultResourceLoader 的SPI,允许在不继承 DefaultResourceLoader(或 ApplicationContext 实现类)的情况下处理自定义协议。 * * @author Juergen Hoeller * @since 4.3 * @see DefaultResourceLoader#addProtocolResolver */ @FunctionalInterface publicinterfaceProtocolResolver{
/** * Resolve the given location against the given resource loader * if this implementation's protocol matches. * <p>根据指定的 ResourceLoader 来解析对应的资源路径,若成功则返回相应的 Resource * @param location the user-specified resource location 指定的资源路径 * @param resourceLoader the associated resource loader 指定的 ResourceLoader * @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);
}
Spring 并没有 ProtocolResolver 接口的任何实现类,这个完全需要用户自己进行定义实现,然后再通过 DefaultResourceLoader.addProtocolResolver(ProtocolResolver resolver) 注册到 Spring 中,该方法代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/** * 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. * <p>注册自定义的特定协议资源加载解决器,允许处理其他协议的资源 * 需要注意的是这些解析器在资源加载时会先执行,因此可能会覆盖其他默认的加载规则 * @since 4.3 * @see #getProtocolResolvers() */ publicvoidaddProtocolResolver(ProtocolResolver resolver){ Assert.notNull(resolver, "ProtocolResolver must not be null"); this.protocolResolvers.add(resolver); }
publicPathMatchingResourcePatternResolver(ResourceLoader resourceLoader){ Assert.notNull(resourceLoader, "ResourceLoader must not be null"); this.resourceLoader = resourceLoader; }
publicPathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader){ this.resourceLoader = new DefaultResourceLoader(classLoader); }
@Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); // 以 "classpath*:" 开头的路径 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // 路径包含通配符 // 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 { // all class path resources with the given name return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } }// 不以 "classpath*:" 开头的路径 else { // Generally only look for a pattern after a prefix here, // and on Tomcat only after the "*/" separator for its "war:" protocol. // 通常只在这里查找前缀后的模式,而在Tomcat中仅在“*/”分隔符后查找其“war:”协议。 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 returnnew Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
/** * <p>通过类加载器找到 classes 路径和所有 jar 包中与给定路径相匹配的资源 */ protected Set<Resource> doFindAllClassPathResources(String path)throws IOException { Set<Resource> result = new LinkedHashSet<>(16); ClassLoader cl = getClassLoader(); // 1. 通过 ClassLoader 加载指定路径下的所有资源 Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); // 2. while (resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); // 将 URL 转换成对应的 UrlResource result.add(convertClassLoaderURL(url)); } // 3. 加载路径下所有的 jar 包 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... addAllClassLoaderJarRoots(cl, result); } return result; }