Spring 统一资源加载策略(二)

1.统一资源接口的子类结构

资源接口实现

  • WritableResource: 可写资源接口
  • ByteArrayResource :二进制数组资源
  • ClassPathResource:类路径下的资源,资源以相对路径的方式表示
  • FileSystemResource:文件系统资源,资源以文件系统的方式表示如:D://data/beans.xml
  • ServletContextPathResource:为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径加载资源。它支持以流和URL的方式访问,在War解包的情况下,也可以通过File方式访问,。该类可以直接从JAR包中访问资源。
  • PathResource:Path封装了java.net.URLjava.nio.file.Path、文件系统资源,它可以访问任何通过URL、Path、文件系统路径表示的资源,如文件系统资源、HTTP资源等。

2.统一资源Resource

Spring框架内使用的org.springframework.core.io.Resouce接口作为所有资源的抽象和访问接口。

public interface Resource extends InputStreamSource {
   

	/**
	 * 判断资源文件是否存在
	 */
	boolean exists();

	/**
	 *  资源文件是否可读
	 */
	default boolean isReadable() {
   
		return exists();
	}

	/**
	 * 判断资源文件句柄是否被打开
	 */
	default boolean isOpen() {
   
		return false;
	}

	/**
	 * 判定资源文件是否为File
	 */
	default boolean isFile() {
   
		return false;
	}

	/**
	 * 返回资源文件的URL
	 */
	URL getURL() throws IOException;

	/**
	 * 返回资源文件URI
	 */
	URI getURI() throws IOException;

	/**
	 * 返回资源文件对象
	 */
	File getFile() throws IOException;

	/**
	 * 返回 ReadableByteChannel nio channel访问
	 */
	default ReadableByteChannel readableChannel() throws IOException {
   
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 资源文件内容长度
	 */
	long contentLength() throws IOException;

	/**
	 * 资源文件的上次修改时间
	 */
	long lastModified() throws IOException;

	/**
	 *根据资源we年的相对路径创建新资源
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * 获取文件名称
	 */
	@Nullable
	String getFilename();

	/**
	 * 资源描述
	 */
	String getDescription();

}

Resource作为资源的统一抽象,定义了一些通用的方法,由子类AbstractResource提供了统一的默认实现,如果需要自定义拓展,可以直接继承此类。


public abstract class AbstractResource implements Resource {
   

	/**
	 * 判断资源文件是否存在
	 */
	@Override
	public boolean exists() {
   
		try {
   
		    // 通过File文件方法判断文件是否存在
			return getFile().exists();
		}
		catch (IOException ex) {
   
			try {
   
			    // 基于InputStream判断文件是否存在
				getInputStream().close();
				return true;
			}
			catch (Throwable isEx) {
   
				return false;
			}
		}
	}

	/**
	 * 资源文件是否可读
	 */
	@Override
	public boolean isReadable() {
   
		return exists();
	}

	/**
	 * 直接返回false 表示未打开
	 */
	@Override
	public boolean isOpen() {
   
		return false;
	}

	/**
	 * 直接返回false,表示不为File
	 */
	@Override
	public boolean isFile() {
   
		return false;
	}

	/**
	 * 直接抛出异常,由子类自定义覆盖实现
	 */
	@Override
	public URL getURL() throws IOException {
   
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/**
	 * 基于getURL() 构建URI
	 */
	@Override
	public URI getURI() throws IOException {
   
		URL url = getURL();
		try {
   
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
   
			throw new NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	/**
	 * 直接抛出异常,由子类实现
	 */
	@Override
	public File getFile() throws IOException {
   
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	/**
	 * 根据InputStream构建ByteChannel
	 */
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
   
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 计算资源文件字节长度
	 */
	@Override
	public long contentLength() 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) {
   
			}
		}
	}

	/**
	 *  获取文件最后一次修改时间 timestamp
	 */
	@Override
	public long lastModified() 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;
	}

	/**
	 * 获取资源File(拿到要获取最后修改时间的资源File)
	 */
	protected File getFileForLastModifiedCheck() throws IOException {
   
		return getFile();
	}

	/**
	 * 根据相对路径构造Resource,默认实现抛出 FileNotFoundException 异常,交个子类实现
	 */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
   
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/**
	 * 获取资源名称,默认返回null,交给子类实现
	 */
	@Override
	@Nullable
	public String getFilename() {
   
		return null;
	}


	/**
	 * This implementation compares description strings.
	 * @see #getDescription()
	 */
	@Override
	public boolean equals(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() {
   
		return getDescription().hashCode();
	}

	/**
	 * This implementation returns the description of this resource.
	 * @see #getDescription()
	 *  返回资源描述
	 */
	@Override
	public String toString() {
   
		return getDescription();
	}

}

3.资源加载器ResourceLoader结构体系

在这里插入图片描述

  • ResourcePatternResolver:是对ResourceLoader的拓展,根据资源文件location一次加载多个资源文件,返回一个Resource列表。
  • PathMatchingResourcePatternResolver: 是ResourcePatternResolver的最常用的子类,除了支持"classpath*:"前缀外,还支持Ant锋哥的路径匹配模式(如:**/*.xml)。
  • FileSystemResourceLoader:继承了DefaultResourceLoader并覆写了getResourceByPath()方法。该类主要增强了可以从文件系统加载资源并返回FileSystemResource
  • ClassRelativeResourceLoader :是类似于FileSystemResourceLoader,是DefaultResourceLoader的另一个子类 ,也是覆写了getResourceByPath()。该类主要增强了可以根据给定的Class所在的包或者包的子包下记载资源,返回的是ClassPathResource

4.源码分析

4.1 资源定位:ResourceLoader

Spring将资源的定义和资源的加载区分开来,Resource定义了统一的资源,那资源加载则由ResourceLoader来统一加载。

public interface ResourceLoader {
   

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


	/**
	 * Return a Resource handle for the specified resource location.
	 * 通过资源定位返回资源文件
	 * <p>The handle should always be a reusable resource descriptor,
	 * allowing for multiple {@link Resource#getInputStream()} calls.
	 * <p><ul>
	 * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
	 * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
	 * <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
	 * (This will be implementation-specific, typically provided by an
	 * ApplicationContext implementation.)
	 * </ul>
	 * <p>Note that a Resource handle does not imply an existing resource;
	 * you need to invoke {@link Resource#exists} to check for existence.
	 * @param location the resource location
	 * @return a corresponding Resource handle (never {@code null})
	 * @see #CLASSPATH_URL_PREFIX
	 * @see Resource#exists()
	 * @see Resource#getInputStream()
	 */
	Resource getResource(String location);

	/**
	 * Expose the ClassLoader used by this ResourceLoader.
	 *  返回资源加载器的类加载器
	 * <p>Clients which need to access the ClassLoader directly can do so
	 * in a uniform manner with the ResourceLoader, rather than relying
	 * on the thread context ClassLoader.
	 * @return the ClassLoader
	 * (only {@code null} if even the system ClassLoader isn't accessible)
	 * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
	 * @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
	 */
	@Nullable
	ClassLoader getClassLoader();

}

ResourceLoader为Spring定义的统一资源加载接口。

4.2 资源加载器的默认实现:DefaultResourceLoader
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);
	
	/**
	 * 无参构造函数
	 * <p>ClassLoader access will happen using the thread context class loader
	 */
	public DefaultResourceLoader() {
   
	    // 默认使用thread context class loader
		this.classLoader = ClassUtils.getDefaultClassLoader();
	}

	/**
	 *  可以手动设置类加载器
	 * @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 {@code null}
	 * 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 this ResourceLoader's initialization.
	 */
	public void setClassLoader(@Nullable ClassLoader classLoader) {
   
		this.classLoader = classLoader;
	}

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

	/**
	 *  这里是设置用自定义的资源加载协议,是DefaultResourceLoaderde的SPI
	 *  允许用户自定义加载协议美不需要集成ResourceLoader的子类
	 *  用户自定义加载资源协议需要自定义实现ProtocolResolver接口,实现resolve方法,然后在此处添加到加载协议
	 * 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.
	 * @since 4.3
	 */
	public Collection<ProtocolResolver> getProtocolResolvers() {
   
		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
	 * @since 5.0
	 */
	@SuppressWarnings(<
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值