黑馬程序員 高新技術--->代理

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


第一節  概述

一、概念:

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

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

3、簡單示例:編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的代碼,如:

目標類:                              代理類:

class X{                               Xproxy{

  void sayHello(){                       void sayHello(){

syso:Hello;                            startTime

}                                           X. sayHello();

}                                      endTime;}}

一般用接口來引用其子類,如:Collectioncoll = new ArrayList();

4、代理類的優點:

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

示例:

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

二、AOP

1、簡述:AOP(Aspect Oriented Program)即面向方面的編程。

2、示意圖:

系統中存在着交叉業務,一個交叉業務就是要切入到系統中的一個方面,如圖:



安全、事務、日誌等功能要貫穿於好多個模塊中,所以他們就是交叉業務。

3、用具體的程序代碼描述交叉業務:

1)代碼實現

2)交叉業務的編程問題即面向方面的編程(AOP),AOP的目標就是使交叉業務模塊化,可以採用將切面代理移動到原始方法的周圍,這與直接在方法中編寫切面代理的過程效果是一樣的,如圖:


因此使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。

 

第二節   動態代理技術

一、概述:

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

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

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

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

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

1)在調用目標方法之前

2)在調用目標方法之後

3)在調用目標方法前後

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

二、分析JVM動態生成的類

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

1)用反射獲得構造方法

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

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

示例:

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

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

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

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參數。

示例:

//封裝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);
}

第三節  實現類似spring的可配置的AOP框架

一、工廠類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進行測試。

 

示例:

//創建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());
	}
}
此時,如果傳入的是java.util.ArrayList的話,返回的結果就是這個對象的名字
否則,如果是特殊的bean對象,那麼返回的就是$Proxy這個名字,是一個ProxyFactoryBean對象。

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