1. 程式人生 > >黑馬程式設計師--高新技術之動態代理

黑馬程式設計師--高新技術之動態代理

                                ------- android培訓java培訓、期待與您交流! ----------

一、關於動態代理的一些基本概念
    在程式設計中,很多時候我們要為已存在的多個具有相同介面的目標類的各個
    方法增加一些系統功能。例如,異常處理、日誌、計算方法的執行時間、事物管理、等等,
    針對這些問題,我們可以編寫一個與目標類具有相同介面的代理類,代理類的每個方法呼叫
    目標類的相同方法,並在呼叫方法時加上系統的程式碼。
    代理模式也是常用的一種java設計模式,代理類的物件本身並不是真正實現服務
    而是通過呼叫委託類的物件的相關方法,來提供服務。
二、代理類的實現
     靜態代理:自己手動建立,編寫原始碼,特點:在程式執行前,代理類的.class檔案就已經存在了


     動態代理:因為JVM可以在執行期動態生成出類的位元組碼,所以可以利用jdk的api動態建立代理類
特點:在執行時利用反射機制動態建立代理類。
三、程式碼實現

靜態代理:(在這裡我模擬的是向資料庫中新增使用者的情形,就只能想到這樣的例子了)

package cn.yusheng.proxy;  
	/*
	 * 定義類實現介面,這是要被代理的類
	 */
	public class UserImpl implements User {
		//新增使用者到資料庫
	  public void addUser(){
		  System.out.println("新增使用者的方法---"); 
	  }
	} 


	package cn.yusheng.proxy;  
	/*
	 * 定義代理類,實現與被代理類相同的介面,在此處模擬的是
	 * 向資料庫中新增使用者時進行的事物
	 */
	public class UserProxy implements User{
		//定義一個被代理類的一個例項
		UserImpl userImpl = new UserImpl();
		//新增使用者到資料庫
	  public void addUser(){
		  System.out.println("事物處理前---"); 
		  //被代理類應該被執行的方法
		  userImpl.addUser();
		  System.out.println("事物處理之後");
	  }
	} 


以上就是靜態代理的簡單實現,但是要為系統中的各種介面的類增加代理功能,
那將需要太多的代理類,全部採用靜態代理方式,正如張老師所說的將是一件非常麻煩的事情。
所以還是利用動態建立代理類的方式比較好。
ps:
   代理類的各個方法中通常除了要呼叫目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置加上系統功能程式碼:
1.在呼叫目標方法之前
2.在呼叫目標方法之後
3.在呼叫目標方法前後
4.在處理目標方法異常的catch塊中

動態代理:

     JDK中對動態代理中提供了InvocationHandler介面和Proxy類,
     兩個非常重要的方法:
     public interface InvocationHandler { 
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
   } 
   引數說明:
   Object proxy:指被代理的物件。
   Method method:要呼叫的方法
   Object[] args[]:方法呼叫時所需要的引數
   這個方法存在於InvocationHandler介面中,在實際運用中經常採用此介面,利用匿名內部類的
   方法實現。
      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, 
InvocationHandler h) throws IllegalArgumentException 


這個方法存在於Proxy類中,用來動態建立代理類的關鍵方法。
引數介紹:
ClassLoader loder:類載入器,一般情況下是目標類的(要被代理的類)類載入器
Class<?>[] interfaces:目標類的全部介面
InvocationHandler h:得到InvocationHandler介面的子類例項 (最好用匿名類的方式)




ps:在此處介紹下java中想要向一個方法中新增一段程式碼時的方法:
在js,我們可以用eval(),小括號裡面就是要新增的要執行的js程式碼,
但在java中卻沒有這樣的方法,但是我們可以採用封裝程式碼到物件的形式。
這裡舉一個簡單的例子
package cn.yusheng.proxy; 
	public class TestHello{
		public void printHello(Hello hello){
			hello.print();
		}
		public static void main(String[] args) {  
			//在這裡通過將列印“helloworld”的程式碼封裝到Hello物件
			//作為引數傳遞給printHello()方法
			printHello(new Hello())
		}
	}

	class Hello{
		System.out.println("helloword");
	}

動態代理對這點的應用,就是將要異常處理、日誌、計算方法的執行時間、事物管理,這些程式碼
的方式,封裝到物件中特定的方法,在以引數的形式傳遞給InvocationHandler()方法中所要求的
引數,Class<?>[] interfaces,中。

程式碼實現:
package com.itheima;
/*
 * 定義要代理類的要新增的程式碼,有異常處理、
 * 日誌、計算方法的執行時間、事物管理
 */
public interface Advice {
	void beforeMethod();
	
	void afterMethod();
}


package cn.itheima.proxy;
/*
 * 要新增的具體的程式碼的實現
 */
public class MyAdvice implements Advice {
	@Override
	public void beforeMethod() {
		System.out.println("method before");

	}
	@Override
	public void afterMethod() {
		System.out.println("method after");
	}

}

package cn.itheima.proxy;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class InvocationTest {

	public static void main(String[] args) {
		//建立想要代理的類的對像即target
		List target = new ArrayList();
		Collection proxy1 = (Collection) getProxy(target,new MyAdvice());
		
		//測試動態生成的代理類
		System.out.println(proxy1.getClass());
		proxy1.add("111");
		System.out.println(proxy1.size());
	}
	
	public static Object getProxy(final Object target,final Advice advice) {
		//引數宣告為final型別,因為後面的程式碼要用到
		Object proxy1 =  Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				//這裡接收的是陣列形式的引數(new Class[]{Collection.class})
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					//List target = new ArrayList();
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						advice.beforeMethod();
						//執行方法,
						Object retVal = method.invoke(target, args);
						advice.afterMethod();
						return retVal;
					}
				}
				
			);
		return proxy1;
	}
}


總結思考:讓jvm建立動態類及其例項物件,需要給它提供哪些資訊?
三個方面:
生成的類中有哪些方法,通過讓其實現哪些介面的方式進行告知;
產生的類位元組碼必須有個一個關聯的類載入器物件;
生成的類中的方法的程式碼是怎樣的,也得由我們提供。把我們的程式碼寫在一個約定好了介面物件的方法中,
把物件傳給它,它呼叫我的方法,即相當於插入了我的程式碼。提供執行程式碼的物件就是那個InvocationHandler物件,
它是在建立動態類的例項物件的構造方法時傳遞進去的。在上面的InvocationHandler物件的invoke方法中加一點程式碼,
就可以看到這些程式碼被呼叫運行了。

ps:終於寫完了第一篇部落格,花了整整三個小時啊!剛開始寫的時候真是無從下手啊,沒寫過部落格,怕寫的不好,
就去看大牛的部落格,發現大家特別喜歡用例子來說明知識,就悶頭想怎麼用一個比較好的例子來說明動態代理的
實在應用,哎,總之花了不少功夫,不過總歸是寫完了,挺高興的。


自己對寫部落格的理解:不要想得太多,就是把自己現階段看的某些知識點概括下來,對於某些知識理解的不清楚的,
可以去網上查資料,然後就是看老師的ppt總結部落格,嘿嘿,就這些了,第一篇部落格完工!