mybatis源碼分析-配置文件加載

mybatis源碼分析-環境搭建 一文中,咱們的測試代碼以下:java

public static void main(String[] args) throws IOException { 
    String resource = "mybatis-config.xml";  
    InputStream inputStream = Resources.getResourceAsStream(resource);  
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    try {  
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);  
        List<Dept> deptList = deptMapper.getAllDept();  
        System.out.println(deptList);  
    } finally {  
        sqlSession.close();  
    }  
 }

mybatis首先須要加載配置文件,包括全局配置文件和映射文件,代碼中咱們使用了 Resources 類的 getResourceAsStream 方法來獲取 InputStream 對象,以此來進行後面的操做,所以咱們先來研究 Resources 類。sql

本節的重點就是下面的代碼:segmentfault

String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);

加載配置文件

在研究Resources以前,咱們考慮一下如何獲取個輸入流,也就是讀取一個文件?mybatis

  • 使用 FileInputStream
  • 使用 ClassLoader 的 getResourceAsStream 方法
  • 使用 類.class 的 getResourceAsStream 方法

使用 FileInputStream
侷限很大,由於通常不使用絕對路徑,下面代碼中第二種方式打包執行 jar 包會出現找不到文件的錯誤。app

public static void main(String[] args) throws IOException {  
  
    //要麼是全路徑  
    File file1 = new File("D:\\my-code\\mybatis\\src\\main\\resources\\mybatis-config.xml");  
    InputStream input1 = new FileInputStream(file1);  
    System.out.println(input1.available());  
  
    //要麼是去在全路徑基礎上去掉項目名  
    File file2 = new File("src\\main\\resources\\mybatis-config.xml");  
    InputStream input2 = new FileInputStream(file2);  
    System.out.println(input2.available());  
}

使用 ClassLoader的 getResourceAsStream 方法maven

public static void main(String[] args) throws IOException {  
    InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mappers/DeptMapper.xml");  
    System.out.println(inputStream.available());  
}

此方法能夠獲取 resources 下面的配置文件,可是若是配置文件存在與和resources同級別的java目錄下面,是否能夠獲取到呢?咱們將配置文件移到java代碼的目錄下面,使用下面代碼獲取文件:ide

public static void main(String[] args) throws IOException {  
    InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("com/yefengyu/mybatis/mappers/DeptMapper.xml");  
    System.out.println(inputStream.available());  
}

結果是空指針異常,找不到相關文件。函數

Exception in thread "main" java.lang.NullPointerException
    at com.yefengyu.mybatis.Main.main(Main.java:22)

這裏特別注意:使用 ClassLoader 獲取文件,都是從類路徑下面獲取,也就是maven install 以後生成的 target 目錄下面的classes目錄,而不是源碼路徑。使用idea開發工具,在代碼目錄下面的配置文件,是不會install 到classes下面的。那若是有些人喜歡把 mapper 配置文件寫入到 java 目錄下面,而不是 resources 目錄下面,那麼如何解決呢?工具

pom.xml文件新加下面的配置便可:也就是將 java 和 resources 下面的配置文件都加載到類路徑下。源碼分析

<build>
    <finalName>strategy-service</finalName>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

使用 類.class 的 getResourceAsStream 方法

public static void main(String[] args) throws IOException {  
    InputStream resource=Main.class.getResourceAsStream("mybatis-config.xml");  
    System.out.println(resource.available());  
}

注意此時 mybatis-config.xml 是在 resources 的根目錄,爲啥獲取不到呢?

Exception in thread "main" java.lang.NullPointerException
    at com.yefengyu.mybatis.Main.main(Main.java:22)

查看下源碼:

public InputStream getResourceAsStream(String name) {  
    name = resolveName(name);  
    ClassLoader cl = getClassLoader0();  
    if (cl==null) {  
        // A system class.  
        return ClassLoader.getSystemResourceAsStream(name);  
    }  
    return cl.getResourceAsStream(name);  
}

發現此種方式也是使用 classLoader 獲取文件的,那麼爲何沒有加載到文件呢?主要是這句:

name = resolveName(name);

這裏從新解析了文件名稱:

private String resolveName(String name) {  
    if (name == null) {  
        return name;  
    }  
    if (!name.startsWith("/")) {  
        Class<?> c = this;  
        while (c.isArray()) {  
            c = c.getComponentType();  
        }  
        String baseName = c.getName();  
        int index = baseName.lastIndexOf('.');  
        if (index != -1) {  
            name = baseName.substring(0, index).replace('.', '/')  
                +"/"+name;  
        }  
    } else {  
        name = name.substring(1);  
    }  
    return name;  
}

這裏主要是先獲取調用類的相對路徑,再加上咱們所傳入的文件路徑,那麼也就是從 java 目錄下面查找了,天然找不到。

若是咱們把 mybatis-config.xml 配置文件放入到 main 函數所在類的同級目錄,從新 install 以後就能夠看到能夠加載文件了。

mybatis如何實現?

咱們主要看下 Resources 類,關於獲取InputStream有兩個方法:

public static InputStream getResourceAsStream(String resource) throws IOException { 
    return getResourceAsStream((ClassLoader)null, resource);  
}  
  
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {  
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);  
    if (in == null) {  
        throw new IOException("Could not find resource " + resource);  
    } else {  
        return in;  
    }  
}

第一個方法調用第二個方法,只傳遞一個參數,就是文件名稱。而後調用第二個重載的方法,傳入空的ClassLoader 對象。第二個方法也使用 classLoaderWrapper 來獲取 InputStream 對象。

classLoaderWrapper 是對 ClassLoader 的一個封裝。主要是嘗試多種classLoader對文件進行加載。相關代碼:

public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {  
  return getResourceAsStream(resource, getClassLoaders(classLoader));  
}

這段代碼就是 Resources 類中,classLoaderWrapper 所調用的方法。它也調用的是它的重載方法,在看重載方法以前,咱們看下 getClassLoaders 方法,此方法是儘量多的獲取 ClassLoader.

ClassLoader[] getClassLoaders(ClassLoader classLoader) {  
  return new ClassLoader[]{  
      classLoader,  
      defaultClassLoader,  
      Thread.currentThread().getContextClassLoader(),  
      getClass().getClassLoader(),  
      systemClassLoader};  
}

這裏返回了5種 ClassLoader,下面看下getResourceAsStream方法:

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {

      InputStream returnValue = cl.getResourceAsStream(resource);

      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      }

      if (null != returnValue) {
        return returnValue;
      }
    }
  }
  return null;
}

這裏仍是使用 ClassLoader獲取 InputStream 對象,一個ClassLoader沒有獲取,下一個ClassLoader繼續,直到全部的都嘗試加載文件,沒有什麼太難的。

對於 Resources 類,除了獲取 InputStream,還有 URL、Properties、Reader、File等,代碼大同小異,無需多講。

收穫

  • 瞭解加載配置文件的細節
  • 重載方法的優雅編寫方式:入參少的方法調用入參多的方法,沒有的參數使用默認值,最後那個參數最多的方法統一處理。
相關文章
相關標籤/搜索