在Spring框架内部,Spring对JDK本身的资源访问接口(如File、URL等)做了封装,提供了一组Resource
接口,包括XML
配置文件、我们声明Bean的类(Class)等资源,都是通过Resource
接口来加载的。如果基于Spring框架进行一些扩展功能开发,当需要加载资源到IoC容器时,我们也需要用到Resource
接口。除此之外,使用Spring框架进行应用开发时,我们也推荐直接使用Resource
接口替代JDK相关接口进行资源加载。
常用的Resource组件包括:
java.net.Url
用于加载通过URL访问的资源这些接口都直接或间接的继承了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
接口我们还可以很方便的遍历指定包下的类,结合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等更成熟的热重载方案。