第一篇 再读Spring 之 从Resource说起
文章目录
1. 前言
最近重新读Spring源码,记录个人阅读中关联到的点滴。
2. 为何定义Resource
因为Spring读取的BeanDefinition文件最终是一个byte[], 本身不关心byte[]的物理载体是一个本地文件,远程文件或者内存中构造的字符串对应的byte[]。因此,在Spring宏达的上层建筑之下需要有一个统一描述各种形式载体的抽象对象,这就是Resource,具体位置在
org.springframework.core.io.Resource
3. 与URL、URI的异同
URL,Unified Resource Location,统一资源定位符,解决的是资源的定位问题,也就是在哪里,怎么访问到(后续应该走网络还是本地文件系统)。URI, Unified Resource Identification,统一资源标识符,解决不同类型资源的唯一标识问题,可以理解URL是某个领域内唯一的,而URI是跨领域唯一。显然,URL和URI描述了Resource的一部分。除了这些,Spring的Resource还定义了资源的有效性,是否可访问,长度,以及URL和URI的获取,所以范围更大。
源码如下:
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;
public interface Resource extends InputStreamSource {
/**
* Determine whether this resource actually exists in physical form.
* <p>This method performs a definitive existence check, whereas the
* existence of a {@code Resource} handle only guarantees a valid
* descriptor handle.
*/
boolean exists();
/**
* Indicate whether non-empty contents of this resource can be read via
* {@link #getInputStream()}.
* <p>Will be {@code true} for typical resource descriptors that exist
* since it strictly implies {@link #exists()} semantics as of 5.1.
* Note that actual content reading may still fail when attempted.
* However, a value of {@code false} is a definitive indication
* that the resource content cannot be read.
* @see #getInputStream()
* @see #exists()
*/
default boolean isReadable() {
return exists();
}
/**
* Indicate whether this resource represents a handle with an open stream.
* If {@code true}, the InputStream cannot be read multiple times,
* and must be read and closed to avoid resource leaks.
* <p>Will be {@code false} for typical resource descriptors.
*/
default boolean isOpen() {
return false;
}
/**
* Determine whether this resource represents a file in a file system.
* A value of {@code true} strongly suggests (but does not guarantee)
* that a {@link #getFile()} call will succeed.
* <p>This is conservatively {@code false} by default.
* @since 5.0
* @see #getFile()
*/
default boolean isFile() {
return false;
}
/**
* Return a URL handle for this resource.
* @throws IOException if the resource cannot be resolved as URL,
* i.e. if the resource is not available as descriptor
*/
URL getURL() throws IOException;
/**
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
* @since 2.5
*/
URI getURI() throws IOException;
/**
* Return a File handle for this resource.
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
* absolute file path, i.e. if the resource is not available in a file system
* @throws IOException in case of general resolution/reading failures
* @see #getInputStream()
*/
File getFile() throws IOException;
/**
* Return a {@link ReadableByteChannel}.
* <p>It is expected that each call creates a <i>fresh</i> channel.
* <p>The default implementation returns {@link Channels#newChannel(InputStream)}
* with the result of {@link #getInputStream()}.
* @return the byte channel for the underlying resource (must not be {@code null})
* @throws java.io.FileNotFoundException if the underlying resource doesn't exist
* @throws IOException if the content channel could not be opened
* @since 5.0
* @see #getInputStream()
*/
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
* Determine the content length for this resource.
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
long contentLength() throws IOException;
/**
* Determine the last-modified timestamp for this resource.
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
long lastModified() throws IOException;
/**
* Create a resource relative to this resource.
* @param relativePath the relative path (relative to this resource)
* @return the resource handle for the relative resource
* @throws IOException if the relative resource cannot be determined
*/
Resource createRelative(String relativePath) throws IOException;
/**
* Determine a filename for this resource, i.e. typically the last
* part of the path: for example, "myfile.txt".
* <p>Returns {@code null} if this type of resource does not
* have a filename.
*/
@Nullable
String getFilename();
/**
* Return a description for this resource,
* to be used for error output when working with the resource.
* <p>Implementations are also encouraged to return this value
* from their {@code toString} method.
* @see Object#toString()
*/
String getDescription();
}
4. JDK的URL处理机制
代码如下:
URL url = new URL(“http://www.csdn.com”);
URLConnectin connection = url.openConnection();
// 连接
connection.connect();
// 获取InputStream
InputStream iStream = connect.getInputStream();
// 获取OutputStream
OutputStream oStream = connect.getOutputStream();
// 执行IO操作...
这里想问的问题是,到底支持哪些前缀的protocol, 如何扩展protocol.
4.1 支持哪些protocol
可以查看URL构造函数,关键代码如下:
if (this.protocol == null) {
throw new MalformedURLException("no protocol: " + original);
} else if (handler == null && (handler = getURLStreamHandler(this.protocol)) == null) {
// 这里获取 StreamHandler
throw new MalformedURLException("unknown protocol: " + this.protocol);
} else {
this.handler = handler;
i = spec.indexOf(35, start);
if (i >= 0) {
this.ref = spec.substring(i + 1, limit);
limit = i;
}
if (isRelative && start == limit) {
this.query = context.query;
if (this.ref == null) {
this.ref = context.ref;
}
}
handler.parseURL(this, spec, start, limit);
}
再看getStreamHandler(),首先从handlers中获取对应protocol的handler。默认handler来自sun.net.www.protocol包下面。如果没有则通过lookupViaProvider和lookupViaProperty。前者基于SPI机制加载目标StreamHandler,后者读取环境变量java.protocol.handler.pkgs配置加载目标Handler。此处的设计模式非常经典,后续讨论。
static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = (URLStreamHandler)handlers.get(protocol);
if (handler != null) {
return handler;
} else {
boolean checkedWithFactory = false;
boolean overrideableProtocol = isOverrideable(protocol);
URLStreamHandlerFactory fac;
if (overrideableProtocol && VM.isBooted()) {
fac = factory;
if (fac != null) {
handler = fac.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
if (handler == null && !protocol.equalsIgnoreCase("jar")) {
handler = lookupViaProviders(protocol);
}
if (handler == null) {
handler = lookupViaProperty(protocol);
}
}
if (handler == null) {
handler = defaultFactory.createURLStreamHandler(protocol);
}
synchronized(streamHandlerLock) {
URLStreamHandler handler2 = null;
handler2 = (URLStreamHandler)handlers.get(protocol);
if (handler2 != null) {
return handler2;
} else {
if (overrideableProtocol && !checkedWithFactory && (fac = factory) != null) {
handler2 = fac.createURLStreamHandler(protocol);
}
if (handler2 != null) {
handler = handler2;
}
if (handler != null) {
handlers.put(protocol, handler);
}
return handler;
}
}
}
}
4.2 Spring中对Resource的处理
- 定义统一接口 ProtocolResolver;
- 在DefaultResourceLoader中定义protocolResolvers,并将已实现的ProtocolResolver加入其中;在getResource方法中遍历protocolResolver完成资源加载;
5. 重温策略模式
- 针对所有协议定义统一handler接口;
- 在系统启动过程中动态注册handler到HandlerFactory中;
- 外部调用方依赖具体协议,直接依赖HandlerFactory;
- 后续扩展只需要增加新的handler实现即可;
6. 总结
以上是本篇文章的全部内容,从Spring的resource,到JDK的URL处理,再到对设计模式的重温。