05--SpringIoC容器初始化资源文件读取(一)

前几篇已经介简单绍了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>

配置文件也比较简单,只定义了foobar两个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获取资源文件,就先讲到这里

猜你喜欢

转载自blog.csdn.net/lyc_liyanchao/article/details/82384247