Resource资源类型

在Spring框架内部,Spring对JDK本身的资源访问接口(如File、URL等)做了封装,提供了一组Resource接口,包括XML配置文件、我们声明Bean的类(Class)等资源,都是通过Resource接口来加载的。如果基于Spring框架进行一些扩展功能开发,当需要加载资源到IoC容器时,我们也需要用到Resource接口。除此之外,使用Spring框架进行应用开发时,我们也推荐直接使用Resource接口替代JDK相关接口进行资源加载。

常用Resource类型

常用的Resource组件包括:

  • UrlResource:封装了java.net.Url用于加载通过URL访问的资源
  • ClassPathResource:用于加载类路径下的资源
  • FileSystemResource:用于加载文件系统路径下的资源
  • InputStreamResource:用于加载输入流中的资源
  • ByteArrayResource:用于加载二进制数组中的资源

这些接口都直接或间接的继承了Resource接口。

通过Resource读取类路径下文件

下面是例子代码中,我们通过Resource接口读取了classpath下data.txt文件中的一行:

// 读classpath下的data.txt文件
Resource resource = new ClassPathResource("data.txt");
// 获取InputStream并读取
InputStream in = resource.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
System.out.println(s);
br.close();
isr.close();
in.close();

代码非常简单,我们创建了一个ClassPathResource对象,然后通过该对象获取到了输入流,然后读取文件即可。

通过Resource实现包扫描

通过Resource接口我们还可以很方便的遍历指定包下的类,结合MetadataReader可以很方便的实现包注解扫描等功能,而无需直接使用反射API,下面是一个例子。

package com.gacfox.demo.demoboot;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;

@Component
public class TestComponent implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        try {
            // 获取所有的资源
            Resource[] resourceList = applicationContext.getResources("classpath*:com/gacfox/demo/demoboot/**/*.class");
            // 遍历资源获取MetaData
            MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(applicationContext);
            for (Resource resource : resourceList) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                ClassMetadata classMetadata = metadataReader.getClassMetadata();
                AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

                System.out.println(classMetadata.getClassName());
                System.out.println(annotationMetadata.getAnnotationTypes());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

代码中,我们使用ApplicationContextAware注入了一个applicationContext对象,我们需要用它的getResources()方法获取指定包(package)下所有的类,然后我们通过创建Resource对象的MetadataReader,就可以获取类的各种信息了。例子代码中,我们获取了类的名字和类标注的注解。

这里注意,我们通过Resource读取类时,这个类其实可以处于未被加载的状态,如果我们需要获取类(Class)对象,可以使用Class.forName()等方式将其加载到JVM中。但注意这里有一个坑:如果我们包扫描的逻辑在一个Jar包中,而引用Jar包的工程使用了SpringBoot的DevTools,由于DevTools为了实现热重载而修改了工程代码包下类的类加载器,而Jar包中的类仍然使用系统类加载器,两个类加载器尽管加载的是同一个类但实际上打破了双亲委派机制,造成了类的重复加载,这就可能导致类型转换失败。遇到这个问题时,可以将Jar包也指定为DevTools管理,或是不使用DevTools而改为JRebel等更成熟的热重载方案。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap