Java中如何自定義一個(gè)類(lèi)加載器
如何自定義加載器?
1.創(chuàng)建一個(gè)自定義加載器類(lèi) 繼承 ClassLoader 類(lèi)
2.重寫(xiě) findClass 方法。 主要是實(shí)現(xiàn)從那個(gè)路徑讀取 jar包或者.class文件,將讀取到的文件用字節(jié)數(shù)組來(lái)存儲(chǔ),然后可以使用父類(lèi)的 defineClass 來(lái)轉(zhuǎn)換成字節(jié)碼。
如果想破壞雙親委派的話,就重寫(xiě) loadClass 方法, 否則不用重寫(xiě)
注意:
1.ClassLoader提供的 protected final Class<?> defineClass(String name, byte[] b, int off, int len) 是用來(lái)將字節(jié)數(shù)組轉(zhuǎn)換成字節(jié)碼文件的,傳入?yún)?shù) 是 (類(lèi)名,字節(jié)數(shù)組數(shù)據(jù),字節(jié)數(shù)組讀取的開(kāi)始下標(biāo),字節(jié)數(shù)組的長(zhǎng)度)
示例:讀取某文件的下的某class文件
創(chuàng)建一個(gè)名為MyClassLoader的類(lèi)加載器:
import java.io.*;
public class MyClassLoader extends ClassLoader{
String path ;
MyClassLoader(String dir){
this.path = dir ;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
this.path = this.path + name +".class";
File f = new File(path);
InputStream in = new FileInputStream(f);
byte [] bys = new byte[ (int)f.length() ];
int len = 0;
while( (len = in.read(bys) )!= -1 ){
}
// byte[] -> .class
return defineClass(name,bys,0,bys.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
}測(cè)試:
在如下目錄,生成一個(gè)Hello.class字節(jié)碼文件
Hello.java:
public class Hello {
public void sayHello(){
System.out.println("Hello World!");
}
}
測(cè)試類(lèi):
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
MyClassLoader my = new MyClassLoader("D:\\test\\jvmtest\\");
Class<?> c1 = my.loadClass("Hello");
Object o = c1.newInstance();
Method d = c1.getMethod("sayHello",null);
d.invoke(o);
}
}運(yùn)行測(cè)試類(lèi):

類(lèi)加載器的使用及自定義類(lèi)加載器
package com.tech.load.def;
/**
* @author lw
* @since 2021/12/3
*/
public class UserImpl {
static {
System.out.println("UserImpl init ...");
}
}package com.tech.load.def;
/**
* @author lw
* @since 2021/12/3
*/
public class DefLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//上下文類(lèi)加載器,默認(rèn)使用的是 應(yīng)用程序類(lèi)加載器
// ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// Class<?> c1 = contextClassLoader.loadClass("com.tech.load.def.UserImpl");
// c1.newInstance(); //classloader.loadClass 不會(huì)觸發(fā)初始化,當(dāng)創(chuàng)建對(duì)象時(shí)執(zhí)行初始化,執(zhí)行靜態(tài)程序塊內(nèi)容 輸出 "UserImpl init ..."
// ClassLoader contextClassLoader1 = Thread.currentThread().getContextClassLoader();
// Class<?> c2 = contextClassLoader1.loadClass("com.tech.load.def.UserImpl");
// c2.newInstance(); //使用相同的類(lèi)加載器 加載相同的類(lèi)名 則加載的是同一個(gè)類(lèi),c1 c2是同一個(gè)類(lèi),由于已經(jīng)初始化過(guò) 創(chuàng)建對(duì)象不再初始化 不再打印 "UserImpl init ..."
// System.out.println(contextClassLoader==contextClassLoader1); //true 獲取的上下文類(lèi)加載器是同一個(gè)類(lèi)加載器
// System.out.println(c1==c2); // true 同一個(gè)類(lèi)加載器器,加載同名的類(lèi),第一次加載時(shí)加載的類(lèi)會(huì)緩存到類(lèi)加載器的緩存,再次加載直接在緩存讀取,兩次加載的是同一個(gè)類(lèi)
//直接獲取類(lèi)的類(lèi)加載器 應(yīng)用程序類(lèi)加載器
ClassLoader classLoader = UserImpl.class.getClassLoader();
ClassLoader classLoader1 = UserImpl.class.getClassLoader();
System.out.println(classLoader==classLoader1); //true 獲取的是同一個(gè)應(yīng)用程序類(lèi)加載器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader==contextClassLoader); //true 線程上下文類(lèi)加載器默認(rèn)采用的也是應(yīng)用程序類(lèi)加載器 與通過(guò)類(lèi)型對(duì)象getClassLoader獲取方式相同
Class<?> c1 = Class.forName("com.tech.load.def.UserImpl"); //會(huì)觸發(fā)類(lèi)的初始化
ClassLoader classLoader2 = c1.getClassLoader();
System.out.println(classLoader==classLoader2); //true 獲取的是同一個(gè)應(yīng)用程序類(lèi)加載器
}
}在應(yīng)用程序中,默認(rèn)我們獲取上下文類(lèi)加載器、類(lèi)型對(duì)象getClassLoader都是采用的同一個(gè)應(yīng)用程序類(lèi)加載器,類(lèi)在第一次被加載后會(huì)緩存到類(lèi)加載器的緩存中,由于是同一個(gè)類(lèi)加載器此時(shí)同名的類(lèi)不能被多次加載,且應(yīng)用程序類(lèi)加載器只能加載classpath下的類(lèi)。
如果我們想加載自定義路徑下的類(lèi),需要用到自定義類(lèi)加載器,可以去指定路徑下加載類(lèi),且通過(guò)創(chuàng)建多個(gè)類(lèi)加載器對(duì)象,加載的同名類(lèi)相互隔離,也就是說(shuō)同名類(lèi)可以被多個(gè)自定義類(lèi)加載器對(duì)象加載。
編寫(xiě)自定義類(lèi)加載器:
- 繼承ClassLoader;
- 重寫(xiě)findClass方法在指定路徑下進(jìn)行類(lèi)的加載,得到字節(jié)數(shù)組,然后使用defineClass根據(jù)字節(jié)數(shù)組生成字節(jié)碼文件 也就是class文件;
編寫(xiě)一個(gè)測(cè)試類(lèi)Goods放在D盤(pán)下,javac得到class文件
/**
* @author lw
* @since 2021/12/3
*/
public class Goods {
static {
System.out.println("Goods init ...");
}
}javac Goods.java
package com.tech.load.def;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 自定義類(lèi)加載器 加載類(lèi)
* @author lw
* @since 2021/12/3
*/
public class DefLoad7 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader classLoader1 = new MyClassLoader();
Class<?> c1 = classLoader1.loadClass("Goods");
Class<?> c2 = classLoader1.loadClass("Goods");
System.out.println(c1==c2);//true 使用同一個(gè)類(lèi)加載器加載同名類(lèi)兩次,實(shí)際只加載了一次,第二次是在類(lèi)加載器的緩存加載的 結(jié)果兩次加載的是同一個(gè)
c1.newInstance(); //會(huì)初始化
c2.newInstance(); //不會(huì)初始化
MyClassLoader classLoader2 = new MyClassLoader();
Class<?> c3 = classLoader2.loadClass("Goods");
System.out.println(c1==c3); //false 使用不同的類(lèi)加載器對(duì)同一個(gè)類(lèi)進(jìn)行加載,會(huì)得到不同的類(lèi)型對(duì)象
c3.newInstance(); //會(huì)初始化
}
}
//自定義類(lèi)加載器 加載D盤(pán)下的類(lèi)
class MyClassLoader extends ClassLoader{
//去指定的路徑下加載類(lèi)
//name是類(lèi)名稱(chēng)
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path="D:\\"+name+".class";
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
//將指定路徑下的文件 拷貝到輸出流
Files.copy(Paths.get(path),os);
byte[] bytes = os.toByteArray();
//調(diào)用父類(lèi)的方法 根據(jù)字節(jié)數(shù)組生成字節(jié)碼文件 也就是class文件
//bytes -> *.class
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("類(lèi)文件未找到",e);
}
}
}
使用自定義加載器,創(chuàng)建多個(gè)類(lèi)加載器對(duì)象去加載同一個(gè)類(lèi),會(huì)得到多個(gè)類(lèi)型對(duì)象。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java訪問(wèn)者設(shè)計(jì)模式詳細(xì)講解
大多數(shù)情況下你不需要訪問(wèn)者模式,但當(dāng)一旦需要訪問(wèn)者模式時(shí),那就是真的需要它了,這是設(shè)計(jì)模式創(chuàng)始人的原話。可以看出應(yīng)用場(chǎng)景比較少,但需要它的時(shí)候是不可或缺的,這篇文章就開(kāi)始學(xué)習(xí)最后一個(gè)設(shè)計(jì)模式——訪問(wèn)者模式2022-11-11
基于Java事件監(jiān)聽(tīng)編寫(xiě)一個(gè)中秋猜燈謎小游戲
眾所周知,JavaSwing是Java中關(guān)于窗口開(kāi)發(fā)的一個(gè)工具包,可以開(kāi)發(fā)一些窗口程序,然后由于工具包的一些限制,導(dǎo)致Java在窗口開(kāi)發(fā)商并沒(méi)有太多優(yōu)勢(shì),不過(guò),在JavaSwing中關(guān)于事件的監(jiān)聽(tīng)機(jī)制是我們需要重點(diǎn)掌握的內(nèi)容,本文將基于Java事件監(jiān)聽(tīng)編寫(xiě)一個(gè)中秋猜燈謎小游戲2023-09-09
Spring Bean實(shí)例化實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Spring Bean實(shí)例化實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
java實(shí)現(xiàn)建造者模式(Builder Pattern)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)建造者模式Builder Pattern,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
java實(shí)現(xiàn)遍歷樹(shù)形菜單兩種實(shí)現(xiàn)代碼分享
這篇文章主要介紹了java實(shí)現(xiàn)遍歷樹(shù)形菜單兩種實(shí)現(xiàn)代碼分享,兩種實(shí)現(xiàn):OpenSessionView實(shí)現(xiàn)、TreeAction實(shí)現(xiàn)。具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11

