黑馬程序員 高新技術--->內省、註解與類加載器

-----------android培訓java培訓、java學習型技術博客、期待與您交流! ------------


第一節  由內省IntroSpector引出JavaBean

一、概述:

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

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

二、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;
	}
}

三、對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;
			}
		}
…

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

四、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"));
	}


5、補充:

1)BeanUtils是以字符串的形式進行操作的

2)PropertyUtils是以傳入值本身的類型進行操作的。

//PropertyUtils可直接解析爲指定類型,而BeanUtils只能指定字符串的類型
	@Test
	public void test7() throws Exception {
		Person p = new Person();
		System.out.println("-----BeanUtiles-------");
		BeanUtils.setProperty(p, "age", "22");//字符串形式
		System.out.println(BeanUtils.getProperty(p, "age"));
		System.out.println(BeanUtils.getProperty(p, "age").getClass().getName());
		
		System.out.println("-----PropertyUtiles-------");
		PropertyUtils.setProperty(p, "age", 22);//Integer形式
		System.out.println(PropertyUtils.getProperty(p, "age"));
		System.out.println(PropertyUtils.getProperty(p, "age").getClass().getName());
	}


第二節  註解--> JDK1.5

一、概述:

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

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

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

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

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

二、java中三種最基本的註解:

1、@SuppressWarning(”deprecation”)--->壓制警告

SupressWarning是告知編譯器或開發工具等提示指定的編譯器警告;

”deprecation”是告知具體的信息即方法已過時。

2、@Deprecated--->提示成員等已經過時,不再推薦使用。

源代碼標記@Deprecated是在JDK1.5中作爲內置的annotation引入的,用於表明類(class)、方法(method)、字段(field)已經不再推薦使用,並且在以後的JDK版本中可能將其刪除,編譯器在默認情況下檢測到有此標記的時候會提示警告信息。

例如:假定之前的某個類升級了,其中的某個方法已經過時了,不能夠將過時的方法刪除,因爲可能會影響到調用此類的這個方法的某些程序,這是就可以通過在方法上加這個註解。

3、@Override--->提示覆蓋(父類方法)

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

三、註釋的應用--->註解類:

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);
		}
	}
	

四、爲註解增加基本屬性

1、屬性:

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

2、定義格式:同接口中的方法一樣:String color();

定義缺省格式:Stringvalue() default ”ignal”;

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

@ItcastAnnotation(color=”red”)

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

1)當只有一個屬性時,可直接傳入屬性值。如”red”

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

五、爲註解增加高級屬性

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、定義:簡單說,類加載器就是加載類的工具。

當出現一個類,用到此類的時候,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
	}
}

二、類加載器的委託機制:

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類的。

 

三、自定義類加載器

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;
	}
}



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