java學習筆記(Java,類加載器,反射)-自定義類加載器獲取運行中工程之外的字節碼文件

類加載器學習筆記

因爲畢業設計的一個功能是想要實現學生做完編程題目後提交到服務器進行判卷,所以遇到了整個畢設設計階段最大的難題:學生新提交的字節碼文件是我的系統編譯運行之後的部分,只用簡單的Class.forName並不能如願得到該.class文件,所以有了這篇筆記O(∩_∩)o

類加載器

1.平時我們所說的加載只是類加載過程的一個步驟,一共分爲7個階段:加載、驗證、準備、解析、初期花、使用、回收。而在加載階段,虛擬機會使用類加載器來加載class文件。

2.類加載器分爲三種:

啓動類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)、應用程序類加載器(Application ClassLoader)

其中啓動類加載器由C++編寫而成,是虛擬機自身的一塊;
擴展類加載器負責加載當時咱們配的<JAVA_HOME>\lib\ext目錄下的類;
應用程序類加載器也稱系統類加載器,負責加載ClassPath指定的類庫。

ExtClassLoader與AppClassLoader的父類都是URLClassLoader,但AppClassLoader的父加載器ExtClassLoader,ExtClassLoader的父加載器爲null。這一個怪異的地方是由於源碼中的這幾句:
首先看一下java的入口源代碼,具體解釋在四段代碼的後面,注意源代碼中加註釋的部分
public Launcher() {
 ...
	Launcher.ExtClassLoader var1;
	try {
		// 在這裏創建ExtClassLoader 實例。
		var1 = Launcher.ExtClassLoader.getExtClassLoader();
	} catch (IOException var10) {
		throw new InternalError("Could not create extension class loader", var10);
	}

	try {
		// 創建AppClassLoader實例。將ExtClassLoader 實例var1作爲參數傳入
		this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
	} catch (IOException var9) {
		throw new InternalError("Could not create application class loader", var9);
	}
這個是ExtClassLoader構造方法中的重要句子:
static class ExtClassLoader extends URLClassLoader {
	    ...
    
        public ExtClassLoader(File[] var1) throws IOException {
        //這裏發現父類構造第二個參數傳了一個null
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }	
        ...
這個是AppClassLoader構造方法中的重要句子:
static class AppClassLoader extends URLClassLoader {

        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null?new File[0]:Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2);
                    // 這裏創建AppClassLoader時,這裏的var0是LaunCher創建時傳來的ExtClassLader的對象,然後進到下面的構造方法
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }
        
        AppClassLoader(URL[] var1, ClassLoader var2) {
        //這裏發現將ExtClassLoader的實例var0改名var2 並傳進列第二個參數
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }
        ...
再看看他們super調用的父類構造就能看出端倪
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(parent);
           ...
首先第一段代碼可以得知Launcher獲取了ExtClassLoader與AppClassLoader,但獲取AppClassLoader時將ExtClassLoader傳了進去。之後,ExtClassLoader往父類構造第二個參數中傳了null,與此同時,AppClassLoader在得到ExtClassLoader後將實例var0傳入自己的構造var2,並傳入了父類構造的第二個參數。 最後一段代碼可以看到他們的父類構造第二個參數叫做parent。 這下明白了,雖然他們的父類都是URLClassLoader,但父加載器卻是null與Ext加載器。

雙親委派模式

雙親委派模式的工作過程是:任何層次的一個類加載器收到了類加載的請求時,並不會自己立馬加載這個類,而是把這個請求委派給父類加載器去完成,因此所有的加載請求最終都會傳送到頂層的啓動類加載器中,只有當父加載器發現自己的搜索範圍沒有時,子加載器纔會去自己的營業範圍內尋找,依次向下。

自頂向下爲:啓動類加載器(BootStrapxxx)->擴展類加載器(Extensionxxx)->應用程序類加載器(Applicationxxx)->用戶自定義加載器 xxx= ClassLoader

比較值得注意的是,儘管ExtClassLoader的parent爲Null,但還認爲BootStrapClassLoader是他的父加載器,因爲ExtClassLoader的源碼中有這麼一句:

try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }

如果父類爲空,去找BootStrap去解決。這算是乾爹?


現在,進行實踐,看看如何通過自定義的類加載器來完成對於新下載的.class文件如何進行獲取

首先,我將一個java文件編譯後的.class文件Question001通過jdbc存進了數據庫。

                ...
                Blob blob = rs.getBlob("q_script");
                
                // 獲取字節流
				InputStream in = blob.getBinaryStream();
				
				// 在本地創建考生文件夾
				File dir = new File("C:\\EOSTestFolder");

				if (!dir.exists()) {
					dir.mkdirs();
				}

				File file = new File("C:\\EOSTestFolder\\" + rs.getString("q_name") + "." + fileBehind);

				if (!file.exists()) {
					file.createNewFile();
				}

				OutputStream out = new BufferedOutputStream(new FileOutputStream(file));

                // 定義一個緩衝區
				byte[] buff = new byte[1024];
				
				for (int i = 0; (i = in.read(buff)) > 0;) {
				// 寫數據
					out.write(buff, 0, i);
				}
				...

在這裏插入圖片描述

之後學生將測試用的題目下載到了本地的C:\EOSTestFolder文件夾,並保存其文件名以便於一會加載它。

現在我們自定義類加載器:

@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		//獲取文件名
		String fileName = getFileName(name);
		
		File file = new File(mLibPath, fileName);

		try {
			FileInputStream is = new FileInputStream(file);

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			int len = 0;
			try {
				while ((len = is.read()) != -1) {
					bos.write(len);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

			byte[] data = bos.toByteArray();
			is.close();
			bos.close();

			//返回需要的類
			return defineClass(name, data, 0, data.length);

		} catch (IOException e) {
			e.printStackTrace();
		}

		return super.findClass(name);

這個加載器的作用就是通過delineClass()將指定路徑下的字節碼文件讀入並轉換爲Class對象。

最後通過反射,看看這個不存在於web工程中的文件經過存取數據庫之後,能否正常的讀出信息:

// 創建類加載器
		CustomClassLoader customClassLoader = new CustomClassLoader("C:\\EOSTestFolder");

		Class<?> clazz = null;

		try {
			clazz = customClassLoader.findClass("EOSTestFolder.Question001");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		System.out.println(clazz.toString());

		Method[] methods = clazz.getDeclaredMethods();

		for (Method m : methods) {

			System.out.println(m.toString());

		}

結果讓人欣慰,只要能得到class就可以繼續攻克接下的難題啦:

class EOSTestFolder.Question001
public java.lang.String EOSTestFolder.Question001.getName()
public void EOSTestFolder.Question001.setName(java.lang.String)
public void EOSTestFolder.Question001.show()

`

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章