在 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
侷限很大,由於通常不使用絕對路徑,下面代碼中第二種方式打包執行 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 以後就能夠看到能夠加載文件了。
咱們主要看下 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等,代碼大同小異,無需多講。