Spring 资源管理
Spring 资源管理类图结构:www.processon.com/view/6311a9…
Chapter 1 概述
1 为什么 Spring 不使用 Java 标准资源管理,而选择重新发明轮子?
- Java 标准资源管理强大,然而扩展复杂(不同的作者对资源理解不一致,导致扩展纷杂),资源存储方式并不统一
- Spring 要自立门户,打造自己的生态系统
- Spring “抄”、“超” 和 “潮”
Chapter 2 Java 标准资源管理
1 Java 标准资源管理的职责
| 职责 | 说明 |
|---|---|
| 面向资源 | 文件系统、artifact(jar、war、ear 文件)以及远程资源(HTTP、FTP 等) |
| API 整合 | java.lang.ClassLoader#getResource、java.io.File 或 java.net.URL |
| 资源定位 | java.net.URL 或 java.net.URI |
| 面向流式存储 | java.net.URLConnection |
| 协议扩展 | java.net.URLStreamHandler 或 java.net.URLStreamHandlerFactory |
java.lang.ClassLoader 可以做到文件的获取,在调用 getResource 后,会得到相应的 URL
URI: 表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行标识的。
URL:统一资源定位符,是 URI 的子集
URL 的格式: protocol :// hostname[:port] / path / ;parameters#fragment (有相关协议 File、Http、Ftp)
协议 / 主机名[端口号] / 路径 / 参数
URL 和 URI 的区别 :只要能唯一标识资源的就是URI,在URI的基础上给出其资源的访问方式的就是URL
2 Java URL 协议扩展
1 、实现 URLStreamHandler 并放置在 sun.net.www.protocol.${protocol}.Handler 包下
实现类名必须为 “Handler” 实现类命名规则:
- 默认:sun.net.www.protocol.${protocol}.Handler
- 自定义:通过 Java Properties java.protocol.handler.pkgs 指定实现类包名, 实现类名必须为“Handler”。如果存在多包名指定,通过分隔符 “|”
实现步骤:
-
在项目根目录下创建 sun.net.www.protocol.${protocol}.Handler 包。
- ${protocol} 是自己定义的协议名称,如果为 x ,则为 x 协议,例如 sun.net.www.protocol.x
- Handler 则是在该包下创建的类名称,必须为这个名称。
-
Handler 需要继承 URLStreamHandler 类并重写 openConnection 方法
public class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new XUrlConnection(u);
}
}
3.创建 ${protocol}UrlConnection 并继承 UrlConnection
package sun.net.www.protocol.x;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class XUrlConnection extends URLConnection {
private final ClassPathResource classPathResource;
protected XUrlConnection(URL url) {
super(url);
this.classPathResource = new ClassPathResource(url.getPath());
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
return classPathResource.getInputStream();
}
}
测试示例:
package sun.net.www.protocol.x;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
public class HandlerTest {
public static void main(String[] args) throws IOException {
// 类似于 classpath:/META-INF/default.properties
URL url = new URL("x:///META-INF/default.properties");
URLConnection urlConnection = url.openConnection();
String s = StreamUtils.copyToString(urlConnection.getInputStream(), Charset.forName("UTF-8"));
System.out.println(s);
}
}
2 、自定义实现
- 在项目工程内创建协议包 org.geekbang.thinking.in.spring.resource.springx
- 在 springx 下创建 Handler,并实现 URLStreamHandler
- 实现 XUrlConnection 继承 UrlConnection,并重写 getInputStream 方法。
- 在项目启动参数添加指定协议包的路径,不包括 springx -Djava.protocol.handler.pkgs=org.geekbang.thinking.in.spring.resource 注意: = 号左右两边不能有空格
3、基于 URLStreamHandlerFactory 进行协议扩展
实现方案:
- 实现 URLStreamHandlerFactory 接口,并重写 createURLStreamHandler 方法,
public class MyUrlStreamHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return new Handler();
}
}
- 该方法需要返回一个 URLStreamHandler 。我们创建一个 Handler 并继承自扩展一的 Handler 即可。
- 示例使用时,需要调用URL.setURLStreamHandlerFactory 方法替换 Factory。
public class HandlerTest {
public static void main(String[] args) throws IOException {
URL.setURLStreamHandlerFactory(new MyUrlStreamHandlerFactory());
URL url = new URL("factory:///META-INF/dev.properties");
InputStream inputStream = url.openStream();
String s = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
System.out.println(s);
}
}
注意事项:
- 基于 java.net.URLStreamHandlerFactory 这种扩展机制存在缺陷,单个 ClassLoader 里面只允许有一次
- 基于 java.net.URLStreamHandler 可以根据不同的协议进行扩展
URLStreamHanlderFactory 扩展机制分析
- URLStreamHanlderFactory 类可以创建一个 URLStreamHanlder
- URLStreamHanlder 可以根据一个 URL 去打开一个连接
- URL 中的 URLStreamHandlerFactory 是个静态属性,一个 ClassLoader 中只会存在一个,并且,这个属性可以为空
public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
synchronized (streamHandlerLock) {
// factory 已经设置过的话,会抛出错误
if (factory != null) {
throw new Error("factory already defined");
}
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkSetFactory();
}
handlers.clear();
factory = fac;
}
}
3 、JDK 1.8 內建协议实现
| file | sun.net.www.protocol.file.Handler |
| ftp | sun.net.www.protocol.ftp.Handler |
| http | sun.net.www.protocol.http.Handler |
| https | sun.net.www.protocol.https.Handler |
| jar | sun.net.www.protocol.jar.Handler |
| mailto | sun.net.www.protocol.mailto.Handler |
| netdoc | sun.net.www.protocol.netdoc.Handler |
Chapter 3 Spring 资源接口
1 资源接口
| 类型 | 接口 |
|---|---|
| 输入流 | org.springframework.core.io.InputStreamSource |
| 只读资源 | org.springframework.core.io.Resource |
| 可写资源 | org.springframework.core.io.WritableResource |
| 编码资源 | org.springframework.core.io.support.EncodedResource |
| 上下文资源 | org.springframework.core.io.ContextResource |
2 输入流 StreamSource
public interface InputStreamSource {
/**
* 返回一个输入流
*/
InputStream getInputStream() throws IOException;
}
3 只读资源 Resource
Resource 可读资源,不可写,但是存在一些漏洞的地方,可以通过 getURL 去获取URL, 然后再OpenConnection 开启一个流进行写操作。 但正常情况下,要写资源还是需要使用 WriteableResource
- exists(); 判断资源是否存在
- isReadable(); 资源是否可读
- isOpen();是否可以打开
- isFile(); 是否为文件
- getURL(); 返回URL
- getURI(); 返回URI
- getFile(); 返回文件
- readableChannel(); 创建返回一个新的 ReadableByteChannel
- contentLength(); 返回资源的长度
- lastModified(); 返回上次修改的时间
- createRelative(); 创建与此资源相关的资源
- getFilename(); 获取文件名称
- getDescription(); 获取资源描述
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;
}
4 可写资源 WritableResource
WriteableResource 可写资源
- isWritable(); 是否可写
- getOutputStream(); 获取写的流
- WritableByteChannel(); 返回一个写入的管道,NIO 支持
public interface WritableResource extends Resource {
default boolean isWritable() {
return true;
}
OutputStream getOutputStream() throws IOException;
}
5 编码资源 EncodedResource
EncodedResource 可编码资源。 可编码的实现方式为 将 Resource 资源转换为 Reader。
public class EncodedResource implements InputStreamSource {
private final Resource resource;
@Nullable
private final String encoding;
@Nullable
private final Charset charset;
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
public final Resource getResource() {
return this.resource;
}
@Nullable
public final String getEncoding() {
return this.encoding;
}
@Nullable
public final Charset getCharset() {
return this.charset;
}
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
@Override
public InputStream getInputStream() throws IOException {
return this.resource.getInputStream();
}
}
6 上下文资源 ContextResource
通常在 Servlet 环境下使用
public interface ContextResource extends Resource {
/**
* 返回封闭“上下文”中的路径。
* <p>这通常是相对于上下文特定根目录的路径,
* 例如ServletContext 根或 PortletContext 根。
*/
String getPathWithinContext();
}
Chapter 4 Spring 内建 Resource 实现
| 资源来源 | 资源协议 | 实现类 |
|---|---|---|
| Bean 定义 | 无 | org.springframework.beans.factory.support.BeanDefinitionResource |
| 数组 | 无 | org.springframework.core.io.ByteArrayResource |
| 类路径 | classpath:/ | org.springframework.core.io.ClassPathResource |
| 文件系统 | file:/ | org.springframework.core.io.FileSystemResource |
| URL | URL 支持的协议 | org.springframework.core.io.UrlResource |
| ServletContext | 无 | org.springframework.web.context.support.ServletContex tResource |
2 Bean 定义 BeanDefinitionResource
BeanDefinitionResource 该接口使用的较少,大多为 Spring 内部使用 exists() 返回不存在 isReadable() 返回不可读。 大多作用为设置 Description。
class BeanDefinitionResource extends AbstractResource {
@Override
public String getDescription() {
return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription();
}
}
3 数组 ByteArrayResource
字节流数组资源:该类的 getInputStream(); 方法返回的是一个 Java 的 ByteArrayInputStream。
public class ByteArrayResource extends AbstractResource {
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
}
4 类路径 ClassPathResource
ClassPathResource 是采用 ClassLoader 获取 InputStream 输入流。 ClassPathResource 构造方法中,如果没有传入 classLoader 则会初始化一个ClassLoader
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
@Nullable
private ClassLoader classLoader;
@Nullable
private Class<?> clazz;
/**
* Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
* A leading slash will be removed, as the ClassLoader resource access
* methods will not accept it.
* <p>The thread context class loader will be used for
* loading the resource.
* @param path the absolute path within the class path
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
*/
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
/**
* Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
* A leading slash will be removed, as the ClassLoader resource access
* methods will not accept it.
* @param path the absolute path within the classpath
* @param classLoader the class loader to load the resource with,
* or {@code null} for the thread context class loader
* @see ClassLoader#getResourceAsStream(String)
*/
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;
// ClassPathResource 构造方法中,如果没有传入 classLoader 则会初始化一个ClassLoader
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
/**
* Create a new {@code ClassPathResource} for {@code Class} usage.
* The path can be relative to the given class, or absolute within
* the classpath via a leading slash.
* @param path relative or absolute path within the class path
* @param clazz the class to load resources with
* @see java.lang.Class#getResourceAsStream
*/
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
/**
* Create a new {@code ClassPathResource} with optional {@code ClassLoader}
* and {@code Class}. Only for internal usage.
* @param path relative or absolute path within the classpath
* @param classLoader the class loader to load the resource with, if any
* @param clazz the class to load resources with, if any
* @deprecated as of 4.3.13, in favor of selective use of
* {@link #ClassPathResource(String, ClassLoader)} vs {@link #ClassPathResource(String, Class)}
*/
@Deprecated
protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
this.path = StringUtils.cleanPath(path);
this.classLoader = classLoader;
this.clazz = clazz;
}
/**
* Return the path for this resource (as resource path within the class path).
*/
public final String getPath() {
return this.path;
}
/**
* Return the ClassLoader that this resource will be obtained from.
*/
@Nullable
public final ClassLoader getClassLoader() {
return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
}
/**
* This implementation checks for the resolution of a resource URL.
* @see java.lang.ClassLoader#getResource(String)
* @see java.lang.Class#getResource(String)
*/
@Override
public boolean exists() {
return (resolveURL() != null);
}
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
*/
@Nullable
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
/**
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
// 如果类存在,就采用类的 classLoader 来获取 InputStream
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
// 采用当前对象的 classLoader 进行加载
is = this.classLoader.getResourceAsStream(this.path);
}
else {
// 调用系统的 classLoader 进行加载
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
// 如果还是不存在,则抛出文件找不到异常。但实际上到不了这里
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
/**
* This implementation returns a URL for the underlying class path resource,
* if available.
* @see java.lang.ClassLoader#getResource(String)
* @see java.lang.Class#getResource(String)
*/
@Override
public URL getURL() throws IOException {
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* This implementation creates a ClassPathResource, applying the given path
* relative to the path of the underlying resource of this descriptor.
* @see org.springframework.util.StringUtils#applyRelativePath(String, String)
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
new ClassPathResource(pathToUse, this.classLoader));
}
/**
* This implementation returns the name of the file that this class path
* resource refers to.
* @see org.springframework.util.StringUtils#getFilename(String)
*/
@Override
@Nullable
public String getFilename() {
return StringUtils.getFilename(this.path);
}
/**
* This implementation returns a description that includes the class path location.
*/
@Override
public String getDescription() {
StringBuilder builder = new StringBuilder("class path resource [");
String pathToUse = this.path;
if (this.clazz != null && !pathToUse.startsWith("/")) {
builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
builder.append('/');
}
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
builder.append(pathToUse);
builder.append(']');
return builder.toString();
}
/**
* This implementation compares the underlying class path locations.
*/
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ClassPathResource)) {
return false;
}
ClassPathResource otherRes = (ClassPathResource) other;
return (this.path.equals(otherRes.path) &&
ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
}
/**
* This implementation returns the hash code of the underlying
* class path location.
*/
@Override
public int hashCode() {
return this.path.hashCode();
}
}
5 文件系统 FileSystemResource
FileSystemResource 文件资源接口 具有 File、FilePath 属性。构造方法中需要传入两个属性中的一个,另外一个属性可以反推。 getInputStream(); 方法返回的是 Nio 的 File 文件。
6 URL UrlResource
UrlResource 根据 URL 读取 InputStream。 getInputStream(); 方法会开启一个 URLConnection,再获取 URL 中的输入流。
public class UrlResource extends AbstractFileResolvingResource {
private URL getCleanedUrl(URL originalUrl, String originalPath) {
String cleanedPath = StringUtils.cleanPath(originalPath);
if (!cleanedPath.equals(originalPath)) {
try {
return new URL(cleanedPath);
}
catch (MalformedURLException ex) {
// Cleaned URL path cannot be converted to URL -> take original URL.
}
}
return originalUrl;
}
@Override
public InputStream getInputStream() throws IOException {
// 开启 url 中的 URLConnection
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
// 获取 URLConnection 中的输入流
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
@Override
public URL getURL() {
return this.url;
}
@Override
public URI getURI() throws IOException {
if (this.uri != null) {
return this.uri;
}
else {
return super.getURI();
}
}
}
Chapter 5 Spring Resource 接口扩展
1 概述
可写资源接口 org.springframework.core.io.WritableResource
- org.springframework.core.io.FileSystemResource
- org.springframework.core.io.FileUrlResource(@since 5.0.2)
- org.springframework.core.io.PathResource(@since 4.0 & @Deprecated) 编码资源接口
- org.springframework.core.io.support.EncodedResource
2 FileSystemResource 与 EncodedResource 演示
package org.geekbang.thinking.in.spring.resource;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.EncodedResource;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
/**
* @ClassName EncodedFileSystemResourceDemo
* @Description {@link FileSystemResource} {@link EncodedResource} 示例演示
* @Author WQ
* @Date 2022/8/31 13:22
* @Version 1.0
*/
public class EncodedFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String path = "C:\Users\Administrator.WIN-25IPOL1DU1L\Desktop\画图\data_2021\jk\thinking-in-spring\resource\src\main\java\org\geekbang\thinking\in\spring\resource\EncodedFileSystemResourceDemo.java";
File file = new File(path);
FileSystemResource fileSystemResource = new FileSystemResource(file);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
Reader reader = encodedResource.getReader();
String s = IOUtils.toString(reader);
System.out.println(s);
}
}
Chapter 6 Spring 资源加载器
1 Resource 加载器
org.springframework.core.io.ResourceLoader
-
org.springframework.core.io.DefaultResourceLoader
- org.springframework.core.io.FileSystemResourceLoader
- org.springframework.core.io.ClassRelativeResourceLoader
- org.springframework.context.support.AbstractApplicationContext
FileSystemResourceLoader、ClassRelativeResourceLoader 、AbstractApplicationContext 都继承自 DefaultResourceLoader。 前两者主要重写 getResourceByPath 方法,差异在于,一个根据路径获取文件,一个是根据 ClassLoader 加载。 AbstractApplicationContext 则重写了其他方法。
/**
* @ClassName EncodedFileSystemResourceLoaderDemo
* @Description {@link FileSystemResourceLoader} 文件系统加载器示例
* @Author WQ
* @Date 2022/8/31 13:22
* @Version 1.0
*/
public class EncodedFileSystemResourceLoaderDemo {
public static void main(String[] args) throws IOException {
String path = "C:\Users\Administrator.WIN-25IPOL1DU1L\Desktop\画图\data_2021\jk\thinking-in-spring\resource\src\main\java\org\geekbang\thinking\in\spring\resource\EncodedFileSystemResourceDemo.java";
FileSystemResourceLoader loader = new FileSystemResourceLoader();
Resource resource = loader.getResource(path);
EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
Reader reader = encodedResource.getReader();
String s = IOUtils.toString(reader);
System.out.println(s);
}
}
2 ResourceLoader
资源加载器 API
public interface ResourceLoader {
/** 从类路径加载的伪 URL 前缀:“classpath:”。*/
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* 根据路径返回资源
*/
Resource getResource(String location);
/**
* 获取 ClassLoader
*/
@Nullable
ClassLoader getClassLoader();
}
3 DefaultResourceLoader
DefaultResourceLoader 具备 ResourceLoader 的能力,可以根据路径获取 Resource 资源,也可以获取 ClassLoader
public class DefaultResourceLoader implements ResourceLoader {
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
public void clearResourceCaches() {
this.resourceCaches.clear();
}
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
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);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
}
4 FileSystemResourceLoader
FileSystemResourceLoader 继承 DefaultResourceLoader 类。 重写了 getResourceByPath 方法
public class FileSystemResourceLoader extends DefaultResourceLoader {
@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();
}
}
}
5 ClassRelativeResourceLoader
package org.springframework.core.io;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class ClassRelativeResourceLoader extends DefaultResourceLoader {
private final Class<?> clazz;
@Override
protected Resource getResourceByPath(String path) {
return new ClassRelativeContextResource(path, this.clazz);
}
private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
private final Class<?> clazz;
public ClassRelativeContextResource(String path, Class<?> clazz) {
super(path, clazz);
this.clazz = clazz;
}
@Override
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassRelativeContextResource(pathToUse, this.clazz);
}
}
}
Chapter 7 Spring 通配路径资源加载器
1 概述
通配路径 ResourceLoader
-
org.springframework.core.io.support.ResourcePatternResolver
- org.springframework.core.io.support.PathMatchingResourcePatternResolver
路径匹配器 (采用通配符表达式进行比较)
-
org.springframework.util.PathMatcher
- Ant 模式匹配实现 - org.springframework.util.AntPathMatcher
2 ResourcePatternResolver 资源模式处理器
资源通配符处理器(采用策略模式实现)
- getResources(): 方法传入一个通配符表达式,返回查找到的资源数组。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
3 PathMatchingResourcePatternResolver
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 路径以 classpath*: 开头?
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
// substring 截取 n 位后面的内容
// 如果匹配
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 类路径资源模式
return findPathMatchingResources(locationPattern);
}
else {
// 具有给定名称的所有类路径资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isTraceEnabled()) {
logger.trace("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
4 findAllClassPathResources
protected Resource[] findAllClassPathResources(String location) throws IOException {
// 获取路径
String path = location;
// 如果以 / 开头,则接去掉
if (path.startsWith("/")) {
path = path.substring(1);
}
// 使用剩余的路径去找资源
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isTraceEnabled()) {
logger.trace("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
5 doFindAllClassPathResources
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
// 建立资源列表
Set<Resource> result = new LinkedHashSet<>(16);
// 获取 ClassLoader
ClassLoader cl = getClassLoader();
// 将 path 转化为 URL
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
// 获取 URL
URL url = resourceUrls.nextElement();
// 将从 ClassLoader 返回的给定 URL 转换为 {@link Resource}。
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
Chapter 8 Spring 通配路径资源扩展
1 概述
实现 org.springframework.util.PathMatcher 重置 PathMatcher
- PathMatchingResourcePatternResolver#setPathMatcher
在 PathMatchingResourcePatternResolver 根据路径获取资源列表的时候, 会根据 PathMatcher 的 isPattern();方法决定是否走 通配符模式进行查找
2 扩展示例
路径设置注意事项: // 读取当前 package 对应的所有的 .java 文件 // determineRootDir(locationPattern); 方法会解析根节点路径,该方法会解析 RootDirPath 的末尾下表,如果 resource 路径后面没有 / 会导致解析失败。 // PathMatchingResourcePatternResolver#retrieveMatchingFiles 方法中会判断 rootDir 路径对应的文件夹是否存在,此处可以 Debug 看看路径那里有问题,放到文件夹中进行访问。
扩展步骤主要分为两步:
- 自定义 PathMatcher 继承 PathMatcher,并重写 isPattern(); 与 match(); 方法
- 将自定义 PathMatcher 设置到 PathMatchingResourcePatternResolver 中。
/**
* 自定义 {@link ResourcePatternResolver} 示例
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see ResourcePatternResolver
* @see PathMatchingResourcePatternResolver
* @see PathMatcher
* @since
*/
public class CustomizedResourcePatternResolverDemo {
public static void main(String[] args) throws IOException {
// 读取当前 package 对应的所有的 .java 文件
// determineRootDir(locationPattern); 方法会解析根节点路径,该方法会解析 RootDirPath 的末尾下表,如果 resource 路径后面没有 / 会导致解析失败。
// PathMatchingResourcePatternResolver#retrieveMatchingFiles 方法中会判断 rootDir 路径对应的文件夹是否存在,此处可以 Debug 看看路径那里有问题,放到文件夹中进行访问。
//构建 rootDirPath
String currentPackagePath = "/" + System.getProperty("user.dir") + "/resource/src/main/java/org/geekbang/thinking/in/spring/resource/";
// 构建通配符
String locationPattern = currentPackagePath + "*.java";
// 构建通配符资源资源匹配处理器
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(new FileSystemResourceLoader());
// 添加路径匹配处理器
resourcePatternResolver.setPathMatcher(new JavaFilePathMatcher());
// 根据指定路径获取资源
Resource[] resources = resourcePatternResolver.getResources(locationPattern);
// 进行输出
Stream.of(resources).map(ResourceUtils::getContent).forEach(System.out::println);
}
/**
* 扩展 PathMatcher 的匹配规则
*/
static class JavaFilePathMatcher implements PathMatcher {
@Override
public boolean isPattern(String path) {
return path.endsWith(".java");
}
@Override
public boolean match(String pattern, String path) {
return path.endsWith(".java");
}
@Override
public boolean matchStart(String pattern, String path) {
return false;
}
@Override
public String extractPathWithinPattern(String pattern, String path) {
return null;
}
@Override
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
return null;
}
@Override
public Comparator<String> getPatternComparator(String path) {
return null;
}
@Override
public String combine(String pattern1, String pattern2) {
return null;
}
}
}
Chapter 9 依赖注入Spring Resource
1 概述
基于 @Value 实现 @Value(“classpath:/...”) private Resource resource;
@Value("classpath :/META-INF/ .properties") private Resource[] resourceArray;
@Value("${user.dir}") private String currentProjectPath;
2 Resource 资源注入示例
package org.geekbang.thinking.in.spring.resource;
import org.geekbang.thinking.in.spring.resource.util.ResourceUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.Resource;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.stream.Stream;
/**
* @ClassName InjectionResourceDemo
* @Description 注入 {@link Resource} 资源示例
* @Author WQ
* @Date 2022/8/31 17:40
* @Version 1.0
*/
public class InjectionResourceDemo {
@Value("classpath:/META-INF/default.properties")
private Resource defaultPropertyResource;
@Value("classpath*:/META-INF/*.properties")
private Resource[] resourceArray;
@Value("${user.dir}")
private String currentProjectPath;
@PostConstruct
public void init() {
System.out.println(ResourceUtils.getContent(defaultPropertyResource));
Stream.of(resourceArray).map(ResourceUtils::getContent).forEach(System.out::println);
System.out.println(currentProjectPath);
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectionResourceDemo.class);
applicationContext.refresh();
applicationContext.close();
}
}
Chapter 10 依赖注入 ResourceLoader
1 概述
- 方法一:实现 ResourceLoaderAware 回调
- 方法二:@Autowired 注入 ResourceLoader
- 方法三:注入 ApplicationContext 作为 ResourceLoader
2 依赖注入 ResourceLoader 示例
public class InjectionResourceLoaderDemo implements ResourceLoaderAware {
@Autowired
private ResourceLoader resourceLoader;
private ResourceLoader resourceLoaderAware;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
System.out.println(resourceLoader == resourceLoaderAware);
System.out.println(resourceLoaderAware == applicationContext);
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectionResourceLoaderDemo.class);
applicationContext.refresh();
applicationContext.close();
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoaderAware = resourceLoader;
}
}
\