Spring资源管理,Spring资源管理源码分析

一、Java标准资源管理

1、Java 标准资源定位

职责说明
面向资源文件系统、artifactjar、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

2、Java URL 协议扩展

基于 java.net.URLStreamHandlerFactory

在这里插入图片描述
java.net.URL中定义了一个静态变量factory

static URLStreamHandlerFactory factory;

public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
    synchronized (streamHandlerLock) {
        if (factory != null) { // 只能设置一次
            throw new Error("factory already defined");
        }
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkSetFactory();
        }
        handlers.clear();
        factory = fac;
    }
}

基于 java.net.URLStreamHandler

基于 java.net.URLStreamHandler 扩展协议JDK 1.8 內建协议实现

协议实现类
filesun.net.www.protocol.file.Handler
ftpsun.net.www.protocol.ftp.Handler
httpsun.net.www.protocol.http.Handler
httpssun.net.www.protocol.https.Handler
jarsun.net.www.protocol.jar.Handler
mailtosun.net.www.protocol.mailto.Handler
netdocsun.net.www.protocol.netdoc.Handler

实现类名必须为 “Handler”

实现类命名规则说明
默认sun.net.www.protocol.${protocol}.Handler
自定义通过 Java Properties java.protocol.handler.pkgs 指定实现类包名实现类名必须为“Handler”。如果存在多包名指定通过分隔符 “|”

java.net.URL中定义protocolPathProp

private static final String protocolPathProp = "java.protocol.handler.pkgs";


static URLStreamHandler getURLStreamHandler(String protocol) {

     URLStreamHandler handler = handlers.get(protocol);
     if (handler == null) {

         boolean checkedWithFactory = false;

         // Use the factory (if any)
         if (factory != null) {
             handler = factory.createURLStreamHandler(protocol);
             checkedWithFactory = true;
         }

         // Try java protocol handler
         if (handler == null) { // 对protocolPathProp 的处理
             String packagePrefixList = null;

             packagePrefixList
                 = java.security.AccessController.doPrivileged(
                 new sun.security.action.GetPropertyAction(
                     protocolPathProp,""));
             if (packagePrefixList != "") {
                 packagePrefixList += "|";
             }

             // REMIND: decide whether to allow the "null" class prefix
             // or not.
             packagePrefixList += "sun.net.www.protocol";

             StringTokenizer packagePrefixIter =
                 new StringTokenizer(packagePrefixList, "|");

3、Java 标准资源管理扩展的步骤

1简易实现
实现 URLStreamHandler 并放置在 sun.net.www.protocol.${protocol}.Handler 包下

2自定义实现
• 实现 URLStreamHandler
• 添加 -Djava.protocol.handler.pkgs 启动参数指向 URLStreamHandler 实现类的包下

3高级实现
• 实现 URLStreamHandlerFactory 并传递到 URL 之中

4、Spring为什么不用Java标准的资源管理

Java 标准资源管理强大然而扩展复杂资源存储方式并不统一。

而使用Java开发的程序员基本也都离不开Spring作为Java届的龙头老大Spring的很多东西都要试图与Java比一比很多东西都完全独立于jdk自己重新弄了一套。

这个操作Spring似乎实现的很成功甚至隐隐超过jdk默认的很多实现有一种引领java开发的潮流的意思。

二、Spring资源接口与实现

1、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

InputStreamSource接口

InputStreamSource接口只有一个方法getInputStream获取输入流。

public interface InputStreamSource {

	InputStream getInputStream() throws IOException;
}

Resource接口

Resource只读资源继承了InputStreamSource接口也具有获取输入流功能。同时具有getURL、getURI、getFile等功能对资源只提供读取功能的接口。

WritableResource接口

WritableResource可写资源继承Resource有isWritable来判断资源是否可写同时可以getOutputStream获取输出流。

EncodedResource类

EncodedResource类用于编码继承InputStreamSource针对需要指定资源编码如UTF-8这种的资源。属性有Resource对象主要通过getInputStreamReader来实现编码。

ContextResource接口

ContextResource接口继承了Resource接口ContextResource应用较少上下文资源一般给Servlet引擎使用。。

2、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
URLURL 支持的协议org.springframework.core.io.UrlResource
ServletContextorg.springframework.web.context.support.ServletContextResource

BeanDefinitionResource

实际上这个类很少用到。

它是不可读的主要用于描述。

ByteArrayResource

内存型的资源流同java.io.ByteArrayInputStream

ClassPathResource

它是用class、classLoader进行资源的读取的。

FileSystemResource

3、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

// 代码实例
String currentJavaFilePath = "D:\\test.text";
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));
}

4、Spring 资源加载器

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

AbstractApplicationContext是应用上下文的实现所以说spring应用上下文也是一种资源加载器。

String currentJavaFilePath = "/D:\\test.text"; // 以 / 开头
// 新建一个 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));
}

5、Spring 通配路径资源加载器

通配路径 ResourceLoader

  • org.springframework.core.io.support.ResourcePatternResolver
    • org.springframework.core.io.support.PathMatchingResourcePatternResolver

路径匹配器

  • org.springframework.util.PathMatcher
    • Ant 模式匹配实现 - org.springframework.util.AntPathMatcher

Spring 通配路径资源扩展

public interface ResourceUtils {

    static String getContent(Resource resource) {
        try {
            return getContent(resource, "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static String getContent(Resource resource, String encoding) throws IOException {
        EncodedResource encodedResource = new EncodedResource(resource, encoding);
        // 字符输入流
        try (Reader reader = encodedResource.getReader()) {
            return IOUtils.toString(reader); // org.apache.commons.io.IOUtils
        }
    }
}

1实现 org.springframework.util.PathMatcher
2重置 PathMatcherPathMatchingResourcePatternResolver#setPathMatcher

public class CustomizedResourcePatternResolverDemo {

    public static void main(String[] args) throws IOException {
        // 读取当前 package 对应的所有的 .java 文件
        // *.java
        String currentPackagePath = "/D:\\";
        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;

通常 Resource 无法通过依赖查找可以通过@Value来配合依赖注入它们属于内部依赖对象非常规的 Bean 生命周期管理。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.Resource;
import javax.annotation.PostConstruct;
import java.util.stream.Stream;

/**
 * 注入 {@link Resource} 对象示例ResourceUtils在上面有
 *
 * @see Resource
 * @see Value
 * @see AnnotationConfigApplicationContext
 * @since
 */
public class InjectingResourceDemo {

    // 注入default.properties资源
    @Value("classpath:/META-INF/default.properties")
    private Resource defaultPropertiesResource;

    // 注入所有的properties资源
    // classpath*:/ 表示所有 ClassPath 下的资源相当于 ClassLoader#getResources 方法
    // classpath:/ 表示当前 ClassPath 下的资源相当于 ClassLoader#getResource 方法
    @Value("classpath*:/META-INF/*.properties")
    private Resource[] propertiesResources;

    // 获取Environment参数
    @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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import javax.annotation.PostConstruct;
import java.util.stream.Stream;

/**
 * 注入 {@link ResourceLoader} 对象示例
 *
 * @see ResourceLoader
 * @see Resource
 * @see Value
 * @see AnnotationConfigApplicationContext
 * @since
 */
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));// true
        System.out.println("resourceLoader == applicationContext : " + (resourceLoader == applicationContext)); // true
    }

    @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 核心编程思想》

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring