黑馬程序員—Java提高3(註解,類加載器,代理)

---------------------- JavaEE+Android、Java培訓、期待與您交流! ----------------------


註解

註解相當於一種標記,在程序中加了註解就等於爲程序打上了某種標記,沒加,則沒有某種標記。以後,java編譯器、開發工具和其他應用程序就可以用反射來了解自己的類及各種元素上有無何種標記,有什麼標記,就會做出相應的處理。標記可以加在包、類、字段、方法、方法參數,以及局部變量上等等。在java.lang包中提供了最基本的annotation,即註解。

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

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

類加載器

簡單說,類加載器就是加載類的工具。

當出現一個類,用到此類的時候,Java虛擬機首先將類字節碼加載進內存,通常字節碼的原始信息放在硬盤上的classpath指定的目錄下。

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

默認類加載器:

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

2)BootStrap--頂級類加載器:

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

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


類加載器的委託機制

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得到字節碼文件。

<span style="font-family:Arial;">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;
	}
}
</span>


代理

生活中的代理:就是常說的代理商,從廠商將商品賣給消費者,消費者不用很麻煩的到廠商在購買了。

程序中的代理:要爲已經存在的多個具有相同接口的目標類的各個方法增加一些系統功能,如異常處理、日誌、計算方法的運行時間、事物管理等等。

代理類的優點:

如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類還是代理類。這樣以後很容易切換,如果想要日誌功能時,就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想換掉系統功能也很容易。

<span style="font-family:Arial;">import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
	public static void main(String[] args) throws Exception{
		//獲取代理類Proxy的Class對象,傳入的是類加載器和相應的字節碼對象
		Class clazzProxy1 = Proxy.getProxyClass(
				Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy1.getName());//$Proxy0
		
		System.out.println("---begin constructor list------");
		//獲取代理類的構造方法,可能含有多個,得到數組
		Constructor[] constructors = clazzProxy1.getConstructors();
		//遍歷數組,獲取每個構造方法
		for(Constructor constructor : constructors){
			//先得到構造方法的名字,並裝入字符串容器中
			String name = constructor.getName();
			StringBuilder sBul = new StringBuilder(name);
			sBul.append('(');
			//獲取構造方法中的參數類型,並遍歷
			Class[] clazzParams = constructor.getParameterTypes();
			for(Class clazzParam : clazzParams){
				sBul.append(clazzParam.getName()).append(',');
			}
			//將最後一個逗號去除
			if(clazzParams != null && clazzParams.length!=0)
				sBul.deleteCharAt(sBul.length()-1);
			sBul.append(')');
			System.out.println(sBul.toString());
		}
		
		System.out.println("---begin method list------");
		//獲取代理類的方法,存入數組
		Method[] methods = clazzProxy1.getMethods();
		//遍歷數組,獲取每個方法
		for(Method method : methods){
			//先得到方法的名字,並裝入字符串容器中
			String name = method.getName();
			StringBuilder sBul = new StringBuilder(name);
			sBul.append('(');
			//獲取方法中的參數類型,並遍歷
			Class[] clazzParams = method.getParameterTypes();
			for(Class clazzParam : clazzParams){
				sBul.append(clazzParam.getName()).append(',');
			}
			//將最後一個逗號去除
			if(clazzParams!=null && clazzParams.length!=0)
				sBul.deleteCharAt(sBul.length()-1);
			sBul.append(')');
			System.out.println(sBul.toString());
		}
}
</span>

AOP

AOP(Aspect Oriented Program)即面向方面的編程。


動態代理技術

一、概述:

1、要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,這時就不能採用靜態代理方式,需用動態代理技術。

2、動態代理類:JVM可在運行時,動態生成類的字節碼,這種動態(不是代理,只是拿出來作爲代理類)生成的類往往被用作代理類,即動態代理類。

注:JVM生成的動態類必須實現一或多個接口,所以JVM生成的動態代理類只能用作具有相同接口的目標類代理。

3、CGLIB庫可以動態生成一個類的子類,一個類的子類也可以作爲該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理,那麼可以使用CGLIB庫。

4、代理類各個方法通常除了調用目標相應方法和對外返回目標返回的結果外,還可以再代理方法中的如下位置上加上系統功能代碼:

1)在調用目標方法之前

2)在調用目標方法之後

3)在調用目標方法前後

4)在處理目標方法異常的catch塊中。

二、分析JVM動態生成的類

1、創建動態類的實例對象:

1)用反射獲得構造方法

2)編寫一個最簡單的InvocationHandler的類

3)調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去。

示例:

第一、打印創建的對象和調用對象的無返回值的方法和getClass方法,演示調用其他沒有返回值的方法報告的異常

第二、將創建的動態類的實例對象的代理改寫成爲匿名內部類的形式編寫。

<span style="font-family:Arial;">package cn.itcast.test3;
import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
	public static void main(String[] args) throws Exception{
//創建動態代理類的三種方式
		//方式一:通過接口的子類創建對象
		Collection proxy1 = (Collection)
				constructor.newInstance(new MyInvocationHandler());
		System.out.println(proxy1);//null
		System.out.println(proxy1.toString());//null
		proxy1.clear();//無異常
		//proxy1.size();//異常		
		//方式二:匿名內部類
		Collection proxy2 = (Collection)
				constructor.newInstance(new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						return null;
					}
				});
		
		//方式三:
		//通過代理類的newProxyInstance方法直接創建對象
		Collection proxy3 = (Collection)Proxy.newProxyInstance(
			//定義代理類的類加載器
			Collection.class.getClassLoader(),
			//代理類要實現的接口列表
			new Class[]{Collection.class},
			//指派方法調用的調用處理程序
			new InvocationHandler() {
				//創建集合,制定一個目標
				ArrayList target = new ArrayList();
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					//測試程序運行時間
					long beginTime = System.currentTimeMillis();
					//調用目標方法,將其從return抽出來,加入代理所需的代碼
					Object retVal = method.invoke(target, args);
					long endTime = System.currentTimeMillis();
					//測試
					System.out.println(method.getName() + 
							" run time of " + 
							(endTime - beginTime));
					return retVal;
				}
			}
			);
		//通過代理類調用目標方法,每調用一個目標的方法就會執行代理類的方法
		//當調用一次add方法時,就會找一次InvocationHandler這個參數的invoke方法
		proxy3.add("sdfd");
		proxy3.add("shrt");
		proxy3.add("rtbv");
		System.out.println(proxy3.size());
		System.out.println(proxy3.getClass().getName());
	}
}
</span>

2、讓JVM創建動態類需要提供的信息:

1)生成類中的哪些方法,通過讓其實現哪些接口的方式進行告知。

2)產生的類字節碼必須有一個關聯的類加載器對象

3)生成的類中的方法的代碼是怎麼樣的,也得由我們自己提供,把我們的代碼寫在一個約定好的子接口對象的方法中,把對象傳給它,它調用我們的方法,即相當於插入了我們自己的代碼。提供執行代碼的對象就是InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的,在上面的InvocationHandler對象的invoke方法中,加一點代碼就可以看到這些代碼被調用運行了。

三、分析動態生成的類的內部代碼

1、構造方法接受一個InvocationHandler對象,接受此對象的用處:

接受一個handler參數是爲了記錄它,以便在之後的程序中運用它。

2、實現Collection接口的動態類中的各個方法的代碼的解析:

1)InvocationHandler接口中定義的invoke方法接受三個參數的含義:

第一、Client(客戶端)程序調用objProxy.add(“avc”)方法時,涉及到了三個參數,分別爲:objProxy對象,add方法,”avc”參數。代碼如下:

class Proxy${

  add(Object obj){

      return handler.invoke(Object proxy, Method method, Object[] args);

   }

}

第二、其中的Objectproxy 即爲objProxy對象,Method method對應add方法,Object[] args就是”avc”參數。在使用newProxyInstance的方式創建代理對象實現時,當前正在調用代理對象(Object proxy),調用當前對象的哪個方法(Method method),調用此對象方法時傳入的參數(Object[] args)。

3、調用代理涉及到三個因素:代理對象,代理對象的哪個方法,以及此方法接受的參數。要執行目標對象,只需要將代理對象作爲目標對象即可。

4、對於上面代碼中的Object retVal = method.invoke(target,args)的分析:

目標對象target執行完返回一個值爲retVal,接着將值作爲結果return回去,則代理方法就會收到一個返回值。其中還可以定義一個過濾器,對參數args(即當前對象的方法傳入的參數)進行過濾(修改)。

5、對於上面代碼中的proxy3.add(“sdfd”)的分析:

1)代理對象調用add方法,傳遞了sdfd參數。

2)add方法內部會找到InvocationHandler中的invoke方法,將代理對象proxy傳進去,把add方法傳入,將“sdfd”參數傳入代理對象中的handler參數,返回了一個結果,就是給了add方法,add方法繼續向外返回給調用的對象proxy3,即最終結果。

其中的handler的invoke方法返回又來自於目標target返回值,從而將此返回值返給Object invoke()方法,即作爲handler參數位置上的值返回給add方法。add方法作爲最後的結果返回。


四、問題:

1、在上面的方式一的代碼中,調用無返回值的方法返回的是null,而調用有返回值方法的時候會報NullPointException異常的原因:

在proxy1.size()方法中,size()會調用Object invoke()方法,而對其覆寫時的返回值是null,而size()本身會返回int類型,兩者返回的類型不相等,所以就會報錯。

而對於proxy3.size()不報錯,是因爲size()返回值與代理對象中handler參數返回值是一致的,當前目標返回什麼,代理就返回什麼,所以不會報錯。

注意:目標返回值和代理返回值必須是同一類型。

2、爲何動態類的實例對象的getClass()方法返回了正確結果,而沒調用invoke方法:

因爲代理類從Object上繼承了許多方法,其中只對三個方法(hashCode、equals和toString)進行開發,委託給handler去自行處理,對於它身上其他方法不會交給代理類去實現,所以對於getClass()方法,還是由Object本身實現的。即proxy3.getClass(),該是什麼結果還是什麼結果,並不會交給invoke方法處理。

五、總結分析動態代理類的統計原理和結構:

1、怎樣將目標傳進去:

1)直接在InvocationHandler實現類中創建目標類的實例對象,可看運行效果和加入日誌代碼,但是毫無意義。

2)爲InvocationHandler實現類注入目標的實例對象,不能採用匿名內部類的形式了。

3)讓匿名內部類的InvocationHandler實現類訪問外面的方法中的目標類實例對象的final類型的引用變量。

2、動態代理的工作原理:

1)Client(客戶端)調用代理,代理的構造方法接受一個InvocationHandler,client調用代理的各個方法,代理的各個方法請求轉發給剛纔通過構造方法傳入的handler對象,又把各請求分發給目標的相應的方法。就是將handler封裝起來,其中this引用了當前的放(發來什麼請求就接受哪個方法)。

2)將創建代理的過程改爲一種更優雅的方式,eclipse重構出一個getProxy方法綁定接受目標,同時返回代理對象,讓調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API。

在這裏將InvocationHandler加入到Proxy的構造方法中,因此,在創建出來的對象,就會存有構造方法中InvocationHandler的一些功能和信息,因爲我們把想要運行的代碼封裝在InvocationHandler對象,把它傳入到構造函數中,那麼就實現了代理對象每次調用目標方法(因爲實現了同一接口)時,都會調用我們加入到InvocationHandler對象中的代碼。這就保證了每次調用代理時,可以在目標上加入我們自己加入的功能。

3、把系統功能代理模塊化,即切面代碼也改爲通過參數形式提供,怎麼把要執行的系統功能代碼以參數的形式提供:

1)把要執行的代碼裝到一個對象的某個方法中,然後把此對象作爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外接提供的代碼。

2)爲bind方法增加一個Advice參數。

<span style="font-family:Arial;">//封裝getProxy方法
package cn.itcast.test3;
import java.lang.reflect.*;
import java.util.*;
public class MyProxy {
	public static void main(String[] args)throws Exception {
		//創建目標對象,並進行操作測試
		final ArrayList target = new ArrayList();
		Collection proxy = (Collection)getProxy(target,new MyAdvice());
		proxy.add("sdf");
		proxy.add("wgcd");
		proxy.add("hgwe");
		System.out.println(proxy.size());
		
	}
	//作爲一個通用的方法,就使用Object
	//傳入一個目標,並傳入一個接口,此接口作爲通信的契約,才能調用額外的方法
	private static Object getProxy(final Object target,final Advice advice) {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				//這裏的接口要和target實現相同的接口
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						//通過契約,使用其方法--before和after方法
						advice.beforeMethod(method);
						Object value = method.invoke(target, args);
						advice.afterMethod(method);
						return value;
					}
				}
				);
		return proxy;
	}
}
//創建實現Advice接口的子類
package cn.itcast.test3;
import java.lang.reflect.Method;
//實現Advice接口中方法的具體內容
public class MyAdvice implements Advice {

	long beginTime = 0;
	public void beforeMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("從這裏開始");
		beginTime = System.currentTimeMillis(); 
	}
	public void afterMethod(Method method) {
		// TODO Auto-generated method stub
		long endTime = System.currentTimeMillis();
		System.out.println("從這裏結束");
		System.out.println(method.getName() + " run time of " + (endTime-beginTime));
	}
}
//創建接口Advice
import java.lang.reflect.Method;
/*接口中需要實現四個方法
 * 調用目標方法之前
 * 調用目標方法之後
 * 調用目標方法前後
 * 在處理目標方法異常的catch塊中
 */
//這裏只列出兩個作爲示例
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
</span>


工廠類BeanFactory

1、工廠類BeanFactory負責創建目標類或代理類的實例對象,並通過配置文件實現切換。

2、getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則返回該類示例對象的getProxy方法返回的對象。

3、BeanFactory的構造方法接收代表配置文件的輸入流對象的配置文件格式如下:

#xxx=java.util.ArrayList

xxx=cn.itcast.test3.aopframework.ProxyFactoryBean

xxx.advice=cn.itcast.test3.MyAdvice

xxx.target=java.util. ArrayList

注意:其中的#代表註釋當前行。

4、ProxyFactoryBean充當封裝成動態的工廠,需爲工廠提供的配置參數信息包括:

目標(target)

通告(advice)

5、BeanFactory和ProxyFactoryBean:

1)BeanFactory是一個純粹的bean工程,就是創建bean即相應的對象的工廠。

2)ProxyfactoryBean是BeanFactory中的一個特殊的Bean,是創建代理的工廠。


實現類似spring的可配置的AOP框架的思路

1、創建BeanFactory類:

1)構造方法:接受一個配置文件,通過Properties對象加載InputStream流對象獲得。

2)創建getBean(String name)方法,接收Bean的名字,從上面加載後的對象獲得。

3)通過其字節碼對象創建實例對象bean。

4)判斷bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要創建代理類,並設置目標和通告,分別得到各自的實例對象,並返回代理類實例對象。如果不是在返回普通類的實例對象。

2、創建ProxyFactoryBean(接口),此處用類做測試,其中有一個getProxy方法,用於獲得代理類對象。

3、對配置文件進行配置,如上面配置一樣。

4、作一個測試類:AopFrameworkTest進行測試。

<span style="font-family:Arial;">//創建BeanFactory類
package cn.itcast.test3.aopframework;
import java.io.*;
import java.util.Properties;
import cn.itcast.test3.Advice;
public class BeanFactory {
	Properties prop = new Properties();
	//創建對象時需要傳入一個配置文件中的數據,所以需要在構造方法中接受一個參數
	public BeanFactory(InputStream ips) {
		try {
			//將配置文件加載進來
			prop.load(ips);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//創建getBean方法,通過配置文件中的名字獲取bean對象
	public Object getBean(String name){
		//從配置文件中讀取類名
		String className = prop.getProperty(name);
		Object bean = null;
		try {
			//由類的字節碼獲取對象
			Class clazz = Class.forName(className);
			bean = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		} 
		//判斷bean是特殊的bean即ProxyFactoryBean還是普通的bean
		if(bean instanceof ProxyFactoryBean){
			Object proxy = null;
			try {
				//是ProxyFactoryBean的話,強轉,並獲取目標和通告
				ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
				//獲取advice和target
				Advice advice = (Advice)Class.forName(prop.getProperty(name + ".advice")).newInstance();
				Object target = Class.forName(prop.getProperty(name + ".target")).newInstance();
				//設置目標和通告
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTarget(target);
				//通過類ProxyFactoryBean(開發中是作爲接口存在)中獲得proxy對象
				proxy = proxyFactoryBean.getProxy();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			//是ProxyFactoryBean的話,返回proxy對象
			return proxy;
		}
		//否則返回普通bean對象
		return bean;
	}
}

//創建ProxyFactoryBean類
package cn.itcast.test3.aopframework;
import java.lang.reflect.*;
import cn.itcast.test3.Advice;
public class ProxyFactoryBean {
	private Object target;
	private Advice advice;
	public Object getTarget() {
		return target;
	}
	public void setTarget(Object target) {
		this.target = target;
	}
	public Advice getAdvice() {
		return advice;
	}
	public void setAdvice(Advice advice) {
		this.advice = advice;
	}
	public Object getProxy() {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				//這裏的接口要和target實現相同的接口
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						//通過契約,使用其方法--before和after方法
						advice.beforeMethod(method);
						Object value = method.invoke(target, args);
						advice.afterMethod(method);
						return value;
					}
				}
				);
		return proxy;
	}
}
//創建測試類AopFrameworkTest
package cn.itcast.test3.aopframework;
import java.io.InputStream;
public class AopFramewrorkTest {
	public static void main(String[] args)throws Exception {
		//讀取配置文件的數據
		InputStream ips = 
				AopFramewrorkTest.class.getResourceAsStream("config.property");
		//獲取bean對象
		Object bean = new BeanFactory(ips).getBean("xxx");
		System.out.println(bean.getClass().getName());
	}
}
</span>






---------------------- JavaEE+Android、Java培訓、期待與您交流! ----------------------

詳細請查看: http://edu.csdn.net

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