一. 類加載器java
JVM中的類加載器:在jvm中,存在兩種類加載器,
a) Boostrap ClassLoader:這個是由c++實現的,因此在方法區並無Class對象的實例存在。用於加載JAVA_HOME/bin目錄下的jar包c++
b) 其餘類加載器:由java實現,能夠在方法區找到其Class對象。這裏又細分爲幾個加載器web
擴展類加載器(Extension ClassLoader):它負責用於加載JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變量指定所指定的路徑中全部類庫,開發者能夠直接使用擴展類加載器。java.ext.dirs系統變量所指定的路徑的能夠經過程序來查看。System.getProperty("java.ext.dirs")api
應用程序類加載器(Application ClassLoader):負責加載用戶類路徑上指定的類庫。開發者能夠直接使用這個類加載器。ps:在沒有指定自定義類加載器的狀況下,這就是程序的默認加載器。數組
自定義類加載器(User ClassLoader):緩存
雙親委派模型:避免因爲Class字節碼被屢次加載。底層類加載器在收到一個類加載的請求的時候,都先把請求轉發給其父加載器(並非一個繼承的關係),父類查找不到纔會讓子類去加載。若是強制只有雙親委派模型,那麼,web服務器的隔離是沒法實現的。tomcat
因爲最近想作一個相似tomcat同樣的簡易版web服務器來加深理解http請求的處理過程。咱們都清楚,每一個web應用在tomcat中均可以使用本身版本的jar。除了少許的包,如Servlet-api.jar,還有一些java原生的包以外,tomcat是會爲每一個不一樣的應用加載不一樣的jar包或者class,且彼此之間不會相互影響。這一步是經過自定義類加載器來實現的,在虛擬機層面,判斷兩個Class是否相等的前提是他們是同一個類加載器加載的,不然就沒有意義了。這篇文章簡單的實現一個自定義的加載過程,PS:這個例子並無破壞雙親委派模型,由於例子中依然會查找父類,若是找不到再使用子類加載。接下來筆者會再更新破壞雙親委派模型的博客,這裏挖個坑。
首先自定義類加載器,最重要的就是先繼承ClassLoader這個類。加載器的加載流程是,給出一個Class文件的全限定名,而後調用loadClass方法,這個方法每部會如今本身已經加載的類中查找,若是找到就返回。找不到則向父類查找,若是父類都找不到這纔開始本身加載,調用findClass方法。因此咱們只要覆蓋findClass方法就能夠實現本身定義的加載了。順帶一提,ClassLoader中有一個方法叫defineClass(String, byte[], int, int);這個方法經過傳進去一個Class文件的字節數組,就能夠方法區生成一個Class對象。因此要實現findClass的目標就很明確了,只要將Class文件讀取進來,而後生成byte數組,調用defineClass方法就能夠了。服務器
@Override protected Class<?> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; }
那麼如何找到Class文件呢?手動生成/網絡下載咱們就暫時不談論。這裏只說兩種最多見的,一是直接.class文件中查找,二是從jar包中加載。
從class文件中加載很是簡單。只要找到相應的文件,就能夠經過字節流讀取進來。代碼以下:網絡
input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray();
從jar讀取則相對麻煩一點,java給咱們提供了一個專門用來讀取jar包文件的類,抽象成一個JarFile的對象。經過調用這個對象的getInputStream方法,也是能夠獲取文件的輸入流,從而讀取字節數組。筆者作了一點相應的緩存,若是每次查找文件都要先讀取jar文件,再遍歷查找class文件是很是耗時的操做。因而,筆者選擇再加載以前,把全部的jar包中的全部class讀取到內存中,保存在一個map對象中。創建一個全限定名和字節數組的映射。這樣在加載階段,就能省下不少的時間了。所有的代碼以下jvm
package com.chasel.cloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 自定義的類加載器【子類優先】 * @author hujiancai * @description * @data 2017年3月11日 * @version v_0.1 */ public class MyWebAppLoader extends ClassLoader{ /** * lib:表示加載的文件在jar包中 * 相似tomcat就是{PROJECT}/WEB-INF/lib/ */ private String lib; /** * classes:表示加載的文件是單純的class文件 * 相似tomcat就是{PROJECT}/WEB-INF/classes/ */ private String classes; /** * 採起將全部的jar包中的class讀取到內存中 * 而後若是須要讀取的時候,再從map中查找 */ private Map<String, byte[]> map; /** * 只須要指定項目路徑就好 * 默認jar加載路徑是目錄下{PROJECT}/WEB-INF/lib/ * 默認class加載路徑是目錄下{PROJECT}/WEB-INF/classes/ * @param webPath * @throws MalformedURLException * @throws SecurityException * @throws NoSuchMethodException */ public MyWebAppLoader(String webPath) throws NoSuchMethodException, SecurityException, MalformedURLException{ lib = webPath + "WEB-INF/lib/"; classes = webPath + "WEB-INF/classes/"; map = new HashMap<String,byte[]>(64); preReadJarFile(); } /** * 按照父類的機制,若是在父類中沒有找到的類 * 纔會調用這個findClass來加載 * 這樣只會加載放在本身目錄下的文件 * 而系統自帶須要的class並非由這個加載 */ @Override protected Class<?> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 從指定的classes文件夾下找到文件 * @param name * @return */ private byte[] getClassFromFileOrMap(String name){ String classPath = classes + name.replace('.', File.separatorChar) + ".class"; File file = new File(classPath); if(file.exists()){ InputStream input = null; try { input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(input != null){ try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } }else{ if(map.containsKey(name)) { //去除map中的引用,避免GC沒法回收無用的class文件 return map.remove(name); } } return null; } /** * 預讀lib下面的包 */ private void preReadJarFile(){ List<File> list = scanDir(); for(File f : list){ JarFile jar; try { jar = new JarFile(f); readJAR(jar); } catch (IOException e) { e.printStackTrace(); } } } /** * 讀取一個jar包內的class文件,並存在當前加載器的map中 * @param jar * @throws IOException */ private void readJAR(JarFile jar) throws IOException{ Enumeration<JarEntry> en = jar.entries(); while (en.hasMoreElements()){ JarEntry je = en.nextElement(); String name = je.getName(); if (name.endsWith(".class")){ String clss = name.replace(".class", "").replaceAll("/", "."); if(this.findLoadedClass(clss) != null) continue; InputStream input = jar.getInputStream(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } byte[] cc = baos.toByteArray(); input.close(); map.put(clss, cc);//暫時保存下來 } } } /** * 掃描lib下面的全部jar包 * @return */ private List<File> scanDir() { List<File> list = new ArrayList<File>(); File[] files = new File(lib).listFiles(); for (File f : files) { if (f.isFile() && f.getName().endsWith(".jar")) list.add(f); } return list; } /** * 添加一個jar包到加載器中去。 * @param jarPath * @throws IOException */ public void addJar(String jarPath) throws IOException{ File file = new File(jarPath); if(file.exists()){ JarFile jar = new JarFile(file); readJAR(jar); } } }