Java深入(內省、類加載器)


 內省引出JavaBean

一、內省

        1、內省對應的英文單詞爲IntroSpector,英文意思是檢查、視察、體檢之意,對於程序即對內部進行檢查,瞭解更多的底層細節。

        2、內省的作用:主要針對JavaBean進行操作。

 

二、JavaBean

1、簡述:

        1)JavaBean是一種特殊的Java類,主要用於傳遞數據信息,這種Java類中的方法主要用於訪問私有的字段,且方法符合某種特殊的命名規則。

        2)它是一種特殊的Java類,其中的方法符合特殊的規則。只要一個類中含有get或is和set打頭的方法,就可以將其當做JavaBean使用。

        3)字段和屬性:

             字段就是我們定義的一些成員變量,如private String name;等

             JavaBean的屬性是根據其中的setter和getter方法來確定的,而不是依據其中的變量,如方法名爲setId,則中文意思是設置Id,getId也是如此;去掉set或者get前綴,剩餘部分就是屬性名稱。如果剩餘部分的第二個字母小寫,則把剩餘部分改爲小寫。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。

        總之,一個類被當作javaBean使用時,JavaBean的屬性是根據方法名推斷出來的,它根本看不到java類內部的成員變量。

2、作用:

        如果要在兩個模板之間傳遞多個信息,可將這些信息封裝到一個JavaBean中,這種JavaBean的實例對象通常稱之爲值對象(Value Object,簡稱VO),這些信息在類中用私有字段來儲存,如果讀取或設置這些字段的值,則需要通過一些相應的方法來訪問。

3、JavaBean的好處:

        一個符合JavaBean特點的類當做普通類一樣可以使用,但是把它當做JavaBean類用會帶來一些額外的好處:

        1)在Java EE開發中,經常要使用到JavaBean。很多環境就要求按JavaBean方式進行操作,別人都這麼用和要求這麼做,那你就沒什麼挑選的餘地!

        2)JDK中提供了對JavaBean進行操作的API,這套API稱爲內省,若要自己通過getX的方式來訪問私有x,可用內省這套API,操作JavaBean要比使用普通的方式更方便。

示例:

    package cn.itheima.demo;  
      
    import java.beans.IntrospectionException;  
    import java.beans.Introspector;  
    import java.beans.PropertyDescriptor;  
    import java.lang.reflect.InvocationTargetException;  
    import java.lang.reflect.Method;  
      
    public class IntroSpectorDemo {  
        public static void main(String[] args) throws Exception {  
            HashCodeTest hct=new HashCodeTest(2,3);  
            String propertyName="x";  
            //"x"-->"X"-->"getX"-->Method getX-->  
              
            //用內省的方式  
            //獲取並getX方法  
            Object retval = getProperty1(hct, propertyName);  
            System.out.println(retval);  
              
            Object value=5;  
            //獲取並調用setX方法  
            setProperty(hct, propertyName, value);  
              
            System.out.println(hct.getX());   
        }  
        //獲取並調用setX方法  
        private static void setProperty(HashCodeTest hct, String propertyName,  
                Object value) throws IntrospectionException,  
                IllegalAccessException, InvocationTargetException {  
            PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//創建對象關聯  
            Method methodSetX=pd.getWriteMethod();//獲取JavaBean類中的setX方法  
            methodSetX.invoke(hct,value);//調用setX方法  
        }  
        //獲取並getX方法  
        private static Object getProperty1(HashCodeTest hct, String propertyName)  
                throws IntrospectionException, IllegalAccessException,  
                InvocationTargetException {  
            PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//創建對象關聯  
            Method methodGetX=pd.getReadMethod();//獲取JavaBean類中的getX方法  
            Object retval=methodGetX.invoke(hct);//調用getX方法  
            return retval;  
        }  
    }  

Eclipse小技巧:

        選擇要變爲方法的代碼,右鍵——>Refactor——>Extract Method,然後就會生成一個方法了。

 

三、對JavaBean的複雜內省操作

        1、在IntroSpector類中有getBeanInfo(Class cls)的方法,通過此方法獲取BeanInfo實例。參數是相應對象的字節碼,即Class對象。

        2BeanInfo類中有getPropertyDescriptors()的方法,可獲取所有的JavaBean類中的屬性信息,返回一個PropertyDescriptor[]

        3、在通過遍歷的形式,獲取與想要的那個屬性信息。

如:獲取並調用getX方法

    //第二種較複雜的獲取並調用JavaBean中的getX方法  
    private static Object getProperty2(HashCodeTest hct, String propertyName)  
            throws IntrospectionException, IllegalAccessException,  
            InvocationTargetException {  
        BeanInfo beanInfo=Introspector.getBeanInfo(hct.getClass());//創建對象關聯  
        PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();//獲取所有的屬性描述  
        Object retval=null;  
              
        //遍歷  
        for (PropertyDescriptor pd : pds) {  
            //如果屬性跟參數的屬性相等,就獲取它的getX方法  
            if (pd.getName().equals(propertyName)) {  
                Method methodGetX=pd.getReadMethod();//獲取getX方法  
                retval=methodGetX.invoke(hct);  
                break;  
            }  
        }  
        return retval;    
    }  

四、BeanUtils工具包

1、BeanUtils等工具包都是由阿帕奇提供的,爲了便於開發。

2、BeanUtils可以將8種基本數據類型進行自動的轉換,因此對於非基本數據類型,就需要註冊轉換器Converter,這就需要ConverUtils包。

3、好處:

        1)提供的set或get方法中,傳入的是字符串,返回的還是字符串,因爲在瀏覽器中,用戶輸入到文本框的都是以字符串的形式發送至服務器上的,所以操作的都是字符串。也就是說這個工具包的內部有自動將整數轉換爲字符串的操作。

        2)支持屬性的級聯操作,即支持屬性鏈。如可以設置:人的腦袋上的眼睛的眼珠的顏色。這種級聯屬性的屬性連如果自己用反射,那就很困難了,通過這個工具包就可以輕鬆調用。

4、可以和Map集合進行相互轉換:可將屬性信息通過鍵值對的形式作爲Map集合存儲(通過static java.util.Mapdescribe(java.lang.Object bean)的方法)。也可以將Map集合轉換爲JavaBean中的屬性信息(通過static void populate(java.lang.Objectbean, java.util.Map properties)的方法)。

注:要正常使用BeanUtils工具,還要將Apache公司的logging(日誌)的jar包也添加進Build Path。

Eclipse小知識:

        在工程中導入工具jar包。

兩種方式:

        1,右鍵項目--選擇Properties---Java Build Path--選擇Liberiers標籤。AddExternal Jars--選擇要導入的jar包。即可。

        這樣做有個問題就是如果jar路徑發生變化。項目就不能使用到這個jar包。

        2,在項目中建立一個lib目錄,專門用於存放項目所使用到的jar工具包。將要使用到jar包複製粘貼進來,並在jar上點右鍵--選擇Builder Path---Add to BiuldPath,即可。這時jar包中的對象,就可以使用了。

        這樣做的好處:項目移動,jar隨項目移動。

示例:

//用BeanUtils工具包的工具類BeanUtils方法  
    BeanUtils.setProperty(hct, propertyName, "9");//set,9是String  
    System.out.println(BeanUtils.getProperty(hct, propertyName));//get  
    System.out.println(BeanUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.String  
          
//BeanUtils工具包中的PropertyUtils的操作  
    PropertyUtils.setProperty(hct, propertyName, 9);//9是Integer  
    System.out.println(PropertyUtils.getProperty(hct, propertyName));  
System.out.println(PropertyUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.Integer 

類加載器

一、概述

1、定義:簡單說,類加載器就是加載類的工具。

        在java程序中用到一個類,出現了這個類的名字。java虛擬機首先將這個類的字節碼加載到內存中,通常這個類的字節碼的原始信息放在硬盤上的classpath指定的目錄下,把.class文件的內容加載到內存裏面來,再對它進行處理,處理之後的結果就是字節碼。這些工作就是類加載器在操作。

2、類加載器作用:將.class文件中的內容變爲字節碼加載進內存。

3、默認類加載器:

        1)Java虛擬機中可安裝多個類加載器,系統默認的有三個主要的,每個類負責加載特定位置的類:BootStrap、ExtClassLoader、AppClassLoader

        2)類加載器本身也是Java類,因爲它是Java類的加載器,本身也需要被類加載器加載,顯然必須有第一個類加載器而不是java類的,這正是BootStrap。它是嵌套在Java虛擬機內核中的,已啓動即出現在虛擬機中,是用c++寫的一段二進制代碼。所以不能通過java程序獲取其名字,獲得的只能是null。

4、Java虛擬機中的所有類加載器採用父子關係的樹形結構進行組織,在實例化每個類裝載器對象時,需要爲其指定一個父級類裝載器對象或者默認採用系統類裝載器爲其父級類加載。

5、類加載器之間的父子關係和管轄範圍圖

示例:

package cn.itheima.demo;  
  
public class ClassLoaderDemo {  
  
    public static void main(String[] args) {  
        System.out.println(  
                ClassLoaderDemo.class.getClassLoader().getClass().getName()  
                );//sun.misc.Launcher$AppClassLoader,表示由AppClassLoader加載  
        System.out.println(System.class.getClassLoader());//null,表示System這個類時由RootStrap加載的  
    }  
}

二、類加載器的委託機制

1、每個ClassLoader本身只能分別加載特定位置和目錄中的類,但它們可以委託其他的類加載器去加載類,這就是類加載器的委託模式。

2、加載類的方式

        當Java虛擬機要加載一個類時,到底要用哪個類加載器加載呢?

         1)首先,當前線程的類加載器去加載線程中的第一個類。

         2)若A引用類B(繼承或者使用了B),Java虛擬機將使用加載類的類加載器來加載類B

         3)還可直接調用ClassLoaderLoaderClass()方法,來指定某個類加載器去加載某個類。

2、每個類加載器加載類時,又先委託給上級類加載器。

        類裝載器一級級委託到BootStrap類加載器,當BootStrap無法加載當前所要加載的類時,然後才一級級回退到子孫類加載器去進行加載。當回退到最初的發起者類裝載器時,如果它自己也不能完成類的裝載,那就會拋出ClassNotFoundException異常。這時就不會再委託發起者加載器的子類去加載了,如果它還有子類的話。

        簡單說,就是先由發起者將類一級級委託爲BootStrap,從父級開始找,找到了直接返回,沒找到再助劑讓其子級找,直到發起者,再沒找到就報異常。

3、委託機制的優點:可以集中管理,不會產生多字節碼重複的現象。

補充:面試題

        可不可以自己寫個類爲:java.lang.System呢?

        回答:第一、通常是不可以的,由於類加載器的委託機制,會先將System這個類一級級委託給最頂級的BootStrap,由於BootStrap在其指定的目錄中加載的是rt.jar中的類,且其中有System這個類,那麼就會直接加載自己目錄中的,也就是Java已經定義好的System這個類,而不會加載自定義的這個System

        第二、但是還是有辦法加載這個自定義的System類的,此時就不能交給上級加載了,需要用自定義的類加載器加載,這就需要有特殊的寫法才能去加載這個自定義的System類的。

體現委託機制的示例:

    package cn.itheima.demo;  
      
    public class ClassLoaderDemo {  
      
        public static void main(String[] args) {  
            /* 
             * 用eclipse的打包工具將ClassLoaderTest輸出成jre/lib/ext目錄下的itheima.jar包 
             * 此時再在eclipse中運行這個類時,下面代碼的while循環內的運行結果顯示爲ExtClassLoadr。 
             * 這就表示,AppClassLoader在加載這個類時,會先委託給其上一級ExtClassLoader加載器去加載,而上級又委託上級 
             * 但是ExtClassloader的上級沒有找到要加載的類,就回到ExtClassLoader,此時它在jre/lib/ext中找到了,所以就結果就顯示它了。 
             * */  
            ClassLoader loader=ClassLoaderDemo.class.getClassLoader();  
            while (loader!=null) {  
                System.out.println(loader.getClass().getName());  
                loader=loader.getParent();//將此loader的上級賦給loader  
            }  
            System.out.println(loader);  
        }  
    }  

三、自定義類加載器

1、自定義的類加載器必須繼承抽象類ClassLoader,要覆寫其中的findClass(String name)方法,而不用覆寫loadClass()方法。

2、覆寫findClass(Stringname)方法的原因:

        1)在loadClass()內部是會先委託給父級,當父級找不到後返回,再調用findClass(String name)方法,也就是你自定義的類加載器去找。所以只需要覆寫findClass方法,就能實現用自定義的類加載器加載類的目的。

        因爲,一般自定義類加載器,會把需要加載的類放在自己指定的目錄中,而java中已有的類加載器是不知道你這個目錄的,所以會找不到。這樣纔會調用你複寫的findClass()方法,用你自定義的類加載器去指定的目錄加載類。

        2)這是一種模板方法設計模式。這樣就保留了loadClass()方法中的流程(這個流程就是先找父級,找不到再調用自定義的類加載器),而我們只需複寫findClass方法,實現局部細節就行了。

        ClassLoader提供了一個protected  Class<?>defineClass(String name, byte[] b, int off, int len)方法,只需要將類對應的class文件傳入,就可以將其變爲字節碼。

3、編程步驟:

        1)編寫一個對文件內容進行簡單加密的程序

        2)編寫好了一個自己的類加載器,可實現對加密過來的類進行加載和解密。

        3)編寫一個程序,調用類加載器加載類,在源程序中不能用該類名定義引用變量,因爲編譯器無法識別這個類,程序中除了可使用ClassLoaderloadClass方法外,還可以使用設置線程的上下文類加載器或系統類加載器,然後再使用Class.forName

4、編碼步驟:

        1)對不帶包名的class文件進行加密,加密結果存放到另外一個目錄,例如: java MyClassLoader MyTest.class F:\itcast

        2)運行加載類的程序,結果能夠被正常加載,但打印出來的類裝載器名稱爲AppClassLoaderjava MyClassLoader MyTest F:\itcast

        3)用加密後的類文件替換CLASSPATH環境下的類文件,再執行上一步操作就出問題了,錯誤說明是AppClassLoader類裝載器裝載失敗。

        4)刪除CLASSPATH環境下的類文件,再執行上一步操作就沒問題了。

示例:

package cn.itheima.demo;  
  
import java.util.Date;  
//定義一個測試類,繼承Date,便於使用時加載  
public class ClassLoaderAttachment extends Date{  
    //複寫toString方法  
    public String toString(){  
        return "Hello World!";  
    }  
}  
  
import java.io.ByteArrayOutputStream;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.InputStream;  
import java.io.OutputStream;  
  
public class MyClassLoader extends ClassLoader{  
  
    public static void main(String[] args) throws Exception {  
        String srcPath=args[0];//文件源  
        String destDir=args[1];//文件目的  
        InputStream ips=new FileInputStream(srcPath);  
        String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);  
        String destFilePath=destDir+"\\"+destFileName;  
        OutputStream ops=new FileOutputStream(destFilePath);  
        cypher(ips,ops);//加密class字節碼  
        ips.close();  
        ops.close();  
    }  
    //加密方法  
    private static void cypher(InputStream ips,OutputStream ops) throws Exception{  
        int b=-1;  
        while((b=ips.read())!=-1){  
            ops.write(b^0xff);  
        }  
    }  
  
    @Override  
    //覆蓋ClassLoader的findClass方法  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        name=name.substring(name.lastIndexOf(".")+1);  
        String classFileName=classDir+"\\"+name+".class";//獲取class文件名  
        InputStream ips=null;  
        try {  
            ips=new FileInputStream(classFileName);  
            ByteArrayOutputStream bos=new ByteArrayOutputStream();//定義字節數組流  
            cypher(ips,bos);//解密  
            ips.close();  
            byte[] buf=bos.toByteArray();//取出字節數組流中的數據  
            return defineClass(null, buf,0,buf.length);//加載進內存  
              
        } catch (Exception e) {  
            // TODO: handle exception  
            e.printStackTrace();  
        }  
        return null;  
        //return super.findClass(name);  
    }  
      
    private String classDir;  
    public MyClassLoader(){}  
    //帶參數的構造函數  
    public MyClassLoader(String classDir){  
        this.classDir=classDir;  
    }  
  
}  
  
import java.util.Date;  
  
public class ClassLoaderDemo {  
  
    public static void main(String[] args) throws Exception {  
        //將用自定義的類加載器加載.class文件  
        Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment");  
        Date d1 =  (Date)clazz.newInstance();//獲取Class類的實例對象  
        System.out.println(d1);  
    }  
} 




 







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