撸一撸Spring Framework-IoC系列文章目录
我们经常需要读取外部资源到应用中,比如文本文件、properties文件、图片文件等。这些资源可能位于不同的位置,比如文件系统、classpath下的资源、或者远程服务器上的资源。通常,我们需要通过不同的API分别加载不同类型路径的资源,有诸多不便之处?
?Spring提供了Resource和ResourceLoader系列接口解决上述问题,API非常友好、强大
Resource是对诸如文件系统资源、classpath资源、URL资源等各种资源的抽象
ResourceLoader提供了加载Resource的方法,它通过资源路径前缀自动选择相应的资源类型,为开发者屏蔽了使用不同Resource实现的差异
Resource相关接口可以脱离Spring独立使用,可以通过如下方式加载不同类型资源:
//通过指定不同前缀的资源路径,加载不同类型的资源
//通过文件系统绝对路径加载资源
Resource resource = resourceLoader.getResource("file:D:/code/spring/src/main/resources/demo.xml");
//通过相对于当前项目根目录的相对路径加载资源
Resource resource = resourceLoader.getResource("file:src/main/resources/demo.xml");
//加载classpath下的资源
Resource resource = resourceLoader.getResource("classpath:demo.xml");
Resource resource = resourceLoader.getResource("classpath:com/example/spring/ResourceLoaderDemo.class");
//通过https url加载CSDN上的一篇博客资源 ?
Resource resource = resourceLoader.getResource("https://blog.csdn.net/wb_snail/article/details/108134550");
(tips:spring不仅提供了单个资源的加载方式,还提供了按通配符加载一组资源的方式,下文会有说明)
拿到Resource后,可以调用Resource#getInputStream获取资源输入流,读取其内容:
InputStream inputStream=resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
while (true) {
String line = reader.readLine();
if (line == null)
break;
System.out.println(line);
}
reader.close();
可以通过如下方式获得ResourceLoader实例:
1、实现ResourceLoaderAware接口,由Spring容器启动时注入
2、@Autowired ResourceLoader resourceLoader(Spring容器启动过程中,会向BeanFactory中注入一些特殊的对象(包括ResourceLoader对象?),特殊是因为它们没有被定义为bean,它们是spring内部的组件,spring允许我们通过@Autowire来使用它们)
3、使用ApplicationContext,ApplicationContext继承于ResourceLoader,实际上通过前两种方式拿到的ResourceLoader对象,就是ApplicationContext
4、直接new DefaultResourceLoader(ResourceLoader接口的默认实现类),默认情况下,使用ApplicationContext#getResource时,底层实现就是DefaultResourceLoader
如上所述,Resource接口抽象了各种类型的资源,比较核心的方法有:
getInputStream():继承于InputStreamSource接口,返回资源对应的输入流,用于读取资源
exists():返回资源是否存在的标识
getFile():如果资源存在于文件系统中,返回对应的文件对象,否则抛出FileNotFoundException(比如ByteArrayResource这种只在内存中存在的资源)
getURL():返回资源对应的URL(java.net.URL),URL是资源定位符,上述例子中的"file:D:/demo.xml"、"classpath:demo.xml"、"https://blog.csdn.net/wb_snail/article/details/108134550"都是URL的String表示形式
具体实现类包括(Resource的继承体系比较大,这里只挑选了一些比较常见的Resource实现):
InputStreamSource:Resource的父接口,只有一个getInputStream()方法
WritableResource:可写资源,它的方法getOutputStream()可以返回资源的输出流
FileSystemResource:文件系统资源,可通过File对象、文件系统绝对路径、Path对象(如Paths.get("D:/demo.xml"))来构建(tips:Spring在处理@ComponentScan定义的包路径下的class文件时,会将它们加载为FileSystemResource)
ClassPathResource:classpath下的资源(tips:Spring在处理@PropertyResource、@PropertyResources时,相关配置文件会被加载为ClassPathResource)
UrlResource:引用了一个java.net.URL对象,可以访问任何可以用URL表示的资源(file、https、ftp等资源)
ServletContextResource:web应用资源,资源路径以相对于web应用根目录的路径表示,比如new?ServletContextResource("/WEB-INF/demo.xml")
ByteArrayResource:通过一个二进制数组创建的资源,比如new ByteArrayResource(new String("hello").getBytes(StandardCharsets.UTF_8))
Resource几乎可以表示任何类型的底层资源,除了Spring已经实现的多种资源类型外,你还可以实现自己的Resource,比如DB中的资源,你可以向ResourceLoader中注册一个ProtocolResolver,然后就可以使用ResourceLoader以与其他类型资源无差别的方式加载你的资源。相关源码如下
public class CustomResource {
public static void main(String[] args) {
DefaultResourceLoader resourceLoader=new DefaultResourceLoader();
//注册自定义ProtocolResolver
resourceLoader.addProtocolResolver(new DbProtocolResolver());
//通过"db:"为前缀的路径加载db中的资源
Resource dbResource=resourceLoader.getResource("db:dataSource_fileDB/table_file/column_content");
}
}
//自定义Resource
public class DbResource extends AbstractResource {
private final String path;
...
}
/**
* 实现ProtocolResolver,针对前缀为"db:"的url,返回DBResource
*/
public class DbProtocolResolver implements ProtocolResolver {
private static final String DB_URL_PREFIX="db:";
@Nullable
public Resource resolve(String location, ResourceLoader resourceLoader){
if(location.startsWith(DB_URL_PREFIX)){
return new DbResource(location.substring(DB_URL_PREFIX.length()));
}
return null;
}
}
//以下是DefaultResourceLoader中相关部分的源码
public class DefaultResourceLoader implements ResourceLoader {
//一组ProtocolResolver实例
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
public DefaultResourceLoader() {
}
//调用该方法,注册你自定义的ProtocolResolver
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//加载资源时,优先通过你注册的ProtocolResolver加载资源
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);
}
}
}
}
ResourceLoader仅支持匹配单个资源,其扩展接口ResourcePatternResolver支持以通配符的方式加载所有满足条件的资源,比如"classpath*:META-INF/spring.handlers"、"classpath:META-INF/*.properties"、"/WEB-INF/*-context.xml"、"file:D:/resources/*.properties"
注意"classpath:"和"classpath*:"的区别,后者会把classpath下所有jar包也作为查找目标,@ComponentScan可以扫描jar包下的@Component,正是利用了这个特性,Spring通过查找spring.handlers文件实现SPI也是一样道理
ResourcePatternResolver支持三种通配符:
*:匹配资源路径中的任意字符
?:匹配资源路径中的单个字符
如"*.xml"可以匹配到a.xml、ab.xml,而"?.xml"只能匹配到a.xml
**:匹配任意层级,比如"mapper/**/*Mapper.xml"可以匹配到mapper/RoleMapper.xml、mapper/order/OrderMapper.xml、mapper/order/goods/GoodsMapper.xml
Spring Framework中,ResourcePatternResolver的唯一有效实现是PathMatchingResourcePatternResolver,ApplicationContext也继承于ResourcePatternResolver,默认情况下,其getResources方法会委托给PathMatchingResourcePatternResolver执行
可以通过两种方式使用ResourcePatternResolver?:
//使用PathMatchingResourcePatternResolver加载
ResourcePatternResolver resourcePatternResolver=new PathMatchingResourcePatternResolver();
Resource[] resources=resourcePatternResolver.getResources("classpath*:META-INF/spring.handlers");
//使用ApplicationContext加载
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext();
applicationContext.refresh();
Resource[] resources=applicationContext.getResources("classpath*:META-INF/spring.handlers");
和ResourceLoader一样,PathMatchingResourcePatternResolver同样支持脱离Spring容器使用,非常方便。上面说了,默认情况下,ApplicationContext#getResources方法会委托给PathMatchingResourcePatternResolver执行,因此只要可以拿到ApplicationContext实例,也可以进行基于正则的批量资源加载
|