引入动机
• 为什么 Spring 不使用 Java 标准资源管理,而选择重新发明轮子?
• Java 标准资源管理强大,然而扩展复杂,资源存储方式并不统一
• Spring 要自立门户(重要的话,要讲三遍)
• Spring “抄”、“超” 和 “潮”
Java 标准资源管理
• 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 URL 协议扩展
- 基于 java.net.URLStreamHandlerFactory
- 基于 java.net.URLStreamHandler
• 基于 java.net.URLStreamHandlerFactory 扩展协议
• 基于 java.net.URLStreamHandler 扩展协议
- 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 |
• 基于 java.net.URLStreamHandler 扩展协议
- 实现类名必须为 “Handler”
实现类命名规则 | 说明 |
---|---|
默认 | sun.net.www.protocol.${protocol}.Handler |
自定义 | 通过 Java Properties java.protocol.handler.pkgs 指定实现类包名,实现类名必须为“Handler”。如果存在多包名指定,通过分隔符 “|” |
Spring 资源接口
• 资源接口
类型 | 接口 |
---|---|
输入流 | 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 |
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.ServletContextResource |
Spring Resource 接口扩展
• 可写资源接口
- 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
/**
* 带有字符编码的 {@link FileSystemResource} 示例
*/
public class EncodedFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String currentJavaFilePath = System.getProperty("user.dir") + "/thinking-in-spring/resource/src/main/java/org/geekbang/thinking/in/spring/resource/EncodedFileSystemResourceDemo.java";
File currentJavaFile = new File(currentJavaFilePath);
// FileSystemResource => WritableResource => Resource
FileSystemResource fileSystemResource = new FileSystemResource(currentJavaFilePath);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
// 字符输入流
// 字符输入流
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
}
Spring 资源加载器
• 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
- org.springframework.core.io.DefaultResourceLoader
/**
* 带有字符编码的 {@link FileSystemResourceLoader} 示例
*/
public class EncodedFileSystemResourceLoaderDemo {
public static void main(String[] args) throws IOException {
String currentJavaFilePath = "/" + System.getProperty("user.dir") + "/thinking-in-spring/resource/src/main/java/org/geekbang/thinking/in/spring/resource/EncodedFileSystemResourceLoaderDemo.java";
// 新建一个 FileSystemResourceLoader 对象
FileSystemResourceLoader resourceLoader = new FileSystemResourceLoader();
// FileSystemResource => WritableResource => Resource
Resource resource = resourceLoader.getResource(currentJavaFilePath);
EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
// 字符输入流
try (Reader reader = encodedResource.getReader()) {
System.out.println(IOUtils.toString(reader));
}
}
}
Spring 通配路径资源加载器
• 通配路径 ResourceLoader
- org.springframework.core.io.support.ResourcePatternResolver
- org.springframework.core.io.support.PathMatchingResourcePatternResolver
• 路径匹配器
- org.springframework.util.PathMatcher
- Ant 模式匹配实现 - org.springframework.util.AntPathMatcher
Spring 通配路径资源扩展
• 实现 org.springframework.util.PathMatcher
• 重置 PathMatcher
- PathMatchingResourcePatternResolver#setPathMatcher
/**
* 自定义 {@link ResourcePatternResolver} 示例
*/
public class CustomizedResourcePatternResolverDemo {
public static void main(String[] args) throws IOException {
// 读取当前 package 对应的所有的 .java 文件
// *.java
String currentPackagePath = "/" + System.getProperty("user.dir") + "/thinking-in-spring/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);
}
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;
}
}
}
依赖注入 Spring Resource
• 基于 @Value 实现
- 如:
@Value(“classpath:/…”)
private Resource resource;
@Value(“classpath*:/META-INF/*.properties”)
private Resource[] propertiesResources;
/**
* 注入 {@link Resource} 对象示例
*/
public class InjectingResourceDemo {
@Value("classpath:/META-INF/default.properties")
private Resource defaultPropertiesResource;
@Value("classpath*:/META-INF/*.properties")
private Resource[] propertiesResources;
@Value("${user.dir}")
private String currentProjectRootPath;
@PostConstruct
public void init() {
System.out.println(ResourceUtils.getContent(defaultPropertiesResource));
System.out.println("================");
Stream.of(propertiesResources).map(ResourceUtils::getContent).forEach(System.out::println);
System.out.println("================");
System.out.println(currentProjectRootPath);
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前类作为 Configuration Class
context.register(InjectingResourceDemo.class);
// 启动 Spring 应用上下文
context.refresh();
// 关闭 Spring 应用上下文
context.close();
}
}
依赖注入 ResourceLoader
• 方法一:实现 ResourceLoaderAware 回调
• 方法二:@Autowired 注入 ResourceLoader
• 方法三:注入 ApplicationContext 作为 ResourceLoader
/**
* 注入 {@link ResourceLoader} 对象示例
*/
public class InjectingResourceLoaderDemo implements ResourceLoaderAware {
private ResourceLoader resourceLoader; // 方法一
@Autowired
private ResourceLoader autowiredResourceLoader; // 方法二
@Autowired
private AbstractApplicationContext applicationContext; // 方法三
@PostConstruct
public void init() {
System.out.println("resourceLoader == autowiredResourceLoader : " + (resourceLoader == autowiredResourceLoader));
System.out.println("resourceLoader == applicationContext : " + (resourceLoader == applicationContext));
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前类作为 Configuration Class
context.register(InjectingResourceLoaderDemo.class);
// 启动 Spring 应用上下文
context.refresh();
// 关闭 Spring 应用上下文
context.close();
}
}
面试题
Spring 配置资源中有哪些常见类型?
答:
• XML 资源
• Properties 资源
• YAML 资源
请例举不同类型 Spring 配置资源?
答:
• XML 资源
- 普通 Bean Definition XML 配置资源 - *.xml
- Spring Schema 资源 - *.xsd
• Properties 资源
- 普通 Properties 格式资源 - *.properties
- Spring Handler 实现类映射文件 - META-INF/spring.handlers
- Spring Schema 资源映射文件 - META-INF/spring.schemas
• YAML 资源
- 普通 YAML 配置资源 - *.yaml 或 *.yml
Java 标准资源管理扩展的步骤?
答:
• 简易实现
- 实现 URLStreamHandler 并放置在 sun.net.www.protocol.${protocol}.Handler 包下
• 自定义实现
- 实现 URLStreamHandler
- 添加 -Djava.protocol.handler.pkgs 启动参数,指向 URLStreamHandler 实现类的包下
• 高级实现
- 实现 URLStreamHandlerFactory 并传递到 URL 之中