前言
Spring是怎么找到resources下面的配置文件的?先说一个大概,后面再完善。
在Spring中对resources下面的配置文件加载是通过new ClassPathReource("XXX")来封装成ClassPathResource,而ClassPathResource中是通过class或者classLoader底层的双亲委派机制方法进行加载,即AppClassLoader在父类加载器中类找到的时候,就会自己查找,在java.class.path路径下查找,找到就返回,没找到就让之类找,如果没有之类就会抛出异常。
问题场景
less
@Test
@SuppressWarnings(value = "deprecation")
public void testSimpleLoad() {
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean myTestBean = (MyTestBean) xmlBeanFactory.getBean("myTestBean");
}
beanFactoryTest.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myTestBean" class="MyTestBean"/>
</beans>
MyTestBean.java
typescript
public class MyTestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
这段代码实现了如下功能:
-
加载资源文件
-
读取配置文件
-
实例化Bean
-
然后获取实例化的Bean
这里我们探究第一个功能,即加载资源文件
探究原理
源码流程
This.reader.loadBeanDefinition(resource)才是资源加载的真正实现
java
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
封装资源文件,对参数使用EncodedResource进行封装
java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
获取资源流inputstream。得到inpustream后,我们就可以进行解析了
csharp
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
然后通过classLoader。GetResourceAsStream(this.path)来获取
kotlin
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
后面就不展开了,就是通过双亲委派机制,父类加载器没找到,而是先调用AppClassLoader#findResource,然后调用BuiltinClassLoader# findResourceOnClassPath(name),然后通过URLClassPath#findResource获取对应的loader,然后最后调用相应的Loader#findResource方法来获取到配置文件的URL。
什么是ClassPathResource?在源码中,是怎么通过ClassPathResource#getInputStream获取到资源输入流呢?
Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源,我们通过ClassPathResource("XXXX")加载classpath资源。
ClassPathResource中的实现了Resource中的getInputStream方法,是通过classLoader提供的底层方法进行调用的。
ClassPathReource的classLoader是什么呢?
目前所知的就是我们在加载我们的配置文件的时候,在创建ClassPathResource实例时候,会指定其classLoader为AppClassLoader
classLoader底层是怎么找到我们要加载的配置文件的呢?
JVM在加载ClassLoaders类的时候会调用静态块文件:
通过System.getProperty("java.class.path")获取系统类路径,然后实例化一个URLClassPath,进而实例化出一个AppClassLoader出来,而这个AppClassLoader就能加载所有的class path文件,至于这个class path是怎么来的,而系统属性中的java.class.path又是怎么赋初值的,这里先不说,我也还没研究明白。
总结
Spring其实就是通过Java提过的类加载机制来加载我们的配置文件。Spring通过接口类Resource抽象了所有内部使用到的底层资源,不同的资源有不同的实现,我们加载配置文件用到的ClassPathResource就对应于Classpath资源,其实现便是通过JAVA类加载提供的底层方法进行的。
扩展
-
System.getProperty("java.class.path")获取到的系统属性是怎么来的或者说是怎么进行赋值的呢?这一块我通过看java.class.path对应的路径发现都是我们依赖的java和resources文件夹路径,所以猜测与我们编译过程中有关或者maven有关
-
最后我们探究下来其实就是通过URLClassPath中的Loader有关,而通过debug发现loader其实有JarLoader,FileLoader等等,这些与我们说的类加载器是啥关系?之前一直以为类加载器就是加载.class文件的。
-
Redource接口抽象了所有spring内部使用的底层资源:文件、Classpath、URL资源、InputStream资源,这里面用到了大量的模板方法来抽取公共方法,抽取抽象类,抽象接口。
-
加载配置火箭获取inputstream只是第一步,后面探究的就是怎么读取配置文件的了。