自定義類加載器-從.class和.jar中讀取

一. 類加載器java

  1. JVM中的類加載器:在jvm中,存在兩種類加載器,
    a) Boostrap ClassLoader:這個是由c++實現的,因此在方法區並無Class對象的實例存在。用於加載JAVA_HOME/bin目錄下的jar包c++

b) 其餘類加載器:由java實現,能夠在方法區找到其Class對象。這裏又細分爲幾個加載器web

  1. 擴展類加載器(Extension ClassLoader):它負責用於加載JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變量指定所指定的路徑中全部類庫,開發者能夠直接使用擴展類加載器。java.ext.dirs系統變量所指定的路徑的能夠經過程序來查看。System.getProperty("java.ext.dirs")api

  2. 應用程序類加載器(Application ClassLoader):負責加載用戶類路徑上指定的類庫。開發者能夠直接使用這個類加載器。ps:在沒有指定自定義類加載器的狀況下,這就是程序的默認加載器。數組

  3. 自定義類加載器(User ClassLoader):緩存

  4. 雙親委派模型:避免因爲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);
        }
    }
}
相關文章
相關標籤/搜索