夯實Spring系列|第十六章:Spring 資源管理

夯實Spring系列|第十六章:Spring 資源管理

本章說明

1.Spring 爲何要重複發明輪子,又如何一步步的趕超 Java 資源管理
2.系統性介紹 Spring 資源管理相關的接口和實現,如何使用
3.如何擴展 Java 標準資源管理

1.項目環境

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);
        }
    }
}

執行結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4PnSnUry-1590629264464)(G:\workspace\learn-document\spring-framework\csdn\image-20200527214111757.png)]

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

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);
        }
    }
}

執行結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MRBtMmdg-1590629264466)(G:\workspace\learn-document\spring-framework\csdn\image-20200527214746722.png)]

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 文件。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QPRdRA3W-1590629264467)(G:\workspace\learn-document\spring-framework\csdn\image-20200527220441084.png)]

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,此爲擴展協議的規約
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WtReYbZm-1590629264469)(G:\workspace\csdn\learn-document\spring-framework\csdn\image-20200528091207628.png)]

假設我們實現 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 參數
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nNbkpX9X-1590629264470)(G:\workspace\csdn\learn-document\spring-framework\csdn\image-20200528092610256.png)]
執行結果:

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核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章