類加載器ClassLoader即用於加載其它類的類,將字節碼加載進內存,建立Class對象,輸入徹底限定的類名,輸出Class對象。html
ClassLoader分三類:java
Bootstrap ClassLoader→Extension ClassLoader→Application ClassLoader,後面的類有一個指針parent指向前面的類,ClassLoader在加載一個類時,基本步驟爲:編程
該步驟所描述的模型即雙親委派模型,優先讓父ClassLoader去加載。數組
這樣作的緣由是能夠避免java類庫被覆蓋的問題,優先讓父ClassLoader加載,避免自定義的類覆蓋java類庫,確保java安全機制,即便自定義ClassLoader能夠不聽從雙親委派模型,但java安全機制保證以java開頭的類不被其它類加載器加載。安全
ClassLoader是一個抽象類,提供將字節碼(class文件)加載至內存成爲Class的基本方法,每個Class都有一個基本方法 :框架
默認狀況下,上述三類類加載器都有默認實現,Bootstrap ClassLoader用C++實現,Extension ClassLoader與ide
Application ClassLoader的實現位於sun.misc.Launcher中。測試
ClassLoader的基本方法爲:url
Class的靜態方法forName()一樣用於加載類進入內存並返回Class對象,區別在於Class.forName加載時可執行static靜態代碼塊,而ClassLoader的loadClass不會執行,Class.forName方法可指定參數boolean initialize決定是否執行,ClassLoader的loadClass內部調用方法loadClass(name,false),這個方法內部調用findClass(name),是真正的類加載方法,第二個參數名爲resolve,爲true時才連接去執行static語句塊。因爲雙親委派模型,即便設置爲true,父類傳遞的仍然是false。spa
普遍應用於各類框架中,使用ClassLoader,可經過讀取配置文件直接加載Class進入內存,這樣在面向接口編程中不用使用new 聲明具體的實現,經過加載配置文件加載指定的類並返回實例,不動代碼動配置,能夠幫助完成依賴注入這個抽象概念的一部分實現。
public class HelloWorld {
public static void say(){
System.out.println("HelloWorld");
}
}
複製代碼
#注意路徑要正確,當前包下,也能夠是其它包
word=com.lsl.kennen.common.HelloWorld
複製代碼
public static void main(String[] args) {
try {
//讀取配置文件,注意文件名路徑要正確
Properties properties=new Properties();
String fileName=new File("").getAbsolutePath()+ "/kennen/src/main/java/com/lsl/kennen/common/test.properties";
properties.load(new FileInputStream(fileName));
//從配置文件中獲取源數據
String className =properties.getProperty("word");
//根據信息加載類進入內存返回Class
Class<?> cls=Class.forName(className);
//獲取實例
HelloWorld helloWorld=(HelloWorld) cls.newInstance();
helloWorld.say();
}catch (Exception e){
e.printStackTrace();
}
}
//結果爲HelloWorld
複製代碼
使用ClassLoader加載類,讀取配置文件動態將Class加載進內存
自定義ClassLoader,利用defineClass方法直接返回一個Class<?>,能夠遠程調用Class的bytes讀取Class文件將其加載進入本地內存並執行,這種功能就不是本地類加載器能辦到的(沒辦法new,沒辦法forName,loadClass,由於讀取的都是本地類的路徑名加載本地bytes)。同時,面向接口編程時,能夠動態加載calss,兩個class,使用不一樣的ClassLoader加載,JVM就認爲他們加載的對象是不一樣的,能夠實現隔離與熱部署。
自定義ClassLoader:繼承ClassLoader,重寫findClass,返回一個Class<?>
須要注意的是:
方法defineClass()用於實際讀取bytes()返回Class<?>,然而指定的類名必須符合規範,如com.lsl.xxx這樣的,內部類則是com.lsl.xxx$Iservice這樣的
由於是經過反射建立實例,因此對象的構造器不能設置爲private,不然拋出異常。
首先,先定義一個Class:
public class HelloWorld{
public void say(){
System.out.println("HelloWorld");
}
}
複製代碼
而後重寫類加載器:
public class Main {
public static void main(String[] args) {
//注意路徑
String className=new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class";
ClassLoader classLoader=new MyClassLoader();
try {
Class<?> c=classLoader.loadClass(className);
((HelloWorld) c.newInstance()).say();
}catch (Exception e){
e.printStackTrace();
}
}
//重寫ClassLoader
static class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
//本地測試,指定從本地讀取class
FileInputStream fileInputStream;//指定源
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();//指定目的
try{
fileInputStream=new FileInputStream(name);
byte[] buf=new byte[1024];
int ite=0;
while((ite=fileInputStream.read(buf))!=-1){//從文件源中讀取數據寫入到buf
outputStream.write(buf,0,ite);//從buf中讀取數據寫入到outputStream
}
}catch (Exception e){
e.printStackTrace();
}
return defineClass("com.lsl.kennen.common.HelloWorld",outputStream.toByteArray(),0,outputStream.toByteArray().length);//加載class文件的byte數組(bytes),返回Class<?>
}
}
}
複製代碼
然而直接這樣寫會拋出異常,重寫的ClassLoader將指定的class文件加載爲Class,加載至內存,返回Class<?>,使用反射強制類型轉換時若是直接強轉爲一個類會拋出異常,由於本地類沒有沒加載,就算加載了,使用的不是一個類加載器,加載出來的對象JVM是不認爲相同的,這個時候就只能使用接口,能夠強轉爲接口,只要加載的類實現了接口那麼就能夠強轉爲接口,調用接口方法。
定義接口:
public interface World {
public void say();
}
複製代碼
定義實現:
public class HelloWorld implements World{
@Override
public void say(){
System.out.println("HelloWorld");
}
}
複製代碼
將 ((HelloWorld) c.newInstance()).say();改成 ((World) c.newInstance()).say();
運轉正常,World恢復。
熱部署即在不中止程序運行的狀況下動態更改Class,只要修改了Class就能馬上看出變化。
使用自定義ClassLoader,由於不一樣的類加載器能夠加載出不一樣的Class,所以利用這一特性便可以實現模塊隔離,也能夠實現熱部署。
定義服務實現:
public class HelloWorld implements World{
private static volatile World helloWorld;
public static World getHelloWorld(){
if(helloWorld==null){
synchronized (HelloWorld.class){
helloWorld=createHelloWorld();
}
}
return helloWorld;
}
public static World createHelloWorld(){
try{
MyClassLoader myClassLoader=new MyClassLoader();
String className=new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class";
Class<?> c=myClassLoader.loadClass(className);
return ((World)c.newInstance());
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void update(){
helloWorld=createHelloWorld();
}
@Override
public void say(){
System.out.println("HelloWorld");
}
}
複製代碼
服務內部實現內部維護一個接口World,提供服務功能,其實是經過自定義的ClassLoader加載的Class返回的實例
類加載器爲:
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
//本地測試,指定從本地讀取class
FileInputStream fileInputStream;//指定源
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();//指定目的
try{
fileInputStream=new FileInputStream(name);
byte[] buf=new byte[1024];
int ite=0;
while((ite=fileInputStream.read(buf))!=-1){//從文件源中讀取數據寫入到buf
outputStream.write(buf,0,ite);//從buf中讀取數據寫入到outputStream
}
}catch (Exception e){
e.printStackTrace();
}
return defineClass("com.lsl.kennen.common.HelloWorld",outputStream.toByteArray(),0,outputStream.toByteArray().length);//加載class文件的byte數組(bytes),返回Class<?>
}
}
複製代碼
模擬熱部署,定義兩個線程,一個不斷訪問服務,一個不斷檢測字節碼問件是否改變以肯定是否更新服務
public class Main {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
while (true){
World world=HelloWorld.getHelloWorld();
world.say();
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
});
Thread thread1=new Thread(new Runnable() {
private long lastModilied=new File(new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class").lastModified();
@Override
public void run() {
while (true){
try{
Thread.sleep(100);
//根據時間差判斷文件是否被修改
long now=new File(new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class").lastModified();
if(now!=lastModilied){
//由時間差更新內部服務,完成熱部署,動態變動執行的Class
lastModilied=now;
HelloWorld.update();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
});
thread1.setDaemon(true);
thread.start();
thread1.start();
}
}
複製代碼
固然直接運行時,沒辦法直接改Class文件,預先編譯好Class,在運行時進行替換便可看到變化
對於遠程加載Class,能夠直接經過URLClassLoader加載,位於java.net包下,一樣的,由於基於ClassLoader,因此必定要面向接口編程,具體的類強轉是行不通的。參見: locez.com/JAVA/urlcla…
參考 :
<<java 編程的邏輯>> 馬俊昌著