Spring Resource和ResourceLoader源码解析

Spring用Resource接口抽象所有的底层资源,包括File、ClassPath、URL等。ResourceLoader接口是Resource的加载器,根据资源的路径/路径模式获取Resource实例。

Resource

接口定义

Resource接口的定义如下:

public interface Resource extends InputStreamSource {

	boolean exists();

	default boolean isReadable() {
		return exists();
	}

	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();

}

它继承自InputStreamSource接口,这个接口只有一个方法:

public interface InputStreamSource {

	InputStream getInputStream() throws IOException;

}

这里比较重要的几个方法有:

  • getInputStream(),定位并且打开当前资源,返回该资源的一个InputStream。每次调用都会返回一个新的InputStream。
  • exists(),返回该资源是否物理存在。
  • getURL(),返回该资源的URL句柄。
  • getURI(),返回该资源的URI句柄。
  • isFile(),是否是文件。
  • getFile(),返回该资源的File句柄。

Resource接口的实现

Spring提供了多种Resource实现,我们可以直接使用。

FileSystemResource

FileSystemResource是针对java.io.File的Resource实现类,其构造函数与getInputStream()方法如下:

public class FileSystemResource extends AbstractResource implements WritableResource {

    private final String path;

    @Nullable
    private final File file;

    private final Path filePath;
    
    public FileSystemResource(String path) {
		Assert.notNull(path, "Path must not be null");
		this.path = StringUtils.cleanPath(path);
		this.file = new File(path);
		this.filePath = this.file.toPath();
	}

    //other constructors...
    ...

    @Override
	public InputStream getInputStream() throws IOException {
		try {
			return Files.newInputStream(this.filePath);
		}
		catch (NoSuchFileException ex) {
			throw new FileNotFoundException(ex.getMessage());
		}
	}

    ...
}

可以看到它封装了java.io.File,在获取输入流时就是打开了该文件的NIO文件流。

ClassPathResource

ClassPathResource是类路径下资源的Resource实现。它通过ClassLoader或Class来加载资源。其构造函数和getInputStream()方法的实现如下:

public class ClassPathResource extends AbstractFileResolvingResource {

    private final String path;

    @Nullable
    private ClassLoader classLoader;

    @Nullable
    private Class<?> clazz;

    public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
		Assert.notNull(path, "Path must not be null");
		String pathToUse = StringUtils.cleanPath(path);
		if (pathToUse.startsWith("/")) {
			pathToUse = pathToUse.substring(1);
		}
		this.path = pathToUse;
		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	}

    //other constructors
    ...

    public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}

    ...

}

可以看到它最终委托Class对象或ClassLoader对象来加载资源。如果创建该ClassPathResource时指定了用于加载资源的Class对象,它会通过该Class对象的getResourceAsStream来加载资源;否则,如果指定了ClassLoader,它会通过该ClassLoader对象的getResourceAsStream来加载资源;否则,通过系统ClassLoader来加载资源。

UrlResource

UrlResource是java.net.URL的Resource实现类,用来访问URL可以正常访问的任意对象。其内部封装了URL对象,因此,它支持http、https、file、ftp、jar等协议。其getInputStream()方法实现如下:

public InputStream getInputStream() throws IOException {
	URLConnection con = this.url.openConnection();
	ResourceUtils.useCachesIfNecessary(con);
	try {
		return con.getInputStream();
	}
	catch (IOException ex) {
	    // Close the HTTP connection (if applicable).
	    if (con instanceof HttpURLConnection) {
		    ((HttpURLConnection) con).disconnect();
	    }
        throw ex;
    }
}

可以看到它是打开一个新的到该URL所引用的远程对象的连接,获取URLConnection对象,然后获取输入流。

InputStreamResource

InputStreamResource把一个InputStream封装为Resource,其内部实现大致如下:

public class InputStreamResource extends AbstractResource {

    private final InputStream inputStream;

    private final String description;

    private boolean read = false;

    public InputStreamResource(InputStream inputStream, @Nullable String description) {
	    Assert.notNull(inputStream, "InputStream must not be null");
	    this.inputStream = inputStream;
	    this.description = (description != null ? description : "");
	}

    ...

    @Override
    public InputStream getInputStream() throws IOException, IllegalStateException {
        if (this.read) {
	        throw new IllegalStateException("InputStream has already been read - " +
					"do not use InputStreamResource if a stream needs to be read multiple times");
        }
        this.read = true;
        return this.inputStream;
    }

    ...
}

ByteArrayResource

ByteArrayResource把字节数组封装为Resource。其实现大致如下:

public class ByteArrayResource extends AbstractResource {

    private final byte[] byteArray;

    private final String description;

    public ByteArrayResource(byte[] byteArray, @Nullable String description) {
        Assert.notNull(byteArray, "Byte array must not be null");
        this.byteArray = byteArray;
        this.description = (description != null ? description : "");
    }

    ...

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.byteArray);
    }

    ...
}

ResourceLoader

接口定义

ResourceLoader的接口定义如下:

public interface ResourceLoader {

    Resource getResource(String location);

    @Nullable
    ClassLoader getClassLoader();
}

其中,getResource(String)接口返回指定位置的资源,getClassLoader()接口返回该ResourceLoader所使用的ClassLoader。

ResourceLoader接口的实现

ApplicationContext

Spring应用上下文都实现了ResourceLoader接口,因此所有的应用上下文都可以通过getResource(String)方法获取Resource实例。

DefaultResourceLoader

DefaultResourceLoader是默认的ResourceLoader实现,同时它也是AbstractApplicationContext类的基类。它的实现大致如下:

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);

    ...

    public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    ...

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

    public void addProtocolResolver(ProtocolResolver resolver) {
        Assert.notNull(resolver, "ProtocolResolver must not be null");
        this.protocolResolvers.add(resolver);
    }

    public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
        return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
    }

    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");

        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }

        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        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) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

    ...
}

可以看到,在创建DefaultResourceLoader的时候可以指定类加载器,如果没有指定,就会使用默认的类加载器。

同时,该类维护了一个ProtocolResolver集合,用来处理其他协议。ProtocolResolver是一个接口,其源码如下:

@FunctionalInterface
public interface ProtocolResolver {

    @Nullable
    Resource resolve(String location, ResourceLoader resourceLoader);

}

这是一个函数接口,其唯一的抽象方法resolve,第一个参数是资源路径,第二个参数是针对该类协议的资源的资源加载器。

然后我们看一下DefaultResourceLoader的getResource(String)方法,该方法首先通过用户自己注册的ProtocolResolver尝试解析和加载资源,如果成功则直接返回;若不成功,则判断资源路径是否以/开头,如果以/开头,则直接返回一个ClassPathContextResource,若不是以/开头,但是以classpath:开头,则直接返回一个ClassPathResource,否则,根据协议返回FileUrlResource或UrlResource。

ResourcePatternResolver

ResourcePatternResolver是继承自ResourceLoader接口的一个接口,用来解析路径模式,例如Ant风格的路径模式,其源码如下:

public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}

其中,getResources(String)接口根据输入的路径模式,返回Resource数组。

ResourcePatternResolver的一个实现是PathMatchingResourcePatternResolver,它可以解析指定的资源位置路径到一个或多个匹配的Resource,这里,资源路径可以是一个指向某一个特定的资源的简单路径,也可以是包含classpath*:前缀的路径,路径里还可以使用Ant风格正则表达式。例如,你可以使用以下这些路径:

  • file:C:/context.xml;
  • classpath:/context.xml;
  • /WEB-INF/context.xml;
  • /WEB-INF/*-context.xml;
  • file:C:/some/path/*-context.xml;
  • com/mycompany/**&#47;applicationContext.xml
  • classpath:com/mycompany/**&#47;applicationContext.xml
  • classpath*:META-INF/*-beans.xml

Ant风格正则表达式的细节可以参见这篇文章:https://jinnianshilongnian.iteye.com/blog/1416322

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值