文章目錄
夯實Spring系列|第十六章:Spring 資源管理
本章說明
1.Spring 爲何要重複發明輪子,又如何一步步的趕超 Java 資源管理
2.系統性介紹 Spring 資源管理相關的接口和實現,如何使用
3.如何擴展 Java 標準資源管理
1.項目環境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模塊:resource
2.引入動機
爲什麼 Spring 不使用 Java 標準資源管理,而選擇重新發明輪子?
- Java 標準資源管理強大,而然擴展複雜,資源存儲方式並不統一
- Spring 要自立門戶
- Spring 抄、超、潮
- 借鑑 Java 以及 J2EE 的設計
- 超越 Java 的實現
- 跟隨技術發展的潮流
3.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
3.1 基於 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 |
3.2 基於 java.net.URLStreamHandler 擴展協議
實現類名必須爲 Handler
實現類命名規則 | 說明 |
---|---|
默認 | sun.net.www.protocol.${protocol}Handler |
自定義 | 通過 Java Properties java.protocol.handler.pkgs 指定實現類包名,實現類必須爲 Handler 。如果存在多包名指定,通過分隔符 | |
比如在 SpringBoot 中,Fat Jar 技術編譯之後生成的 jar 包,就是對 jar 協議進行了擴展,覆蓋了 jdk 中的默認實現。
雖然是 jdk 源碼,但是因爲包名是 sun 開頭而並非 java 開頭,所以可以進行覆蓋或者擴展。
4.Spring 資源接口
資源接口
類型 | 接口 |
---|---|
輸入流 | org.springframework.core.io.InputStreamResource |
只讀資源 | org.springframework.core.io.Resource |
可寫資源 | org.springframework.core.io.WritableResource |
編碼資源 | org.springframework.core.io.support.EncodedResource |
上下文資源 | org.springframework.core.io.ContextResource |
5.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 通過接口層面的抽象和內建的實現將 數組、類路徑、文件系統、URL 等多種資源類型進行了統一,方便我們開發人員的使用。
6.Spring Resource 接口擴展
可寫資源接口
- org.springframework.core.io.WritableResource
- org.springframework.core.io.FileSystemResource
- org.springframework.core.io.FileUrlResource
- org.springframework.core.io.PathResource
- 編碼資源接口
- org.springframework.core.io.support.EncodedResource
6.1 EncodedResource 示例
讀取自身 Java 文本信息
/**
* 帶有字符編碼 {@link FileSystemResource} 示例
*
* @see FileSystemResource
* @see EncodedResource
*/
public class EncodedFileSystemResourceDemo {
public static void main(String[] args) throws IOException {
String projectPath = "\\resource\\src\\main\\java\\com\\huajie\\thinking\\in\\spring\\resource";
String currentJavaFilePath = System.getProperty("user.dir") + projectPath + "\\EncodedFileSystemResourceDemo.java";
System.out.println(currentJavaFilePath);
File currentJavaFile = new File(currentJavaFilePath);
// FileSystemResource -> WritableResource -> Resource
FileSystemResource fileSystemResource = new FileSystemResource(currentJavaFile);
EncodedResource encodedResource = new EncodedResource(fileSystemResource, "UTF-8");
// 自動關閉資源
try (Reader reader = encodedResource.getReader()) {
String s = IOUtils.toString(reader);
System.out.println(s);
}
}
}
執行結果:
7.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
7.1 FileSystemResourceLoader 示例
/**
* 帶有字符編碼 {@link FileSystemResourceLoader} 示例
*
* @see FileSystemResourceLoader
* @see FileSystemResource
* @see EncodedResource
*/
public class EncodedFileSystemResourceLoaderDemo {
public static void main(String[] args) throws IOException {
String projectPath = "\\resource\\src\\main\\java\\com\\huajie\\thinking\\in\\spring\\resource";
String currentJavaFilePath = System.getProperty("user.dir") + projectPath + "\\EncodedFileSystemResourceLoaderDemo.java";
System.out.println(currentJavaFilePath);
File currentJavaFile = new File(currentJavaFilePath);
FileSystemResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource resource = resourceLoader.getResource(currentJavaFilePath);
EncodedResource encodedResource = new EncodedResource(resource,"UTF-8");
try (Reader reader = encodedResource.getReader()) {
String s = IOUtils.toString(reader);
System.out.println(s);
}
}
}
執行結果:
8.Spring 通配路徑資源加載器
通配路徑 ResourceLoader
- org.springframework.core.io.support.ResourcePatternResolver
- org.springframework.core.io.support.PathMatchingResourcePatternResolver
路徑匹配器
- org.springframework.util.PathMatcher
- org.springframework.util.AntPathMatcher
9.Spring 通配路徑資源擴展
實現 org.springframework.util.PathMatcher
重置 PathMatcher
- PathMatchingResourcePatternResolver#setPathMatcher
9.1 示例
重構 Resource 工具類
/**
* {@link Resource} 工具類
*/
public interface ResourceUtils {
static String getContent(Resource resource, String encoding) {
EncodedResource encodedResource = new EncodedResource(resource, encoding);
// 自動關閉資源
try (Reader reader = encodedResource.getReader()) {
return IOUtils.toString(reader);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
static String getContent(Resource resource) {
return getContent(resource, "UTF-8");
}
}
路徑資源加載器示例
/**
* 通配路徑資源加載器 {@link ResourcePatternResolver} 示例
*
* @see PathMatchingResourcePatternResolver
* @see ResourcePatternResolver
* @see AntPathMatcher
*/
public class CustomizedResourcePatternResolverDemo {
public static void main(String[] args) throws IOException {
// 讀取當前 package 對應的所有的 .java 文件 *.java
String projectPath = "/resource/src/main/java/com/huajie/thinking/in/spring/resource/";
String currentPackagePath = System.getProperty("user.dir") + projectPath;
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;
}
}
}
斷點調試,可以看到獲取到了我們目錄下面的所有 java 文件。
10.依賴注入 Spring Resource
基於 @Value 實現
@Value("classpath:/...")
private Resource resource;
10.1 示例
/**
* 注入 {@link Resource} 示例
*
* @see Resource
* @see Value
*/
public class InjectionResourceDemo {
@Value("classpath:/META-INF/default.properties")
private Resource resource;
@Value("classpath:/META-INF/*.properties")
private Collection<Resource> resources;
@Value("classpath*:/META-INF/*.properties")
private Resource[] resourcesArray;
@Value("${user.dir}")
private String currentPath;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectionResourceDemo.class);
applicationContext.refresh();
InjectionResourceDemo bean = applicationContext.getBean(InjectionResourceDemo.class);
String content = ResourceUtils.getContent(bean.resource);
System.out.println(content);
System.out.println("================");
System.out.println(bean.resources);
bean.resources.forEach(resource -> System.out.println("Collection:"+ResourceUtils.getContent(resource)));
System.out.println("================");
Stream.of(bean.resourcesArray).forEach(resource -> System.out.println("Resource[]:"+ResourceUtils.getContent(resource)));
applicationContext.close();
}
}
執行結果:
text=hello,world
================
[class path resource [META-INF/*.properties]]
Collection:null
================
Resource[]:text=hello,world
Resource[]:name=xwf
java.io.FileNotFoundException: class path resource [META-INF/*.properties] cannot be opened because it does not exist
...
由結果可知
1.@Value 注入單個 Resource 對象正常
2.通過 Collection 的方式,雖然也注入成功,但是不支持 *
這種通配符的用法
3.Resource[] 的方式支持 *
統配符
4.如果將 Collection 寫成完整路徑,執行正常
@Value("classpath:/META-INF/default.properties")
private Collection<Resource> resources;
11.依賴注入 ResourceLoader
方法一:實現 ResourceLoaderAware 回調
方法二:@Autowired 注入 ResourceLoader
方法三:注入 ApplicationContext 作爲 ResourceLoader
11.1 示例
/**
* 注入 {@link ResourceLoader} 示例
*
* @see ResourceLoader
* @see Resource
* @see Value
*/
public class InjectionResourceLoaderDemo implements ResourceLoaderAware {
private ResourceLoader awareResourceLoader;
@Autowired
private ResourceLoader autoWiredResourceLoader;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
System.out.println("awareResourceLoader==autoWiredResourceLoader : " + (awareResourceLoader == autoWiredResourceLoader));
System.out.println("applicationContext==autoWiredResourceLoader : " + (autoWiredResourceLoader == applicationContext));
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(InjectionResourceLoaderDemo.class);
applicationContext.refresh();
applicationContext.close();
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.awareResourceLoader = resourceLoader;
}
}
執行結果:
awareResourceLoader==autoWiredResourceLoader : true
applicationContext==autoWiredResourceLoader : true
有結果可知通過這三種方式獲取的 ResourceLoader 都是同一個對象,這個對象就是 ApplicationContext 當前的應用上下文。
源碼位置:
1.org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 664 行
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
2.org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces 113 行
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
12.面試題
12.1 Spring 配置資源中有哪些場景類型?
- XML 資源
- Properties 資源
- YAML 資源
12.2 請列舉不同類型 Spring 配置資源?
- XML 資源
- 普通 Bean Defintion XML 配置資源 - *.xml
- Spring Schema 資源 - *.xsd
- Properties 資源
- 普通 Properties 格式資源 - *.properties
- Spring Handler 實現類映射文件 - META-INF/spring.handlers
- Spring Schema 資源映射文件 - META-INF/spring.schemas
- YAML 資源
- 普通 YAML 配置資源 - *.yaml 或者 *.yml
12.3 Java 標準資源管理擴展的步驟?
簡易實現
- 實現 URLStreamHandler 並放置在 sun.net.www.protocol.${protocol}.Handler 包下
自定義實現
- 實現 URLStreamHandler
- 添加 - Djava.protocol.handler.pkgs 啓動參數,指向 URLStreamHandler 實現類的包下
高級實現
- 實現 URLStreamHandlerFactory 並傳遞到 URL 中
12.3.1 簡易實現
請注意必須按此包名 sun.net.www.protocol
,此爲擴展協議的規約
假設我們實現 X 協議
新建 Handler 實現 URLStreamHandler
- 包路徑:package sun.net.www.protocol.x;
/**
* X 協議 {@link URLStreamHandler 實現}
*/
public class Handler extends URLStreamHandler{
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new XURLConnection(u);
}
}
新建 XURLConnection 實現 URLConnection
/**
* X {@link URLConnection} 實現
*/
public class XURLConnection extends URLConnection {
private final ClassPathResource resource;
/**
* Constructs a URL connection to the specified URL. A connection to
* the object referenced by the URL is not created.
*
* @param url the specified URL.
*/
// URL = x://META-INF/default.properties
public XURLConnection(URL url) {
super(url);
this.resource = new ClassPathResource(url.getPath());
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
return resource.getInputStream();
}
}
測試
/**
* X Handler 測試示例
*/
public class HandlerTest {
public static void main(String[] args) throws IOException {
// 這裏的 x: 對應的是 sun.net.www.protocol.x 中的 x ; 表示 x協議
URL url = new URL("x:///META-INF/default.properties");
InputStream inputStream = url.openStream();
String s = IOUtils.toString(inputStream);
System.out.println(s);
}
}
執行結果:
text=hello,world
12.3.2 自定義實現
爲了方便我們直接繼承上面的 x 實現,假設我們這裏是 springx 協議
/**
* 簡單的繼承 {@link Handler}
*/
public class Handler extends sun.net.www.protocol.x.Handler {
// -Djava.protocol.handler.pkgs=com.huajie.thinking.in.spring.resource
public static void main(String[] args) throws Exception {
URL url = new URL("springx:///META-INF/default.properties");
InputStream inputStream = url.openStream();
String s = IOUtils.toString(inputStream);
System.out.println(s);
}
}
啓動的時候設置 -Djava.protocol.handler.pkgs=com.huajie.thinking.in.spring.resource 參數
執行結果:
text=hello,world
12.3.3 高級實現
代碼不變但是包路徑在 package sun.net.www.protocol.f;
public class Handler extends URLStreamHandler {
@Override
public URLConnection openConnection(URL u) throws IOException {
return new XURLConnection(u);
}
}
HandlerFactory 實現 URLStreamHandlerFactory
/**
* F HandlerFactory 測試示例
*/
public class HandlerFactory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";
public URLStreamHandler createURLStreamHandler(String protocol) {
String className = PREFIX + "." + protocol + ".Handler";
try {
Class clazz = Class.forName(className);
return (URLStreamHandler) clazz.newInstance();
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
return null;
}
}
測試
/**
* F Handler 測試示例
*/
public class HandlerTest {
public static void main(String[] args) throws IOException {
// 這裏的 x: 對應的是 sun.net.www.protocol.x 中的 x ; 表示 x協議
URL url = new URL("f:///META-INF/default.properties");
Handler handler = (Handler) new HandlerFactory().createURLStreamHandler("f");
URLConnection urlConnection = handler.openConnection(url);
InputStream inputStream = urlConnection.getInputStream();
String s = IOUtils.toString(inputStream);
System.out.println(s);
}
}
執行結果:
text=hello,world
13.參考
- 極客時間-小馬哥《小馬哥講Spring核心編程思想》