Spring IOC 源码学习 2 加载

1 资源定义

  • spring 为咱们提供了资源的抽象和和访问接口 org.springframework.core.io.Resource, Resource继承了 org.springframework.core.io.InputStreamSource 接口,同时由子类 AbstractResource提供默认的实现方法

接口定义以下java

/* 
 * @see #getInputStream()
 * @see #getURL()
 * @see #getURI()
 * @see #getFile()
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see FileUrlResource
 * @see FileSystemResource
 * @see ClassPathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */
public interface Resource extends InputStreamSource {

	/**
	 * 是否存在
	 */
	boolean exists();

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

	/**
	 * 资源所表明的句柄是否被一个 stream 打开了
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * 是不是文件格式
	 */
	default boolean isFile() {
		return false;
	}

	
	URL getURL() throws IOException;

	
	URI getURI() throws IOException;


	File getFile() throws IOException;

	
	default ReadableByteChannel readableChannel() throws IOException{
		return Channels.newChannel(getInputStream());
	}

	
	long contentLength() throws IOException;

	
	long lastModified() throws IOException;


	Resource createRelative(String relativePath) throws IOException;

	
	@Nullable
	String getFilename();

	
	String getDescription();
复制代码

类图以下 spring

其中express

  • FileSystemResource 和文件资源打交道 而且在Spring 5.0之后 使用 NIO2 API进行读/写交互。
  • ByteArrayResource 对字节数组提供的数据的封装
  • UrlResource 对 java.net.URL类型资源的封装
  • ClassPathResource class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源
  • InputStreamResource 将给定的 InputStream 做为一种资源的 Resource 的实现类。

2 AbstractResource

public abstract class AbstractResource implements Resource {

	/**
	 * 判断文件是否存在,若判断过程产生异常,就关闭对应的流
	 */
	@Override
	public boolean exists() {
        //判断文件是否存在
		try {
			return getFile().exists();
		}
		catch (IOException ex) {
			// 基于 InputStream 进行判断
			try {
				getInputStream().close();
				return true;
			}
			catch (Throwable isEx) {
				return false;
			}
		}
	}

	/**
	 * 默承认读
	 */
	@Override
	public boolean isReadable() {
		return exists();
	}

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

	/**
	 * 直接返回False 表示不是一个文件
	 */
	@Override
	public boolean isFile() {
		return false;
	}

	/**
	 * 直接抛出异常,交给子类去实现
	 */
	@Override
	public URL getURL() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/**
	 * 基于 getURL() 返回的 URL 构建 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");
	}

	/**
	 * 根据 getInputStream() 的返回结果构建 ReadableByteChannel
	 */
	@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) {
			}
		}
	}

	/**
	 * 上次修改时间
	 */
	@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;
	}

	/**
	 * 
	 */
	protected File getFileForLastModifiedCheck() throws IOException {
		return getFile();
	}

	/**
	 * 
	 */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/**
	 * 
	 */
	@Override
	@Nullable
	public String getFilename() {
		return null;
	}


	/**
	 * 
	 */
	@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(); } /** * */ @Override public String toString() { return getDescription(); } 复制代码
  • 若是咱们想要实现自定义的Resource,只需继承AbstractResource 便可

3 统一资源定位 ResourceLoader

Spring将资源的定位和加载进行了隔离数组

  • Resource 对资源进行了统一的定义
  • ResourceLoader 对资源加载进行统一的定义

ResourceLoader源码bash

String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; //默认  classpath:


	Resource getResource(String location);

	
	@Nullable
	ClassLoader getClassLoader();
复制代码
  • getResource 根据提供的参数location 返回Resource 对象,须要提早使用Resource.exist() 来判断对象是否存在。支持多种资源加载
    • URL位置资源,如 "file:C:/test.xml"
    • ClassPath位置资源,如 "classpath:test.xml
    • 相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不一样而不一样
  • getClassLoader 返回 ClassLoader 实例

3.1 子类实现

类图以下ide

3.1.1

DefaultResourceLoader 是 ResourceLoader 的默认实现函数

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);
	
    @Nullable
    private ClassLoader classLoader;

    public DefaultResourceLoader() { // 无参构造函数
    	this.classLoader = ClassUtils.getDefaultClassLoader();
    }
    
    public DefaultResourceLoader(@Nullable ClassLoader classLoader) { // 带 ClassLoader 参数的构造函数
    	this.classLoader = classLoader;
    }
    
    public void setClassLoader(@Nullable ClassLoader classLoader) {
    	this.classLoader = classLoader;
    }
    
    public void addProtocolResolver(ProtocolResolver resolver) {
    	Assert.notNull(resolver, "ProtocolResolver must not be null");
    	this.protocolResolvers.add(resolver);
    }

	
	
    public Collection<ProtocolResolver> getProtocolResolvers() {
        return this.protocolResolvers;
    }

    @Override
    @Nullable
    public ClassLoader getClassLoader() {
    	return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }
}
复制代码

3.1.1.1 构造函数

  • 无参构造函数,使用默认的 ClassLoader 为默认的
    • Thread.currentThread().getContextClassLoader() *在使用无参构造函数的时候能够使用 ClassUtils.getDefaultClassLoader()
  • 有参构造函数 带 ClassLoader 参数的构造函数

3.1.1.2 getResource

  • ResourceLoader 中最核心的方法为 getResource(String location) ,它根据提供的 location 返回相应的 Resource 。而 DefaultResourceLoader 对该方法提供了核心实现(由于,它的两个子类都没有提供覆盖该方法,因此能够判定 ResourceLoader 的资源加载策略就封装在 DefaultResourceLoader 中)
@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");

    // 首先,经过 ProtocolResolver 来加载资源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    // 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    // 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
    } else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    // 而后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,不然返回 UrlResource 类型的资源
    } else {
        try {
            // 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) {
            // 最后,返回 ClassPathContextResource 类型的资源
            
            return getResourceByPath(location);
        }
    }
}
复制代码
  • 首先,经过 ProtocolResolver 来加载资源,成功返回 Resource 。
  • 其次,若 location 以 "/" 开头,则调用 #getResourceByPath() 方法,构造 ClassPathContextResource 类型资源并返回。代码以下:
protected Resource getResourceByPath(String path) {
	return new ClassPathContextResource(path, getClassLoader());
}
复制代码
  • 若 location 以 "classpath:" 开头,则构造 ClassPathResource 类型资源并返回。在构造该资源时,经过 getClassLoader() 获取当前的 ClassLoader
  • 而后,构造 URL ,尝试经过它进行资源定位,若没有抛出 MalformedURLException 异常,则判断是否为 FileURL , 若是是则构造 FileUrlResource 类型的资源,不然构造 UrlResource 类型的资源。
  • 若在加载过程当中抛出 MalformedURLException 异常,则委派 DefaultResourceLoader.getResourceByPath() 方法,实现资源定位加载

3.1.2 ProtocolResolver

代码路径 org.springframework.core.io.ProtocolResolver,做为 DefaultResourceLoader, 实现自定义 Resource,无需集成AbstractResource,只需实现ProtocolResolver接口便可gradle

@FunctionalInterface
public interface ProtocolResolver {

	/**
	 * 使用指定的 ResourceLoader ,解析指定的 location  若成功,则返回对应的 Resource
	 */
	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);

}
复制代码

须要用户自定义它的实现类,而后调用DefaultResourceLoader.addProtocolResolver(ProtocolResolver resolver)方法便可ui

#3.2 FileSystemResourceLoader FileSystemResourceLoader 继承自DefaultResourceLoader 并覆写了 getResourceByPath(String locaition) 方法this

@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();
	    }
	}
复制代码

##3.2.1 FileSystemContextResource 继承自FileSystemResource 实现 ContextResource

  • 构造方法 调用 FileSystemResource 的构造函数来构造 FileSystemResource

##3.2.2 示例

public static void main(String[] args) {
        ResourceLoader resourceLoader = new DefaultResourceLoader();

        Resource fileResource1 = resourceLoader.getResource("D:/Users/cindy/code/demo/build.gradle");
        System.out.println("fileResource1 is FileSystemResource:" + (fileResource1 instanceof FileSystemResource));

        Resource fileResource2 = resourceLoader.getResource("/Users/cindy/code/demo/build.gradle");
        System.out.println("fileResource2 is ClassPathResource:" + (fileResource2 instanceof ClassPathResource));

        Resource urlResource1 = resourceLoader.getResource("file:/Users/cindy/code/demo/build.gradle");
        System.out.println("urlResource1 is UrlResource:" + (urlResource1 instanceof UrlResource));

        Resource urlResource2 = resourceLoader.getResource("http://www.baidu.com");
        System.out.println("urlResource1 is urlResource:" + (urlResource2 instanceof  UrlResource));
    }
    
复制代码
  • 输出
fileResource1 is FileSystemResource:false
fileResource2 is ClassPathResource:true
urlResource1 is UrlResource:true
urlResource1 is urlResource:true
复制代码
  • 若是将 DefaultResourceLoader 改成 FileSystemResourceLoader
fileResource1 is FileSystemResource:true
fileResource2 is ClassPathResource:false
urlResource1 is UrlResource:true
urlResource1 is urlResource:true
复制代码
  • DefaultResourceLoader 对getResourceByPath(String location) 方法处理其实不是很恰当,FileSystemResourceLoader 覆写了 getResourceByPath(String location) 方法,使之从文件系统加载资源并以 FileSystemResource 类型返回,这样咱们就能够获得想要的资源类型
相关文章
相关标签/搜索