前几篇已经介简单绍了SpringIoC容器,其中BeanFactory是容器的基础实现,ApplicationContext是容器的高级实现,ApplicationContext在实现了BeanFactory容器的基础上扩展了很多高级功能,我们还是从基础入手,先分析BeanFactory,它是容器的基础,对BeanFactory有所了解之后,再来分析ApplicationContext就会简单的多…
IoC容器的初始化可以分为三步
* 资源文件定位
* 资源文件解析
* 注册BeanDefinition
本篇分析资源文件定位
打开SimplePropertyNamespaceHandlerWithExpressionLanguageTests.java
添加如下测试用例
@Test
public void testXmlBeanFactory() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource
("org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml"));
ITestBean foo = (ITestBean) beanFactory.getBean("foo");
System.out.println(foo.getName());
}
代码很简单,创建XmlBeanFactory实例,并从中获取名为foo
的bean并打印bean名称,再来看下配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="org.springframework.tests.sample.beans.TestBean" p:name="Baz"/>
<bean id="bar" class="org.springframework.tests.sample.beans.TestBean" p:name="#{foo.name}"/>
</beans>
配置文件也比较简单,只定义了foo
和bar
两个bean
上面代码中ClassPathResource是加载资源文件的类,查看其继承关系,Resource接口是其顶级接口,以此为分析的入口
1.Resource接口
表示从实际类型的底层资源(例如文件或类路径资源)中抽象出来的资源描述符的接口,是Spring内部对资源文件的统一接口,该接口的子接口和实现类很多
1.2 ClassPathResource
Resource接口类路径资源的实现。使用给定ClassLoader或给定Class来加载资源。我们来看ClassPathResource的几种典型用法
@Test
public void testClassPathResource1() throws IOException {
Resource resource = new ClassPathResource("/org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml");
print(resource);
}
@Test
public void testClassPathResource2() throws IOException {
Resource resource = new ClassPathResource("simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml",
SimplePropertyNamespaceHandlerWithExpressionLanguageTests.class);
print(resource);
}
@Test
public void testClassPathResource3() throws IOException {
Resource resource = new ClassPathResource("/org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml",
SimplePropertyNamespaceHandlerWithExpressionLanguageTests.class.getClassLoader());
print(resource);
}
public void print(Resource resource) {
byte[] read = new byte[10];
try {
resource.getInputStream().read(read, 0, read.length);
System.out.println(new String(read));
} catch (IOException e) {
e.printStackTrace();
}
}
使用了ClassPathResource三种不同的构造函数创建ClassPathResource对象,前两个测试用例使用了类对象信息,类加载器,而第三个测试用例只传递了path路径信息,分析下其中细节
1.2.1 只传递路径
//资源文件路径
private final String path;
//类加载器
@Nullable
private ClassLoader classLoader;
//类对象信息
@Nullable
private Class<?> clazz;
//构造函数 path:资源文件路径
public ClassPathResource(String path) {
//调用另一个构造函数,其中ClassLoader对象为null
this(path, (ClassLoader) null);
}
//构造函数
//path:资源文件路径
//classLoader:类加载器
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
//规范路径
String pathToUse = StringUtils.cleanPath(path);
//如果路径以"/"开头,则截取开头"/"以后字符做为路径
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
//将处理后的路径赋给this.path
this.path = pathToUse;
//获取classLoader并赋给this.classLoader
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
代码比较简单,其中this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
获取类加载器的代码,还是比较复杂的,这里涉及到了一些类加载器的知识,看代码之前,先了解下java中的类加载器
- bootstrap class loader:主要负责main方法启动的时候,加载JAVA_HOME/lib下的jar包
- extension class loader:主要负责加载JAVA_HOME/ext/lib下的jar包
- system class loader:主要负责加载classpath下的jar包或者类
java中的类加载机制比较复杂,这里只做一个简单的介绍,想更深入的了解的话,可以参考【深入Java虚拟机】之四:类加载机制
接下来看getDefaultClassLoader的过程
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
//优先获取线程上下文类加载器
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
// 获取当前类的类加载器
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
//获取SystemClassLoader
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
这里获取到的类加载器是线程上下文类加载器
,到这里ClassPathResource也创建完成了,此时的ClassPathResource实例已经拥有了资源文件路径和类加载器两个信息,如何通过这些信息来读取文件输入流呢?
resource.getInputStream().read(read, 0, read.length);
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
//如果类对象新不为null,则使用类对象信息的getResourceAsStream获取输入流
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
//如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
//否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
//以上三种方法都无法获取到输入流的话,那么说明文件不存在,抛出异常
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
这里类加载器是不为空的,调用is = this.classLoader.getResourceAsStream(this.path);
,继续看代码
URLClassLoader.java-->
public InputStream getResourceAsStream(String name) {
//将给定名为name的路径转换为URL资源
URL url = getResource(name);
try {
if (url == null) {
return null;
}
//打开URLConnection
URLConnection urlc = url.openConnection();
//获取InputStream
InputStream is = urlc.getInputStream();
//判断是否从其他jar包加载资源文件
if (urlc instanceof JarURLConnection) {
JarURLConnection juc = (JarURLConnection)urlc;
JarFile jar = juc.getJarFile();
synchronized (closeables) {
if (!closeables.containsKey(jar)) {
closeables.put(jar, null);
}
}
} else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
synchronized (closeables) {
closeables.put(is, null);
}
}
return is;
} catch (IOException e) {
return null;
}
}
涉及很多Java文件的操作,感兴趣的读者可自行查看其他的资料!
1.2.2 资源文件名称+类对象信息
对于第二个测试用例,只传递了文件名名称和类对象信息,ClassPathResource是如何加载到对应的资源呢?
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
构造函数很简单,分别为path何clazz赋值,接着看resource.getInputStream().read(read, 0, read.length);
,getInputStream()方法上面已经有了,此时类对象信息不为空,应该走第一个if语句
Class.java-->
public InputStream getResourceAsStream(String name) {
//解析文件名称
name = resolveName(name);
//获取classLoader
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
// 如果无法获取到classLoader,则说明当前类是一个系统类
return ClassLoader.getSystemResourceAsStream(name);
}
//返回文件输入流
return cl.getResourceAsStream(name);
}
其中的关键在于resolveName(name)方法,经过解析后,那么值变为org/springframework/beans/factory/xml/simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml
,该值即simplePropertyNamespaceHandlerWithExpressionLanguageTests.xml的相对路径
1.2.3 资源文件路径+classLoader对象
第三个测试用例的resource.getInputStream().read(read, 0, read.length);
与第一个测试用例,走的是相同的分支,不再赘述
通过ClassPathResource获取资源文件,就先讲到这里