Spring-Core
资源管理
资源管理是Spring的一个核心的基础功能,不过在说Spring的资源管理之前,先来简单说一下Java中的资源管理。
Java资源管理
Java中的资源管理主要是通过java.net.URL来实现的,通过URL的openConnection方法可以对资源打开一个连接,通过这个连接读取资源的内容。
资源不仅仅指的是网络资源,还可以是本地文件、一个jar包等等。
1、来个Demo
举个例子,比如你想到访问www.baidu.com这个百度首页网络资源,那么此时就可以这么写
public class JavaResourceDemo {
public static void main(String[] args) throws IOException {
//构建URL 指定资源的协议为http协议
URL url = new URL("http://www.baidu.com");
//打开资源连接
URLConnection urlConnection = url.openConnection();
//获取资源输入流
InputStream inputStream = urlConnection.getInputStream();
//通过hutool工具类读取流中数据
String content = IoUtil.read(new InputStreamReader(inputStream));
System.out.println(content);
}
}
解释一下上面代码的意思:
首先构建一个URL,指定资源的访问协议为http协议 通过URL打开一个资源访问连接,然后获取一个输入流,读取内容 运行结果
图片 成功读取到百度首页的数据。
当然,也可以通过URL访问本地文件资源,在创建URL的时候只需要指定协议类型为file://和文件的路径就行了
URL url = new URL("file://" + "文件的路径");
这种方式这里我就不演示了。
其实这种方式实际上最终也是通过FileInputStream来读取文件数据的,不信你可以自己debug试试。
2、原理
每种协议的URL资源都需要一个对应的一个URLStreamHandler来处理。
图片
URLStreamHandler
比如说,http://协议有对应的URLStreamHandler的实现,file://协议的有对应的URLStreamHandler的实现。
Java除了支持http://和file://协议之外,还支持其它的协议,如下图所示:
- file
- ftp
- http
- https
- jar
- mailto
- netdoc
图片 对于的URLStreamHandler如下图所示
图片 当在构建URL的时候,会去解析资源的访问协议,根据访问协议找到对应的URLStreamHandler的实现。
当然,除了Java本身支持的协议之外,我们还可以自己去扩展这个协议,大致只需要两步即可:
- 实现URLConnection,可以通过这个连接读取资源的内容
- 实现URLStreamHandler,通过URLStreamHandler可以获取到URLConnection
不过需要注意的是,URLStreamHandler的实现需要放在sun.net.www.protocol.协议名称包下,类名必须是Handler,这也是为什么截图中的实现类名都叫Handler的原因。
当然如果不放在指定的包下也可以,但是需要实现java.net.URLStreamHandlerFactory接口。
对于扩展我就不演示了,如果你感兴趣可以自行谷歌一下。
Spring资源管理
虽然Java提供了标准的资源管理方式,但是Spring并没有用,而是自己搞了一套资源管理方式。
1、资源抽象
在Spring中,资源大致被抽象为两个接口
- Resource:可读资源,可以获取到资源的输入流
- WritableResource:读写资源,除了资源输入流之外,还可以获取到资源的输出流
Resource
图片 Resource接口继承了InputStreamSource接口,而InputStreamSource接口可以获取定义了获取输入流的方法
图片
WritableResource
图片 WritableResource继承了Resource接口,可以获取到资源的输出流,因为有的资源不仅可读,还可写,就比如一些本地文件的资源,往往都是可读可写的
Resource的实现很多,这里我举几个常见的:
- FileSystemResource:读取文件系统的资源
- UrlResource:前面提到的Java的标准资源管理的封装,底层就是通过URL来访问资源
- ClassPathResource:读取classpath路径下的资源
- ByteArrayResource:读取静态字节数组的数据
比如,想要通过Spring的资源管理方式来访问前面提到百度首页网络资源,就可以这么写
//构建资源
Resource resource = new UrlResource("http://www.baidu.com");
//获取资源输入流
InputStream inputStream = resource.getInputStream();
如果是一个本地文件资源,那么除了可以使用UrlResource,也可以使用FileSystemResource,都是可以的。
2、资源加载
虽然Resource有很多实现,但是在实际使用中,可能无法判断使用具体的哪个实现,所以Spring提供了ResourceLoader资源加载器来根据资源的类型来加载资源。
图片 ResourceLoader 通过getResource方法,传入一个路径就可以加载到对应的资源,而这个路径不一定是本地文件,可以是任何可加载的路径。
ResourceLoader有个唯一的实现DefaultResourceLoader
图片 比如对于上面的例子,就可以通过ResourceLoader来加载资源,而不用直接new具体的实现了
//创建ResourceLoader
ResourceLoader resourceLoader = new DefaultResourceLoader();
//获取资源
Resource resource = resourceLoader.getResource("http://www.baidu.com");
除了ResourceLoader之外,还有一个ResourcePatternResolver可以加载资源
图片 ResourcePatternResolver继承了ResourceLoader
通过ResourcePatternResolver提供的方法可以看出,他可以加载多个资源,支持使用通配符的方式,比如classpath*:,就可以加载所有classpath的资源。
ResourcePatternResolver只有一个实现PathMatchingResourcePatternResolver
图片 PathMatchingResourcePatternResolver
3、小结
到这就讲完了Spring的资源管理,这里总结一下本节大致的内容
Java的标准资源管理:
- URL
- URLStreamHandler
Spring的资源管理:
- 资源抽象:Resource 、WritableResource
- 资源加载:ResourceLoader 、ResourcePatternResolver
Spring的资源管理在Spring中用的很多,比如在SpringBoot中,application.yml的文件就是通过ResourceLoader加载成Resource,之后再读取文件的内容的。
图片
环 境
上一节末尾举的例子中提到,SpringBoot配置文件是通过ResourceLoader来加载配置文件,读取文件的配置内容
那么当配置文件都加载完成之后,这个配置应该存到哪里,怎么能够读到呢?
这就引出了Spring框架中的一个关键概念,环境,它其实就是用于管理应用程序配置的。
1、Environment
Environment就是环境抽象出来的接口
图片 Environment继承PropertyResolver
public interface PropertyResolver {
boolean containsProperty(String key);
String getProperty(String key);
<T> T getProperty(String key, Class<T> targetType);
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
String resolvePlaceholders(String text);
}
如上是PropertyResolver提供的部分方法,这里简单说一下上面方法的作用
getProperty(String key),很明显是通过配置的key获取对应的value值getProperty(String key, Class<T> targetType),这是获取配置,并转换成对应的类型,比如你获取的是个字符串的"true",这里就可以给你转换成布尔值的true,具体的底层实现留到下一节讲resolvePlaceholders(String text),这类方法可以处理${...}占位符,也就是先取出${...}占位符中的key,然后再通过key获取到值
所以Environment主要有一下几种功能:
- 根据key获取配置
- 获取到指定类型的配置
- 处理占位符 来个demo
先在application.yml的配置文件中加入配置
图片 测试代码如下
@SpringBootApplication
public class EnvironmentDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(EnvironmentDemo.class, args);
//从ApplicationContext中获取到ConfigurableEnvironment
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//获取name属性对应的值
String name = environment.getProperty("name");
System.out.println("name = " + name);
}
}
启动应用,获取到ConfigurableEnvironment对象,再获取到值
ConfigurableEnvironment是Environment子接口,通过命名也可以知道,他可以对Environment进行一些功能的配置。
运行结果:
name = 三友的java日记
2、配置属性源PropertySource
PropertySource是真正存配置的地方,属于配置的来源,它提供了一个统一的访问接口,使得应用程序可以以统一的方式获取配置获取到属性。
图片 PropertySource 来个简单demo
public class PropertySourceDemo {
public static void main(String[] args) {
Map<String, Object> source = new HashMap<>();
source.put("name", "三友的java日记");
PropertySource<Map<String, Object>> propertySource = new MapPropertySource("myPropertySource", source);
Object name = propertySource.getProperty("name");
System.out.println("name = " + name);
}
}
简单说一下上面代码的意思
- 首先创建了一个map,就是配置来源,往里面添加了一个配置key-value
- 创建了一个PropertySource,使用的实现是MapPropertySource,需要传入配置map,所以最终获取到属性不用想就知道是从map中获取的
- 最后成获取到属性
图片 除了MapPropertySource之外,还有非常多的实现
图片 PropertySource实现 比如CommandLinePropertySource,它其实就封装了通过命令启动时的传递的配置参数
既然PropertySource才是真正存储配置的地方,那么Environment获取到的配置真正也就是从PropertySource获取的,并且他们其实是一对多的关系
图片 其实很好理解一对多的关系,因为一个应用程序的配置可能来源很多地方,比如在SpringBoot环境底下,除了我们自定义的配置外,还有比如系统环境配置等等,这些都可以通过Environment获取到
当从Environment中获取配置的时候,会去遍历所有的PropertySource,一旦找到配置key对应的值,就会返回
所以,如果有多个PropertySource都含有同一个配置项的话,也就是配置key相同,那么获取到的配置是从排在前面的PropertySource的获取的
这就是为什么,当你在配置文件配置username属性时获取到的却是系统变量username对应的值,因为系统的PropertySource排在配置文件对应的PropertySource之前
3、SpringBoot是如何解析配置文件
SpringBoot是通过PropertySourceLoader来解析配置文件的
图片 load方法的第二个参数就是我们前面提到的资源接口Resource
通过Resource就可以获取到配置文件的输入流,之后就可以读取到配置文件的内容,再把配置文件解析成多个PropertySource,之后把PropertySource放入到Environment中,这样我们就可以通过Environment获取到配置文件的内容了。
PropertySourceLoader默认有两个实现,分别用来解析properties和yml格式的配置文件
图片 此时,上面的图就可以优化成这样
图片
类型转换
在上一节介绍Environment时提到了它的getProperty(String key, Class<T> targetType)可以将配置的字符串转换成对应的类型,那么他是如何转换的呢?
这就跟本文要讲的Spring类型转换机制有关了
1、类型转换API
Spring类型转换主要涉及到以下几个api:
- PropertyEditor
- Converter
- GenericConverter
- ConversionService
- TypeConverter
接下来我会来详细介绍这几个api的原理和他们之间的关系。
1.1、PropertyEditor
PropertyEditor并不是Spring提供的api,而是JDK提供的api,他的主要作用其实就是将String类型的字符串转换成Java对象属性值。
public interface PropertyEditor {
void setValue(Object value);
Object getValue();
String getAsText();
void setAsText(String text) throws java.lang.IllegalArgumentException;
}
就拿项目中常用的@Value来举例子,当我们通过@Value注解的方式将配置注入到字段时,大致步骤如下图所示: