第一篇 再读Spring 之 从Resource说起

第一篇 再读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的处理

  1. 定义统一接口 ProtocolResolver;
  2. 在DefaultResourceLoader中定义protocolResolvers,并将已实现的ProtocolResolver加入其中;在getResource方法中遍历protocolResolver完成资源加载;

5. 重温策略模式

  1. 针对所有协议定义统一handler接口;
  2. 在系统启动过程中动态注册handler到HandlerFactory中;
  3. 外部调用方依赖具体协议,直接依赖HandlerFactory;
  4. 后续扩展只需要增加新的handler实现即可;

6. 总结

以上是本篇文章的全部内容,从Spring的resource,到JDK的URL处理,再到对设计模式的重温。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值