11. 资源管理 Resources
Resource 都包括哪些东西 ?
基本上可以理解为项目中 resources 目录下的所有内容
- Spring 配置 xml 文件
- 外部化配置,properties 文件或 yaml 文件
- Class 文件,被 @Component 标记的 Class 文件 ?算吗
11.1 引入动机
为什么 Spring 不使用 Java 标准资源管理,而选择重新发明轮子?
- Java 标准资源管理虽然强大,但是扩展复杂,资源存储的方式并不统一
- Spring 要自立门户
- Spring 三个原则,抄,超,潮;抄 java,也要超越 java,还要使用新潮的技术和思想
11.2 Java 标准资源管理
11.3 Spring 资源接口
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;
}
11.4 Spring 内建 Resource 实现
Spring 内建 Resource 实现,这些类都是 Resource 接口的实现类:
11.5 Spring 资源加载器
- 使用 FileSystemResource 读取当前 class 文件内容,代码如下所示:
/**
* 读取当前class文件内容
*
* @author mao 2021/5/21 1:49
*/
public class EncodeFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String path = System.getProperty("user.dir")
+ "\\thinking-in-spring\\resource\\src\\main\\java\\org\\geekbang\\resource\\EncodeFileSystemResourceDemo.java";
System.out.println(path);
FileSystemResource fileSystemResource = new FileSystemResource(path);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
Reader reader = encodedResource.getReader();
System.out.println(IOUtils.toString(reader));
}
}
输出结果,输出当前 class 文件的路径,输出 class 文件的内容
D:\MyProjects\idea\think-in-spring\thinking-in-spring\resource\src\main\java\org\geekbang\resource\EncodeFileSystemResourceDemo.java
package org.geekbang.resource;
import com.sun.javafx.runtime.SystemProperties;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
import java.io.Reader;
/**
* 读取当前class文件内容
*
* @author mao 2021/5/21 1:49
*/
public class EncodeFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String path = System.getProperty("user.dir")
+ "\\thinking-in-spring\\resource\\src\\main\\java\\org\\geekbang\\resource\\EncodeFileSystemResourceDemo.java";
System.out.println(path);
FileSystemResource fileSystemResource = new FileSystemResource(path);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
}
- 使用 FileSystemResourceLoader 读取当前 class 文件内容,代码如下所示:
public static void main(String[] args) throws IOException {
String path = System.getProperty("user.dir")
+ "\\thinking-in-spring\\resource\\src\\main\\java\\org\\geekbang\\resource\\EncodeFileSystemResourceDemo.java";
System.out.println(path);
FileSystemResourceLoader resourceLoader = new FileSystemResourceLoader();
// 本质是创建了一个 ClassPathContextResource
Resource resource = resourceLoader.getResource(path);
EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
输出结果
D:\MyProjects\idea\think-in-spring\thinking-in-spring\resource\src\main\java\org\geekbang\resource\EncodeFileSystemResourceDemo.java
package org.geekbang.resource;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
import java.io.Reader;
/**
* 读取当前class文件内容
* {@link FileSystemResource} 示例
* @author mao 2021/5/21 1:49
*/
public class EncodeFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String path = System.getProperty("user.dir")
+ "\\thinking-in-spring\\resource\\src\\main\\java\\org\\geekbang\\resource\\EncodeFileSystemResourceDemo.java";
System.out.println(path);
FileSystemResource fileSystemResource = new FileSystemResource(path);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
Reader reader = encodedResource.getReader();
System.out.println(IOUtils.toString(reader));
}
}
11.6 Spring 通配路径资源加载器
- 通配路径解析
- 接口 - ResourcePatternResolver
- 实现类 - PathMatchingResourcePatternResolver
- 路径匹配
- 接口 -
- 实现 - AntPathMatcher
源码如下
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
private final ResourceLoader resourceLoader;
private PathMatcher pathMatcher = new AntPathMatcher();
// 底层还是ResourceLoader来获取资源
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 资源路径是 classpath*: 开头的
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 如果路径匹配,则去查找资源
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}
else {
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
}
11.7 Spring 通配路径资源扩展
11.8 依赖注入 Resource
- 基于 @Value 注入 -
@Value("classpath:/...") - 不能通过 @Autowired 注入,Spring 内建单例对象并没有 Resource,不要记混了
public class InjectingResourceDemo {
@Value("classpath:/hello.properties")
private Resource helloResource;
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectingResourceDemo.class);
applicationContext.refresh();
InjectingResourceDemo bean = applicationContext.getBean(InjectingResourceDemo.class);
System.out.println(bean.helloResource);
EncodedResource encodedResource = new EncodedResource(bean.helloResource);
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
}
输出结果
class path resource [hello.properties]
name=tracccer
11.9 依赖注入 ResourceLoader
- @Autowired 注入 ResourceLoader
- 注入 ApplicationContext 作为 ResourceLoader
- 实现 ResourceLoaderAware 回调方法
- 下面的代码演示了上述三种方式,依赖注入 ResourceLoader
public class InjectingResourceLoaderDemo implements ResourceLoaderAware {
public static final String LOCATION = "classpath:/hello.properties";
// 1.@Autowired 注入 ResourceLoader
@Autowired
private ResourceLoader resourceLoader;
// 2.注入 ApplicationContext 作为 ResourceLoader
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectingResourceLoaderDemo.class);
applicationContext.refresh();
InjectingResourceLoaderDemo bean = applicationContext.getBean(InjectingResourceLoaderDemo.class);
System.out.println(bean.resourceLoader);
// 获取@Autowired注入的resourceLoader
Resource resource = bean.resourceLoader.getResource(LOCATION);
getContent(resource);
// 通过@Autowired注入的ApplicationContext,获取resourceLoader
Resource resource1 = bean.resourceLoader.getResource(LOCATION);
getContent(resource1);
// 二者其实是同一个对象
System.out.println(bean.resourceLoader == bean.applicationContext);
}
// 3. 实现ResourceLoaderAware回调方法, 加载resource, 打印内容
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
Resource resource1 = resourceLoader.getResource(LOCATION);
try {
getContent(resource1);
} catch (IOException e) {
e.printStackTrace();
}
}
// 打印资源的内容
private static void getContent(Resource resource) throws IOException {
EncodedResource encodedResource = new EncodedResource(resource);
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
}
输出结果
name=tracccer
org.springframework.context.annotation.AnnotationConfigApplicationContext@1a6c5a9e, started on Fri May 21 03:04:55 CST 2021
name=tracccer
name=tracccer
true
- Spring 框架在应用上下文启动时
refresh()会调用下面这个方法,注册 4 个非Spring容器管理的对象到容器,其中就包括我们上面使用的 ResourceLoader 和 ApplicationContext,源码如下:
AbstractApplicationContext.java
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 注册ApplicationContextAwareProcessor,用于在初始化前回调各个Aware
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 注册非容器管理对象ResolvableDependency, this是当前应用上下文
// 可以看到后3个其实是同一个对象this
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
}
DefaultListableBeanFactory.java
// 注册ResolvableDependency, 将其保存到resolvableDependencies中
public void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue) {
Assert.notNull(dependencyType, "Dependency type must not be null");
if (autowiredValue != null) {
this.resolvableDependencies.put(dependencyType, autowiredValue);
}
}
至于依赖查找是如果找到 ResourceLoader 对象的,见 7.2.3 章节 依赖注入的来源
- ApplicationContextAwareProcessor 实现了 BeanPostProcessor#postProcessBeforeInitialization 方法,该方法会在 bean 初始化前被回调,该方法会对调用所有 Aware 接口的实现类,也就包括我们前面示例代码中实现的 ResourceLoaderAware 接口。
ApplicationContextAwareProcessor.java
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){
return bean;
}
// 调用Aware接口,见下个方法
invokeAwareInterfaces(bean);
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
// 回调ResourceLoaderAware的setResourceLoader方法
// 也就是我们重写的方法
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
}
这也恰好说明了,示例代码中,最先输出的是实现 ResourceLoaderAware 接口的方式依赖注入的 Resource,因为是在 bean 初始化前执行的。
11.10 面试题
-
Spring 配置资源中有哪些常见类型?
- xml 资源
- BeanDefinition 配置资源
- properties 资源
- 外部化配置 properties
- Spring Handler 实现类资源文件 - spring.handlers
- Spring Schema 资源映射文件 - spring.schemas
- yaml 资源
- 外部化配置 yaml
- xml 资源
-
Resource 与 ResourceLoader 的区别
-
依赖注入 Resource 和 ResourceLoader 有哪几种方式?