天天看點

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

      類加載器從 jdk 1.0 就出現了,最初是為了滿足 java applet 的需要而開發出來的。java applet 需要從遠端下載下傳 java 類檔案到浏覽器中并執行。現在類加載器在 web 容器和 osgi 中得到了廣泛的使用。一般來說,java 應用的開發人員不需要直接同類加載器進行互動。java 虛拟機預設的行為就已經足夠滿足大多數情況的需求了。不過如果遇到了需要與類加載器進行互動的情況,而對類加載器的機制又不是很了解的話,就很容易花大量的時間去調試<code>classnotfoundexception</code>和<code>noclassdeffounderror</code>等異常。本文将詳細介紹 java 的類加載器,幫助讀者深刻了解 java 語言中的這個重要概念。下面首先介紹一些相關的基本概念。

     顧名思義,類加載器是用來将java類加載到java虛拟機(jvm)中的。它是java語言的一個創新,也是java語言流行的重要原因之一。它使得java類可以被動态加載到java虛拟機中并執行。

     一般來說,java虛拟機使用java類的方式如下:java源程式(.java檔案)----&gt;經過編譯之後----&gt;變成java位元組碼(.class檔案)。類加載器(classloader)負責讀取java位元組碼,并轉換成java.lang.class類的一個執行個體,每個這樣的一個執行個體用來表示一個java類,通過此執行個體的newinstance()方法就可以建立出該類的一個對象。

        java 類加載器的作用就是在運作時加載類(把類的二進制加載到記憶體中),它可以在将類加載到虛拟機中的時候檢查類的完整性。

      java虛拟機中可以安裝多個類加載器,系統預設三個主要的類加載器,每個類加載器負責加載特定位置的類。他們分别是:

bootstrap、extclassloader、appclassloader

     加載器也是一個java類,由于java類要由類加載器來加載,那第一個類加載器又是由誰來加載它呢?這個加載器是bootstrap。

     bootstrap------它不是一個java類,他是嵌套在jvm(java虛拟機)中的一個用c++編寫的二進制代碼。

  //得到我們自定義類的位元組碼,得到此位元組碼的類加載器,得到類加載器的位元組碼,得到位元組碼的名字

  system.out.println(myclassloadertest.class.getclassloader().getclass().getname());

  //得到我們自定義類的位元組碼,得到此位元組碼的類加載器,得到類加載器的位元組碼

  system.out.println(myclassloadertest.class.getclassloader().getclass());

運作輸出:

sun.misc.launcher$appclassloader

class sun.misc.launcher$appclassloader

運作下面代碼:

//得到系統類的位元組碼,得到此位元組碼的類加載器

system.out.println(system.class.getclassloader());-------------------------------------------------1

//得到系統類的位元組碼,得到此位元組碼的類加載器,得到類加載器的位元組碼,得到位元組碼的名字

system.out.println(system.class.getclassloader().getclass().getname());

null                                         

exception in thread "main" java.lang.nullpointerexception

at shipin44.myclassloadertest.main(myclassloadertest.java:16)

程式報空指針異常。

位置1的代碼,運作結果為null,不代表它沒類加載器,(如果沒有那是誰加載它的呢?)而是加載它的加載器是bootstrap(一個特殊的類加載器,是c++寫的二進制代碼),bootstrap不是一個java對象,是以上面列印null。

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式
類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

public static void main(string[] args) {

  classloader cl = myclassloadertest.class.getclassloader();

  while (cl!=null) {

   system.out.println(cl.getclass().getname());

   //cl本來就是類加載器了,下面這樣的寫法是得到父加載器的父加載器多跳了1級

   cl = cl.getparent().getclass().getclassloader(); 

  }

  //列印第一個類加載器

  system.out.println(cl);

  system.out.println("------------正确輸出-------------");

  cl = myclassloadertest.class.getclassloader();

   system.out.println(cl.getclass().getname());  

   cl = cl.getparent(); 

 }

輸出結果:

null

------------正确輸出-------------

sun.misc.launcher$extclassloader

滑鼠右鍵點選myclassloadertest類---&gt;選export---&gt;java---&gt;jar file---&gt;next---&gt;finish,然後将導出的包copy到jre\lib\ext目錄下

運作輸出:

zf.jar包中的myclassloadertest類中的代碼如下:

  classloader cl = myclassloadertest.class.getclassloader();

  system.out.println("------------正确輸出-------------");

      當第一個類加載器要加載類的時候,它先不直接加載類,而是交(委托)給它的父類加載器去加載,而父類加載器又交(委托)給它的父類加載器去加載,就這樣一直往上的走,當到了bootstrap這個類加載器時,它沒有父加載器,然後它就到自己所管轄的範圍去找,找到了就加載出來,沒找到它就交給它的兒子加載器去加載,這時候它的兒子加載器才會去管轄的範圍找,如果又沒找到,那就又給兒子找,就這樣一直往下,當最後回到第一個類加載器的時候,如果還是沒找到的話就報異常。

      圖文解說(2級标題)

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

      它的好處是可以集中管理,有這樣一個情況,我有一個類要加載,這時候myclassloader1找到了這個類并加載了此類,而myclassloader2也找到了這個類也加載了此類,這時候記憶體中就存在了2個一樣的位元組碼,這樣就很浪費資源。

       1-1、首先它會調用目前線程的類加載器去加載線程中的第一個類。

                thread a = thread.currentthread();

                system.out.println(a.getcontextclassloader().getclass().getname());

                運作上面的代碼輸出:

                sun.misc.launcher$appclassloader

       1-2、如果類a中引用了類b,java虛拟機将使用加載類a的加載器來加載類b。        

       1-3、還可以直接調用classloader.loadclass()方法來指定某個類加載器去加載某個類。

               thread a = thread.currentthread();

               system.out.println(a.getcontextclassloader().getclass().getname());

               a.setcontextclassloader(system.class.getclassloader());---------------------------------這裡不寫.getclass().getname())是因為null沒有位元組碼,寫了程式報空指針異常。

               system.out.println(a.getcontextclassloader());

               運作上面的代碼輸出:

               sun.misc.launcher$appclassloader

               null

      問:能不能自己寫一個類叫java.lang.system?

      答:一把情況下不能,因為委托機制的原因,當你寫了這個類時,到加載的時候,流程會一直想上走,當到bootstrap時,它發現自己的管轄範圍有,于是他就直接加載一個system給你了,而這個system是rt.jar中的類。如果自己寫個類加載器就可以了,或者類名相同但是包名不相同也是可以的,但是不能類名和包名都相同。比如樓主可以寫個類叫:com.lang.string。

     1-1、自定義的類加載器必須繼承classloader

     1-2、一般盡量不要覆寫已有的loadclass()方法中的委托邏輯

              一般在jdk 1.2之前的版本才這樣做,而且事實證明,這樣做極有可能引起系統預設的類加載器不能正常工作。在jvm規範和jdk文檔中(1.2或者以後版本中),都沒有建議使用者覆寫loadclass(…)方法,相比而言,明确提示開發者在開發自定義的類加載器時覆寫findclass(…)邏輯。

        如果,覆寫loadclass方法,程式将不會用委托機制在建立代碼,是以一般都覆寫findclass方法。

問題一:自定義一個類,然後編寫一個簡單的加密算法(把類的位元組碼中的0換成1,把1換成0),并輸出到指定目錄下,然後再自定義一個類加載器,用來加載加密過的類。

建立一個用來加密的原始類,此類繼承java.util.date。并重寫tostring()方法。代碼如下

package shipin44;

import java.util.date;

public class classloaderattachment extends date {

 public string tostring(){

  return "hello,itcast";

}

編寫加密方法。代碼如下:

/**

  * 将傳過來的二進制加密(把1變成0,把0變成1)

  * cypher:暗号的意思

  * @param ips:超類,為了能傳多種輸入流(裡氏代換原則)

  * @param ops:超類,為了能傳多種輸出流(裡氏代換原則)

  * @throws exception

  */

 public static void cypher(inputstream ips,outputstream ops) throws exception{

  int b = -1;

//  ips.read();幫助文檔中的解釋

//  read

//  public abstract int read()

//                    throws ioexception從輸入流中讀取資料的下一個位元組。傳回 0 到 255 範圍内的 int 位元組值。如果因為已經到達流末尾而沒有可用的位元組,則傳回值 -1。在輸入資料可用、檢測到流末尾或者抛出異常前,此方法一直阻塞。

//  子類必須提供此方法的一個實作。 

//  傳回:下一個資料位元組;如果到達流的末尾,則傳回 -1。 

//  抛出: ioexception - 如果發生 i/o 錯誤。

  while ((b = ips.read()) != -1) {

   ops.write( b ^ 0xff);

   //system.out.println(b+"\t");//列印輸出的内容看不懂

   //system.out.println(b ^ 0xff);//列印輸出的内容看不懂

編寫執行加密操作的方法。代碼如下

  * 開始做加密檔案的工作

  * @param args:調用min方法時傳進來的參數

  * @throws exception 

 public static void doworkcypher(string[] args) throws exception{

        //源檔案路徑

  string srcpath = args[0];

  //檔案所在的目錄

  string destdir = args[1];

  //獲得檔案名

  string destfilename = srcpath.substring(srcpath.lastindexof('\\')+1);

  //目标檔案的相對路徑

  string destpath = destdir + "\\" + destfilename;

        //輸入流,把東西寫入記憶體中,srcpath原檔案的絕對路徑

  fileinputstream fis = new fileinputstream(srcpath);

  //輸入流,把東西從記憶體中寫出來,寫到硬碟上,destpath加密後的檔案的相對路徑

  fileoutputstream fos = new fileoutputstream(destpath);

  //加密類

  cypher(fis, fos);

  //關閉流

  fis.close();

  fos.close();

編寫main方法。

public static void main(string[] args) throws exception{

  // todo auto-generated method stub

  doworkcypher(args);

調用main方法

1、滑鼠右鍵點工程---&gt;new---&gt;folder---&gt;輸入itcast

2、滑鼠右鍵點以上方法所屬的類---&gt;run as---&gt;java application,程式報錯,因為調用min方法時沒傳相應的參數進去。

3、滑鼠右鍵點以上方法所屬的類---&gt;run as---&gt;run confugurarion---&gt;arguments---&gt;program(節目) arguments(參數)---&gt;輸入classloaderattachment.class所在的絕對路徑---&gt;空格---&gt;再輸入上面的folder名---&gt;點apply---&gt;最後run。

4、最後點項目工程f5重新整理後,會發現itcast檔案夾下多了個classloaderattachment.class檔案,此檔案是加過密的檔案。

測試結果

1、在一個min方法中列印system.out.println(new classloaderattachment().tostring());,輸出結果是:

hello,itcast

這時候的加載的類是原始的類

2、用itcast檔案夾下的classloaderattachment.class,把f:\java\bianchengruanjian\myeclipseworkspace\zhangxiaoxiangjichujiaqiang\bin\shipin44\classloaderattachment.class目錄下的原始.class(未加過密的)替換掉,運作system.out.println(new classloaderattachment().tostring());,程式報錯。

編寫自定義類加載器,此類必須繼承classloader。

重寫findclass方法,不能重寫loadclass方法。代碼如下:

//目錄名(itcast)

 string classdir;

 /**

  * 重寫findclass方法

 @override

 protected class&lt;?&gt; findclass(string name) throws classnotfoundexception {

  // todo auto-generated method stub  

  string classfilename = classdir + "\\" + name + ".class";

  try {

   fileinputstream fis = new fileinputstream(classfilename);

//加密的.class的絕對路徑,這樣也可以,這位置的classloaderattachment.class是加過密的

//  classfilename = "c:\\users\\json\\desktop\\classloaderattachment.class";

   bytearrayoutputstream bos = new bytearrayoutputstream();

   cypher(fis, bos);

   fis.close();

   byte[] bytes = bos.tobytearray();

   return defineclass(bytes, 0, bytes.length);

  } catch (exception e) {

   // todo auto-generated catch block

   e.printstacktrace();

  return super.findclass(name);

 //構造函數

 public myclassloader(string classdir){

  this.classdir = classdir;

1、在一個min方法中運作system.out.println(new classloaderattachment().tostring());,列印輸出:

2、将加過密後的classloaderattachment.class替換掉未加過密的classloaderattachment.class檔案。在myeclipseworkspace\zhangxiaoxiangjichujiaqiang\bin\shipin44目錄下運作程式報錯,如下圖:

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

3、用自定義類加載器myclassloader加載,加過密的classloaderattachment,在min方法中運作如下代碼:

  class clazz = new myclassloader("itcast").loadclass("classloaderattachment");

  date d1 = (date) clazz.newinstance();

  system.out.println(d1.getclass().getclassloader().getclass().getname());

  system.out.println(d1.getclass().getclassloader().getparent().getclass().getname());

  system.out.println(d1.tostring());

   輸出結果:

shipin44.myclassloader

      模闆方法模式(template method pattern),定義一個操作中的算法骨架,而将一些實作步驟延遲到子類當中。模闆方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。

      模闆方法模式是比較簡單的一種設計模式,但是它卻是代碼複用的一項基本的技術,在類庫中尤其重要,它遵循“抽象類應當擁有盡可能多的行為,應當擁有盡可能少的資料”的重構原則。作為模闆的方法要定義在父類中(并寫完此方法),在方法的定義中使用抽象方法,而隻看父類的抽象方法是根本不知道怎麼處理的,實際具體處理的是子類,在子類中實作具體功能,是以不同的子類執行将會得出不同的實作結果,但是處理流程還是按照父類定制的方式。這就是模闆方法的要義所在,定制算法骨架,讓子類具體實作。

      1-1、一次性實作一個算法不變的部分,并将可變的行為留給子類來實作;

      1-2、各子類中具有公共行為的時候,應該被提取出來并集中到一個公共父類中以避免代碼重複。

      1-3、當需要控制子類擴充的時候。模闆方法在特定點調用鈎子操作,這樣就隻允許在這些點進行擴充。

      下面簡單地描述一下上班族正常一天的生活行為

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

我們看到,每個人吃早餐和乘坐交通工具的方式都是個性化行為,但是每個人的行為架構确實一緻的,那就是:起床、吃早餐、乘坐交通工具、工作。而每個人吃早餐和乘坐交通工具的行為卻是要單獨實作的。

類加載器什麼是類加載器?類加載器的作用java虛拟機中三個主要的類加載器類加載器的委托機制 編寫自己的類加載器------------ ( 有包名的類不能調用無包名的類。在幫助文檔中一般看到protect方法多的就要想到用extends) 23種常用的設計模式之----------模闆方法設計模式

package com.demo;

public abstract class abstractpeople {

  * 起床

 public void getup(){

  system.out.println("起床");

  * 吃早餐

 public abstract void havebreakfast();

  * 抽象乘坐交通工具的方法

 public abstract void transport();

  * 工作

 public void dowork(){

  system.out.println("工作");

  * 模闆方法(每天的行為)

 public void daylift(){

  system.out.println("-----------------------");

  getup();

  havebreakfast();

  transport();

  dowork();

  system.out.println("------------------------");

public class peoplea extends abstractpeople {

  * 具體吃早餐的方法

 public void havebreakfast() {

  system.out.println("吃三明治,喝牛奶");

  * 具體做交通工具的方法

 public void transport() {

  system.out.println("開私家車上班");

public class peopleb extends abstractpeople {

  system.out.println("喝粥,吃小菜");

  system.out.println("坐公共汽車上班");

public class peoplec extends abstractpeople {

  system.out.println("吃煎餅,喝豆漿");

  system.out.println("做地鐵上班");

public class client {

  * @param args

 public static void main(string[] args) {

  abstractpeople peoplea = new peoplea();

  abstractpeople peopleb = new peopleb();

  abstractpeople peoplec = new peoplec();

  peoplea.daylift();

  peopleb.daylift();

  peoplec.daylift();

運作client類輸出結果:

-----------------------

起床

吃三明治,喝牛奶

開私家車上班

工作

------------------------

喝粥,吃小菜

坐公共汽車上班

吃煎餅,喝豆漿

做地鐵上班