目录
Resources资源和资源加载
1.为什么要讲解Resources
在Java中 java.net.URL类是统一资源定位符的抽象,主要用于描述互联网上资源的一个字符串。一般语 法为:
scheme:[//authority]path[?query][#fragment]
例如:
http://www.smallming.com
file:///C:/users/smallming
代码示例:
@Test
void testURL() throws IOException {
URL url = new URL("file:///D:/a.txt");
InputStream is = url.openStream();
int i =0;
while((i=is.read())!=-1){
System.out.print((char) i);
}
is.close();
}
但是URL在访问资源时功能并不是特别完善。(有时硬写也能访问,但是可能不是很方便。)
例如访问类路径资源、访问Servlet上下文资源等都没有提供统一的标准。
Spring 框架的Resources就是为了解决这个问题的。
2.Spring框架中Resources是什么
想要了解Resources是什么就需要先说一下InputStreamSource接口。InputStreamSource接口是 Spring框架提供的顶级接口。所有Spring访问通过流访问的资源都通过这个接口。
接口中就一个方法,getInputStream()表示获取低级资源(如文件或类路径资源)的输入流。
public interface InputStreamSource {
// 返回资源的字节输入流对象
InputStream getInputStream() throws IOException;
}
而Resources接口是InputStreamSource接口的子接口。里面提供的方法比InputStreamSource接口更多,可以以更多的形式返回物理文件(如文件或类路径资源)的File、URL、InputStream、byte[]内容。
public interface Resource extends InputStreamSource {
// 判断资源是否存在
boolean exists();
// 判断资源是否可以可读的
boolean isReadable();
// 判断资源流是否处于打开状态,如果处于打开状态,不允许重复读取。
boolean isOpen();
// 是否是文件
boolean isFile();
// 获取URL
URL getURL() throws IOException;
// 获取URI
URI getURI() throws IOException;
// 获取资源的文件对象
File getFile() throws IOException;
// 获取资源字节通道,用作读取
ReadableByteChannel readableChannel() throws IOException;
// 内容长度
long contentLength() throws IOException;
// 最后一次修改时间的时间戳
long lastModified() throws IOException;
// 创建一个相对资源
Resource createRelative(String relativePath) throws IOException;
// 获取资源名
String getFilename();
// 获取资源描述
String getDescription();
}
实际上我们在使用Spring框架时,很多方法参数是String类型资源路径,这个资源路径就是通过 Resources来加载资源的。
Spring框架提供Resources接口就是为了标准化资源访问,对于特定的资源都有统一的前缀进行访问。
3.策略设计模式
策略(Strategy)设计模式,是23种设计模式(Gang of Four,GoF ,四人组)中一种。
策略设计模式有一系列相对独立的解决方案,用户根据不同情况进行选择,这种情况就可以使用策略模 式。再具体点说就是有一个公共接口,再有一些列实现类,这些类封装了不同算法,根据不同的场景, 选择这些不同的算法。
具体举例说明:编程中不同学科有着不同的学习路线,当我们选择学习Java时,应该按照以java学习路 线学习。学习前端时,按照前端的学习路径学习。我们就以这个现实场景来讲解策略模式。 如果不使用策略模式,也可以写出来代码,这个时候应该用if...else if ...else 结构
String study="java";
if(study.equals("java")){
System.out.println("java应该先学。。。");
}else if(study.equals("frontend")){
System.out.println("前端应该先学。。。");
}
这段代码是不复习面向对象设计原则-开闭原则(OCP,对修改关闭,对扩展开放),当我们还有其他学科的 学习路线时,需要修改代码,继续添加else if。在这种场景下就可以使用策略模式。 我们先来学习下策略模式包含哪些内容。
在策略设计模式中,一般包含三个角色和一个使用者:
策略模式代码实现。
// 策略抽象,定义所有学科学习方法
public interface ITStrategy {
void study();
}
// 具体策略:Java学习方法
public class JavaStrategy implements ITStrategy{
@Override
public void study() {
System.out.println("Java应该先学。。。");
}
}
// 具体策略:前端学习方法
public class FrontendStrategy implements ITStrategy{
@Override
public void study() {
System.out.println("前端应该先学。。。");
}
}
// 环境:关联策略抽象
public class ITContext {
private ITStrategy itStrategy;
public ITContext() {}
// 有参构造,传入具体策略
public ITContext(ITStrategy itStrategy) {
this.itStrategy = itStrategy;
}
// 对外提供方法,执行策略
public void study(){
itStrategy.study();
}
}
// 客户:使用环境
public class Test {
public static void main(String[] args) {
ITContext itContext = new ITContext(new JavaStrategy());
itContext.study();
ITContext itContext2 = new ITContext(new FrontendStrategy());
itContext2.study();
}
}
通过上面的代码可以看出来,我们后面添加Python的学习方法对代码没有影响,和Java、前端的学习方 法都是相互独立的。
所以我们来总结下,策略模式的优缺点:
优点:
1、不需要使用多重if。
2、高内聚,低耦合,符合OCP原则,便于扩展。
3、具体策略相互独立,客户可以根据不同情况选择不同策略,互不影响。
缺点:
1、客户必须知道所有具体策略,才能决定使用哪个策略。
2、当策略比较多时,具体策略会很多。
4.Resources的具体实现
Spring框架的Resources就是基于策略模式实现的。我们根据策略模式总结出Spring框架需要知道的类,把这些类都讲完了,Spring框架Resources基本就完事了。同时也知道了Spring框架Resources如何 对策略模式的应用。
Resources接口有很多子接口和具体实现类。整个Resources使用了设计模式中策略模式。
Spring框架在实现策略模式时肯定要比我们写的要稍微复杂一些,通过整理后的图形也可以看懂的。
4.1 UrlResource
UrlResource是java.net.URL在Spring框架中的实现。可以通过URL来访问任何资源。每周资源都有固定 的前缀表示:
1、file: 访问文件系统的资源文件, file:///C:/user/smallming/a.txt ,磁盘名开头为绝对路 径,没有以磁盘名开头为当前应用根目录(无论是否以斜杠开头)
2、https: 通过https(超文本安全传输协议)协议访问资源 https://www.baidu.com
3、ftp: 通过ftp(文件传输协议)协议访问资源 ftp://ip:port
这个类属于万能类,当我们在使用Spring框架访问资源时,尤其是在使用String类型参数时,由Spring 框架进行判断到底使用哪个Resources的实现类。如果发现路径没有标准前缀,则使用UrlResource来访问这个资源
4.2 ClassPathResource
ClassPathResource主要用于操作类路径资源。但是无法操作jar包中类路径资源。
在Maven项目中类路径资源:
1、src/main/java 中文件最终会被编译到类路径中
2、src/main/resources中文件最终会被编译到类路径中
在IDEA中看到的target/classes中内容就是类路径资源
当我们想要操作类路径资源时,可以直接使用ClassPathResource的有参构造方法
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
如果没有使用有参构造方法,属于隐士访问资源,需要在String类型中明确添加 classpath: 作为前缀
4.3 FileSystemResource
FileSystemResource 是 java.io.File 和 java.nio.File.Path 的具体实现。 主要用于获取文件系统资源,返回File或URL类型。同时因为实现了WritableResource接口,不仅仅支持读操作还支持写操作。
文件系统,简单理解为服务器内部文件,例如:
C:\\Users\\smallming\\a.txt 或 file:///C:/Users/smallming/a.txt
从Spring Framework 5.0 开始FileSystemResource 使用NIO.2 进行操作文件。从Spring Framework 5.1 开始支持NIO.2的Path进行构建,虽然支持Path构建但是使用的是Files进行文件操作。
小复习:
NIO.2 是Java 7开始支持一种NIO升级版。用于替代原来java.io.File进行文件操作,来提升性能和 增加操作的简便性。里面常用的就是Path、Paths、Files
使用File获取文件对象: File file = new File("C:\\Users\\smallming","a.txt");
使用NIO.2获取文件:Path path = Paths.get("C:\Users\smallming","a.txt");
4.4 PathResource
PathResource是一个纯NIO.2中Path的支持,专门是用于Path的。而上面学习的FileSystemResource 是从5.1版本开始支持的,并且FileSystemResource实际还是使用Files进行文件操作。 所有PathResource是一个纯粹的NIO.2中Path实现。用于访问文件系统资源,并返回File或URL。
4.5 ServletContextResource
ServletContextResource 是对jakarta.servlet.ServletContext资源访问的实现。 通过相对路径访问web项目根目录中资源。。 在项目中没有导入Spring框架web模块(spring-web)时,是无法搜索到这个类的。
4.6 InputStreamResource
InputStreamResource是对Java中java.io.InputStream的实现,只有其他Resource实现都没有办法使用 时,才可能会用InputStreamResource。
InputStreamResource访问的资源流一直处于open状态,所以当希望保存资源或者多次读取资源时不要使用。
所以InputStreamResource有一定局限性,主要用于一次性资源读取。
4.7 ByteArrayResource
ByteArrayResource主要用于把byte[]转换为Resource。
5. ResourceLoader
ResourceLoader 接口是Spring框架提供的资源加载器。通过这个接口加载资源,根据加载方式,获取 到Resources接口的哪个实现类。在ApplicationContext接口的实现类中都实现类ResourceLoader,所 以ApplicationContext不同实现类对应使用不同的Resources实现类。 ResourceLoader接口的内容也比较简单。
public interface ResourceLoader {
// 常量值为 classpath:
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 加载资源,返回资源对象。不同的加载方式返回不同的资源对象
Resource getResource(String location);
// 获取类加载器
@Nullable
ClassLoader getClassLoader();
}
特点如下:
1、当使用特定前缀时,则使用前缀对应的Resources接口实现类。和ApplicationContext接口实现类无关。前缀优先级高于ApplicationContext实现类
2、当使用特定类时,不添加前缀,会对应一种Resources实现类。
通过上面表格也说明了,我们之前一直使用的ClassPathXmlApplicationContext使用的是 ClassPathResource进行资源加载。 所以下面几种写法是等效的,都是使用ClassPathResource进行资源加载。
没有前缀
ApplicationContext ac = new ClassPathXmlApplicationContext("ac.xml");
有前缀
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:ac.xml");
使用ApplicationContext其他实现类,但是路径以classpath:前缀开头
ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:ac.xml");
System.out.println(ac.getResource("classpath:ac.xml").getClass().getName());
6.ResourcePatternResolver
ResourcepatternResovler 是 ResourceLoader接口的子接口。
public interface ResourcePatternResolver extends ResourceLoader
所以ResourcePatternResolver也是资源加载器。但是相对于ResourceLoader支持资源加载时,支持 Ant表达式,路径中包含占位符 * 号或 ** 号或 ? 号。
注意:当使用 * 或 ? 时,必须有前缀classpath*:,使用classpath:无效。但是 ** 没有强制要求必须使 用 classpath* ,但是必须以 / 或 classpath 或 classpath* 开头
强调: ApplicationContext接口实现的不是ResourceLoader,而是ResourcePatternResovler,所以 ApplicationContext所有实现类,在加载资源时都支持Ant表达式。
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory, HierarchicalBeanFactory,MessageSource ,
ApplicationEventPublisher, ResourcePatternResolver
7.ResourceLoaderAware
在Spring Framework 3.1 开始提供了一个Aware接口。这个接口是允许Bean获取到IoC容器部分参数或 功能的。这在一定程度上提升了Bean的能力。
Aware接口是一个顶级接口,里面没有任何方法。
public interface Aware {
}
想要让Bean具有哪种功能,需要使用Aware不同的子接口或实现类。直接子接口一共有15个,具体实现 类特别多。
如果只是用Spring框架完成CRUD,Aware这个接口用不上的。但是如果在做一些基于Spring框架的底层 开发时,Aware接口是离不开的。所以Aware接口一定要记住,这是Spring框架的一个隐藏大佬。
ResourceLoaderAware就是Aware接口的子接口。主要作用是让Bean可以获取到ResourceLoader对象。通过ResourceLoader进行加载资源。功能比较简单。 我们写一个小案例,在People类中借助ResourceLoaderAware把外部的一个属性文件加载到属性中。
新建配置文件applicationContext-resources.xml,启动注解扫描
新建properties文件
在src/main/resources中新建resources.properties文件,并在文件中随意添加几组键值对
name=smallming
age=16
新建Bean类com.bjsxt.resource.People
类中包含一个Properties类型属性,这个属性不在容器内部进行加载,而是通过代码,在People类中加 载外部属性文件。 所以让类实现ResourceLoaderAware接口。并重写setResourceLoader方法。方法参数 ResourceLoader由IoC容器帮助注入进来。
@Data
@Component
public class People implements ResourceLoaderAware {
private Properties prop;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
try {
Resource resource = resourceLoader.getResource("resources.properties");
InputStream is = resource.getInputStream();
prop = new Properties();// 一定要实例化,否则空指针
prop.load(is);// 把读取到的输入流加载到prop中。
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
编写测试类
测试查看,Bean中prop属性是否已经有值
@SpringJUnitConfig
@ContextConfiguration("classpath:applicationContext-resource.xml")
public class ResourceTest {
@Autowired
People people;
@Test
void test(){
System.out.println(people.getProp());
}
}