Java學習之內省、注解與類加載器

一、由內省IntroSpector引出JavaBean

1、概述:

1、IntroSpector:即內省,是對內部進行檢查,了解更多的底層細節。

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

2、JavaBean(存在于java.bean包中)

1、簡述:

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

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

3)字段和屬性:
字段就是我們定義的一些成員變量,如private String name;
而屬性是具有某些功能,Bean屬性,是含有get或set方法的那些屬性的字段,即這個變量的get屬性,set屬性等。

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

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

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

5、JavaBean的好處:
一個符合JavaBean特點的類當做普通類一樣可以使用,但是把它當做JavaBean類用肯定有好處的:

1)在JavaEE開發中,經常要使用JavaBean。很多環境就要求按JavaBean的方式進行操作,別人都這么用,那么就必須要求這么做。

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

示例:

package cn.itcast.text1;  
  
import java.beans.BeanInfo;  
import java.beans.IntrospectionException;  
import java.beans.Introspector;  
import java.beans.PropertyDescriptor;  
import java.lang.reflect.InvocationTargetException;  
import java.lang.reflect.Method;  
  
public class IntroSpectorTest {  
  
    /** 
     * @param args 
     */  
    /* 
     * public static void main(String[] args) throws Exception { 
     
        // TODO Auto-generated method stub 
        ReflectPoint pt1 = new ReflectPoint(3,5); 
        String propertyName = "x"; 
        //"x"-->"X"-->"getX"-->MethodGetX--> 
        //內省的方式: 
        //屬性描述符:PropertyDescriptor 
        //get屬性信息 
        PropertyDescriptor pd = 
                new PropertyDescriptor(propertyName,pt1.getClass()); 
        Method methodGetX = pd.getReadMethod(); 
        Object retVal = methodGetX.invoke(pt1); 
        System.out.println(retVal); 
        //set屬性信息 
        Object value = 7; 
        PropertyDescriptor pd2 = 
                new PropertyDescriptor(propertyName,pt1.getClass()); 
        Method methodSetX = pd2.getWriteMethod(); 
        methodSetX.invoke(pt1,value); 
         
        System.out.println(pt1.getX()); 
     } 
     */  
    //上面的get或set代碼分別通過選中要重構的代碼,通過右擊選重構獲得get和set方法:  
    public static void main(String[] args) throws Exception {  
        // TODO Auto-generated method stub  
        ReflectPoint pt1 = new ReflectPoint(3,5);  
        String propertyName = "x";  
        //一般方式:"x"-->"X"-->"getX"-->MethodGetX-->  
        //內省方式:  
        //通過get和set方法獲取屬性值  
        Object retVal = getProperty(pt1, propertyName);  
        System.out.println(retVal);  
          
        Object value = 7;  
        setProperty(pt1, propertyName, value);  
        System.out.println(pt1.getX());  
    }  
      
        //設置屬性值的方法             //此處的類型為Object,通用,下同  
    private static void setProperty(Object rf, String propertyName,  
            Object value) throws IntrospectionException,  
            IllegalAccessException, InvocationTargetException {  
        //創建屬性描述符對象,將屬性名稱和加載文件等信息寫入其中  
        PropertyDescriptor pd =  
                new PropertyDescriptor(propertyName,rf.getClass());  
        //通過反射的方法類Method,獲取屬性所對應的set方法  
        Method methodSetX = pd.getWriteMethod();  
        methodSetX.invoke(rf, value);  
    }  
    //獲取屬性值的方法  
    private static Object getProperty(Object rf, String propertyName)  
            throws IntrospectionException, IllegalAccessException,  
            InvocationTargetException {  
        //創建屬性描述符對象,獲取屬性所對應的名稱和加載文件等信息  
        PropertyDescriptor pd =  
                new PropertyDescriptor(propertyName,rf.getClass());  
        //通過反射的方法類Method,獲取屬性所對應的get方法  
        Method methodGetX = pd.getReadMethod();  
        Object retVal = methodGetX.invoke(rf);  
        return retVal;  
    }  
}

3、對JavaBean的復雜內省操作:

1、在IntroSpector類中有getBeanInfo(Class cls)的方法。

2、獲取Class對象的Bean信息,返回的是BeanInfo類型。

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

4、在通過遍歷的形式,找出與自己想要的那個屬性信息。
如:改寫get方法:

…  
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());  
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();  
        Object value = null;  
        for(PropertyDescriptor pd : pds){  
            if(pd.getName().equals(propertyName)){  
                Method methodGetX = pd.getReadMethod();  
                value = methodGetX.invoke(pt1);  
                break;  
            }  
        }  
…  

這種方式要比上面的方法復雜些。

4、BeanUtils工具包:

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

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

2、好處:

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

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

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

4、示例:
1)設置和獲取屬性值:

import java.lang.reflect.InvocationTargetException;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.Map;  
import java.util.TreeMap;  
  
import org.apache.commons.beanutils.BeanUtils;  
import org.apache.commons.beanutils.ConversionException;  
import org.apache.commons.beanutils.ConvertUtils;  
import org.apache.commons.beanutils.Converter;  
import org.apache.commons.beanutils.PropertyUtils;  
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;  
import org.junit.Test;  
  
public class BeanUtilDemo {  
  
    /** 
     * BeanUtils使用 
     */  
  
    @Test   
    public void test1() throws Exception{  
        //創建對象,設置屬性值  
        Person p = new Person();  
        BeanUtils.setProperty(p, "name", "zzz");  
        String name = BeanUtils.getProperty(p, "name");  
        System.out.println(name);  
    }  
      
    @Test   
    public void test2() throws Exception{  
        //創建對象,傳入屬性值  
        Person p = new Person();  
        String name = "wangwu";  
        String age = "23";  
        String hight = "173.5";  
        //設置屬性值  
        BeanUtils.setProperty(p, "name", name);  
        BeanUtils.setProperty(p, "age", age);  
        BeanUtils.setProperty(p, "hight", hight);  
        //獲取屬性值  
        System.out.println(BeanUtils.getProperty(p, "name"));  
        System.out.println(BeanUtils.getProperty(p, "age"));  
        System.out.println(BeanUtils.getProperty(p, "hight"));  
    }

2)未注冊的屬性值的獲取和設置

//獲取未注冊的屬性,即非八種基本數據類型的引用類型  
//private Date birthday  
@Test   
public void test3() throws Exception{  
    Person p = new Person();  
    String name = "wangwu";  
    String age = "23";  
    String hight = "173.5";  
    String birthday = "1990-09-09";  
    ConvertUtils.register(new Converter() {  
        //注冊器Converter接口中方法的重寫  
        @Override  
        public Object convert(Class type, Object value) {  
            if(value == null)  
                return null;  
            if(!(value instanceof String))  
                throw new ConversionException("只支持String類型的轉換");  
            String str = (String) value;  
            if(value.equals(""))  
                return null;  
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");  
            try{  
                return sdf.parse(str);  
            }catch(ParseException e){  
                throw new RuntimeException(e);//異常鏈不能掉,這里必須寫上e  
            }  
        }},  
        Date.class);  
      
    //測試  
    BeanUtils.setProperty(p, "name", name);  
    BeanUtils.setProperty(p, "age", age);  
    BeanUtils.setProperty(p, "hight", hight);  
    BeanUtils.setProperty(p, "birthday", birthday);  
    System.out.println(BeanUtils.getProperty(p, "name"));  
    System.out.println(BeanUtils.getProperty(p, "age"));  
    System.out.println(BeanUtils.getProperty(p, "hight"));  
    System.out.println(BeanUtils.getProperty(p, "birthday"));  
}  
  
//使用已經寫好的注冊器DateLocaleConverter  
@Test   
public void test4() throws Exception{  
    Person p = new Person();  
    String name = "wangwu";  
    String age = "23";  
    String hight = "173.5";  
    String birthday = "1990-09-09";  
    //將日期注冊到BeanUtils上  
    ConvertUtils.register(new DateLocaleConverter(), Date.class);//提供的注冊器不健壯,因為傳入空字符串,就會報錯  
                                                                //所以,當沒有提供注冊器或需要加強注冊器的時候,可以自己寫  
    //測試  
    BeanUtils.setProperty(p, "name", name);  
    BeanUtils.setProperty(p, "age", age);  
    BeanUtils.setProperty(p, "hight", hight);  
    BeanUtils.setProperty(p, "birthday", birthday);  
    System.out.println(BeanUtils.getProperty(p, "name"));  
    System.out.println(BeanUtils.getProperty(p, "age"));  
    System.out.println(BeanUtils.getProperty(p, "hight"));  
    System.out.println(BeanUtils.getProperty(p, "birthday"));  
    Date date = p.getBirthday();  
    System.out.println(date.toLocaleString());  
}  

3)Map集合在BeanUtils中的應用:

//Map集合在BeanUtils中的應用  
@Test  
public void test5() throws Exception {  
    /* 
     * JDK 7.0新特性:  
     * Map map = {"name" : "zs", "age" : 22, "hight" : 176.5}; 
     */  
    //將數據存入集合  
    Map map = new TreeMap();  
    map.put("name", "zhangsan");  
    map.put("age", "20");  
    map.put("hight", "172.5");  
    map.put("birthday", "1999-10-02");  
      
    //注冊器  
    ConvertUtils.register(new DateLocaleConverter(), Date.class);  
    //獲取屬性  
    Person p = new Person();  
    BeanUtils.populate(p, map);  
      
    System.out.println(BeanUtils.getProperty(p, "name"));  
    System.out.println(BeanUtils.getProperty(p, "age"));  
    System.out.println(BeanUtils.getProperty(p, "hight"));  
    System.out.println(BeanUtils.getProperty(p, "birthday"));  
      
}  
  
//屬性鏈  
@Test  
public void test6() throws Exception {  
    Person p = new Person();  
    BeanUtils.setProperty(p, "birthday.time", "111212");  
    System.out.println(BeanUtils.getProperty(p, "birthday.time"));  
}  

二、注解 【JDK1.5】

1、概述:

1、注解相當于一種標記,在程序中加了注解就等于為程序打上了某種標記,沒加,則沒有某種標記。

2、以后,java編譯器、開發工具和其他應用程序就可以用反射來了解自己的類及各種元素上有無何種標記,有什么標記,就會做出相應的處理。

3、標記可以加在包、類、字段、方法、方法參數,以及局部變量上等等。

4、在java.lang包中提供了最基本的annotation,即注解。

5、格式:@注解類名()。如果有屬性,則在括號中加上屬性名(可省略)和屬性值。

2、本的注解:

1、@SuppressWarning(”deprecation”) 壓制警告
SupressWarning是告知編譯器或開發工具等提示指定的編譯器警告;
”deprecation”是告知具體的信息即方法已過時。

2、@Deprecated 提示成員等已經過時,不再推薦使用。
源代碼標記@Deprecated是在JDK1.5中作為內置的annotation引入的,用于表明類(class)、方法(method)、字段(field)已經不再推薦使用,并且在以后的JDK版本中可能將其刪除,編譯器在默認情況下檢測到有此標記的時候會提示警告信息。
例如:假定之前的某個類升級了,其中的某個方法已經過時了,不能夠將過時的方法刪除,因為可能會影響到調用此類的這個方法的某些程序,這是就可以通過在方法上加這個注解。

3、@Override 提示覆蓋(父類方法)
加上此注解,,可對自己類中的方法判斷是否是要覆蓋的父類的方法,典型的例子即在集合中覆蓋equals(Object obj)方法,其中的參數類型必須是Object,才能被覆蓋,若不是,加上此注解就會提示警告。

3、注釋的應用--->注解類:

1、定義格式:@interface 名稱{statement}

2、元注解(注解的注解)
一個注解有其生命周期(Retetion)和存放的位置(Taget),這就可以通過元注解說明。

1)Retetion:用于說明注解保留在哪個時期,加載定義的注解之上。
①一個注解的聲明周期包含:
java源程序--(javac)-->class文件--(類加載器)-->內存中的字節碼

第一、當再源程序上加了注解,javac將java源程序編譯為class文件,可能會把源程序中的一些注解去掉,進行相應的處理操作,當我們拿到源程序的時候,就看不到這些注解了。

第二、假設javac把這些注解留在了源程序中(或者說留在了class文件中),當運行此class文件的時候,用類加載器將class文件調入內存中,此時有轉換的過程,即把class文件中的注解是否保留下來也不一定。

注意:
class文件中不是字節碼,只有把class文件中的內部加載進內存,用類加載器加載處理后(進行完整的檢查等處理),最終得到的二進制內容才是字節碼。

②Reteton(枚舉類)取值:
Retetion.Policy.SOURSE:java源文件時期,如@Overried和@SuppressWarning
Retetion.Policy.CLASS: class文件時期(默認階段)
Retetion.Policy.RUNTIME:運行時期,如@Deprecated

2)Taget:用于說明注解存放在哪些成分上,默認值是任何元素
其值可設置為枚舉類ElementType類中的任何一個,包括:包、字段、方法、方法參數、構造器、類等值。
取值為:
PACKAGE(包聲明)
FIELD(字段聲明)
ANNOTATION_TYPE(注釋類型聲明)
CONSIRUCTOR(構造器聲明)
METHOD(方法聲明)
PARAMETER(參數聲明)
TYPE(類、接口(包含注釋類型)或枚舉聲明)
LOCAL_VARIABLE(局部變量聲明)

注意:
其中代表類的值是TYPE。因為class、enum、interface和@interface等都是屬于Type的。不可用CLASS表示。

3、通過反射查看其它類中的注釋:

過程:
第一、注解類:@interfaceA{}
第二、應用了“注釋類”的類:@Aclass B{}
第三、對“應用注釋類的類”進行反射操作的類:class{...},操作如下:

B.class.isAnnotionPresent(A.class);//判斷是否存在此注解類
A a = B.class.getAnnotation(a.class);//存在的話則得到這個注釋類的對象

示例:

@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.TYPE,ElementType.METHOD})  
public @interface ItcastAnnotation {}  
  
@ItcastAnnotation()  
public class AnnotionTest {  
    @SuppressWarnings("deprecation")//表示壓制警告的注解  
    @ItcastAnnotation()  
    public static void main(String[] args) {  
        System.runFinalizersOnExit(true);  
        //反射方式查看注解  
        //檢查類上是否有注解  
        if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){  
            //通過反射獲取到注解  
            ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);  
            System.out.println(annotation);  
        }  
    }

4、為注解增加基本屬性

1、屬性:
一個注解相當于一個胸牌,但僅通過胸牌還不足以區別帶胸牌的兩個人,這時就需要給胸牌增加一個屬性來區分,如顏色等。

2、定義格式:同接口中的方法一樣:String color();
定義缺省格式:Stringvalue() default ”ignal”;

3、應用:直接在注解的括號中添加自身的屬性,如:

@ItcastAnnotation(color=”red”)

這個和上面的@SuppressWarnings("deprecation")是一樣的,其中的"deprecation"就是屬性值

1)當只有一個屬性時,可直接傳入屬性值。如”red”
2)當含有其他屬性值的時候,如果那個屬性值是缺省的(default),也可以直接傳入這個屬性值。

5、為注解增加高級屬性

1、可以為注解增加的高級屬性的返回值類型有:
1)八種基本數據類型
2)String類型
3)Class類型
4)枚舉類型
5)注解類型
6)前五種類型的數組

2、數組類型的屬性:
定義:int[]arrayArr() default {1,2,3}; -->可不定義默認值
應用:@MyAnnotation(arrayArr={2,3,4}) -->可重新賦值

注:若數組屬性中只有一個元素(或重新賦值為一個元素),這時屬性值部分可省略大括號。

3、枚舉類型的屬性:
假設定義了一個枚舉類TraffLamp,它是EnumTest的內部類,其值是交通燈的三色。
定義:EnumTest.TrafficLamplamp();
應用:@MyAnnotation(lamp=EnumTestTrafficLamp.GREEN)

4、注解類型的屬性:
假定有個注解類:MetaAnnotation,其中定義了一個屬性:String value()
定義:MetaAnnotationannotation() default @MetaAnnotation(”xxx”);
應用:@MyAnnotation(annotation=@MetaAnnotation(”yyy”)) -->可重新賦值

可認為上面的@MetaAnnotation是MyAnnotation類的一個實例對象,同樣可以認為上面的@MetaAnnotation是MetaAnnotation類的一個實例對象,調用:

MetaAnnotation ma =MyAnnotation.annotation();
System.out.println(ma.value());

5、Class類型的屬性:
定義:Class cls();
應用:@MyAnnotation(cls=ItcastAnnotion.class)

注:這里的.class必須是已定義的類,或是已有的字節碼對象

7、基本數據類型的屬性(以int為例):
定義:int val()default 3; -->可不定義默認值
應用:@MyAnnotation(val=7) --> 可重新賦值

8、注解的詳細語法可通過查看java語言規范了解即javaLanguage Specification

示例:

//自定義注解類  
package cn.itcast.text2;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
import cn.itcast.text1.EnumText;  
//將定義的注解的生命周期設置在運行時期  
@Retention(RetentionPolicy.RUNTIME)  
//定義注解的放置位置  
@Target({ElementType.TYPE,ElementType.METHOD})  
//自定義注解  
public @interface ItcastAnnotation {  
    //定義屬性  
    String str();  
    int val() default 1;  
    int[] arr() default {2,3,4};  
    Class cls() default AnnotionTest.class;  
    EnumText.TrafficLamp lamp() default EnumText.TrafficLamp.YELLOW;  
    MetaAnnotation annotation() default @MetaAnnotation("sss");  
}  
  
//測試注解類,用反射查看其屬性  
package cn.itcast.text2;  
import cn.itcast.text1.EnumText;  
@ItcastAnnotation(annotation=@MetaAnnotation("anntation"),  
                Lamp=EnumText.TrafficLamp.RED,  
                arr=7,val=5,str="String",  
                cls=ItcastAnnotation.class)  
public class AnnotionTest {  
    @SuppressWarnings("deprecation")//表示壓制警告的注解  
    @ItcastAnnotation(str = "yyy")//有缺省值可不用寫缺省部分  
    public static void main(String[] args) {  
        //反射方式查看注解  
        //檢查類上是否有注解  
        if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){  
            //通過反射獲取到注解  
            ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);  
            //打印查看屬性值  
            System.out.println(annotation);  
            System.out.println(annotation.str());  
            System.out.println(annotation.val());  
            System.out.println(annotation.arr().length);  
            System.out.println(annotation.cls().getName());  
            System.out.println(annotation.lamp().nextLamp());  
            System.out.println(annotation.annotation().value());  
        }  
    }  
}  
  
//定義枚舉類,交通燈  
package cn.itcast.text1;  
public class EnumText {  
    public static void main(String[] args) {}  
    //定義交通燈  
    public enum TrafficLamp{  
        //定義3個元素,即此類的子類,覆寫抽象方法  
        RED(30){  
            @Override  
            public TrafficLamp nextLamp() {return GREEN;}},  
        GREEN(45){  
            @Override  
            public TrafficLamp nextLamp() {return YELLOW;}},  
        YELLOW(5) {  
            @Override  
            public TrafficLamp nextLamp() {return RED;}};  
        private int time;  
        //構造方法  
        private TrafficLamp(int time){this.time = time;}  
        //抽象方法,轉為下個燈  
        public abstract TrafficLamp nextLamp();  
    }  
}  

三、類加載器

1、概述:

1、定義:簡單說,類加載器就是加載類的工具。
當出現一個類,用到此類的時候,Java虛擬機首先將類字節碼加載進內存,通常字節碼的原始信息放在硬盤上的classpath指定的目錄下。

2、類加載器作用:將.class文件中的內容加載進內存進行處理,處理完后的結果就是字節碼。

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

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

4、Java虛擬機中的所有類加載器采用子父關系的樹形結構進行組織,在實例化每個類加載器對象或默認采用系統類加載器作為其父級類加載器。

示意圖

示例:

package cn.itcast.text2;  
import java.util.Date;  
public class ClassLoadTest{  
    public static void main(String[] args) throws Exception{  
        System.out.println(  
                ClassLoadTest.class.getClassLoader().  
                getClass().getName());//為AppClassLoader  
        System.out.println(  
                System.class.getClassLoader());//為null  
    }  
}

2、類加載器的委托機制:

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

1)首先,當前線程的類加載器去加載線程中的第一個類。
2)若A引用類B(繼承或者使用了B),Java虛擬機將使用加載類的類加載器來加載類B。
3)還可直接調用ClassLoader的LoaderClass()方法,來制定某個類加載器去加載某個類。

2、加載器的委托機制:每個類加載器加載類時,又先委托給上級類加載器。
每個ClassLoader本身只能分別加載特定位置和目錄中的類,但他們可以委托其他類的加載器去加載,這就是類加載器的委托模式,類加載器一級級委托到BootStrap類加載器,當BootStrap在指定目錄中沒有找到要加載的類時,無法加載當前所要加載的類,就會一級級返回子孫類加載器,進行真正的加載,每級都會先到自己相應指定的目錄中去找,有沒有當前的類;直到退回到最初的類裝載器的發起者時,如果它自身還未找到,未完成類的加載,那就報告ClassNoFoundException的異常。

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

3、委托機制的優點:可以集中管理,不會產生多字節碼重復的現象。
補充:
面試題:可不可以自己寫個類為:java.lang.System呢?

回答:
第一、通常是不可以的,由于類加載器的委托機制,會先將System這個類一級級委托給最頂級的BootStrap,由于BootStrap在其指定的目錄中加載的是rt.jar中的類,且其中有System這個類,那么就會直接加載自己目錄中的,也就是Java已經定義好的System這個類,而不會加載自定義的這個System。
第二、但是還是有辦法加載這個自定義的System類的,此時就不能交給上級加載了,需要用自定義的類加載器加載,這就需要有特殊的寫法才能去加載這個自定義的System類的。

3、自定義類加載器

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

2、覆寫findClass(String name)方法的原因:
1)是要保留loadClass()方法中的流程,因為loadClass()中調用了findClass(String name)這個方法,此方法返回的就是去尋找父級的類加載器。

2)在loadClass()內部是會先委托給父級,當父級找到后就會調用findClass(String name)方法,而找不到時就會用子級的類加載器,再找不到就報異常了,所以只需要覆寫findClass方法,那么就具有了實現用自定義的類加載器加載類的目的。

流程:
父級-->loadClass-->findClass-->得到Class文件后轉化成字節碼-->defind()。

3、編程步驟:
1)編寫一個對文件內容進行簡單加盟的程序
2)編寫好了一個自己的類加載器,可實現對加密過來的類進行裝載和解密。
3)編寫一個程序,調用類加載器加載類,在源程序中不能用該類名定義引用變量,因為編譯器無法識別這個類,程序中除了可使用ClassLoader的load方法外,還能使用放置線程的上線文類加載器加載或系統類加載器,然后在使用forName得到字節碼文件。

示例:

package cn.itcast.text2;  
import java.util.Date;  
  
public class ClassLoaderAttachment extends Date {  
    //對此類進行加密  
        public String toString(){  
            return "hello world";  
        }  
        public static void main(String [] args){  
              
        }  
}  
  
//自定義類加載器  
package cn.itcast.text2;  
  
import java.io.*;  
//繼承抽象類ClassLoader  
public class MyClassLoader  extends ClassLoader {  
    public static void main(String[] args) throws Exception {  
        //傳入兩個參數,源和目標  
        String scrPath = args[0];  
        String destDir = args[1];  
        //將數據讀取到輸入流中,并寫入到輸出流中  
        FileInputStream fis = new FileInputStream(scrPath);  
        String destFileName =   
                scrPath.substring(scrPath.lastIndexOf('\\')+1);  
        String destPath = destDir + "\\" + destFileName;  
        FileOutputStream fos = new FileOutputStream(destPath);  
        //加密數據  
        cypher(fis,fos);  
        fis.close();  
        fos.close();  
    }  
    //定義加密數據的方法  
    private static void cypher(InputStream ips,OutputStream ops)throws Exception{  
        int b = 0;  
        while((b=ips.read())!=-1){  
            ops.write(b ^ 0xff);  
        }  
    }  
    //定義全局變量  
    private String classDir;  
    @Override//覆寫findClass方法,自定義類加載器  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        String classFileName = classDir + "\\" + name + ".class";   
        try {  
            //將要加載的文件讀取到流中,并寫入字節流中  
            FileInputStream fis = new FileInputStream(classFileName);  
            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(){}  
    public MyClassLoader(String classDir){  
        this.classDir = classDir;  
    }  
}

類加載器與內省結合的小程序

下面是內省和類加載器綜合使用的一個小程序

package cn.itcast.text1;  
  
import java.beans.IntrospectionException;  
import java.beans.PropertyDescriptor;  
import java.io.IOException;  
import java.io.InputStream;  
import java.lang.reflect.InvocationTargetException;  
import java.lang.reflect.Method;  
import java.util.Properties;  
  
public class LoaderToJavaBean {  
        public static void main(String [] args)throws Exception {  
                //創建ReflectPoint對象,并賦值  
                ReflectPoint rf = new ReflectPoint(3,7);  
                //通過get方法獲取屬性名稱  
                String propertyName = getPropertyName();  
                //通過get方法獲取屬性值  
                Object retVal = getProperty(rf, propertyName);  
                System.out.println(retVal);  
                //通過set方法設置屬性值  
                Object value = 7;  
                setProperty(rf, propertyName, value);  
                System.out.println(rf.getX());  
        }  
        //設置屬性值的方法  
        private static void setProperty(Object rf, String propertyName,  
                        Object value) throws IntrospectionException,  
                        IllegalAccessException, InvocationTargetException {  
                //創建屬性描述符對象,將屬性名稱和加載文件等信息寫入其中  
                PropertyDescriptor pd =  
                                new PropertyDescriptor(propertyName,rf.getClass());  
                //通過反射的方法類Method,獲取屬性所對應的set方法  
                Method methodSetX = pd.getWriteMethod();  
                methodSetX.invoke(rf, value);  
        }  
        //獲取屬性值的方法  
        private static Object getProperty(Object rf, String propertyName)  
                        throws IntrospectionException, IllegalAccessException,  
                        InvocationTargetException {  
                //創建屬性描述符對象,獲取屬性所對應的名稱和加載文件等信息  
                PropertyDescriptor pd =  
                                new PropertyDescriptor(propertyName,rf.getClass());  
                //通過反射的方法類Method,獲取屬性所對應的get方法  
                Method methodGetX = pd.getReadMethod();  
                Object retVal = methodGetX.invoke(rf);  
                return retVal;  
        }  
        //獲取屬性名稱的方法  
        private static String getPropertyName() throws IOException {  
                //創建讀取流對象,將文件中的信息讀取到流中  
                InputStream in =  
                        LoaderToJavaBean.class.getResourceAsStream("config.propert");//這里可以用任何你程序中存在的類加載  
                //創建Propertie對象,將流中信息加載進內存  
                Properties props = new Properties();  
                props.load(in);  
                in.close();  
                //獲取文件中的屬性名稱,并作為返回值返回  
                String propertyName = props.getProperty("propertyName");  
                return propertyName;  
        }  
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容