JAVA學習篇--靜態代理VS動態代理

代理Proxy:

 

Proxy代理模式是一種結構型設計模式,主要解決的問題是:在直接訪問對象時帶來的問題

代理是一種常用的設計模式,其目的就是爲其他對象提供一個代理以控制對某個對象的訪問。代理類負責爲委託類預處理消息,過濾消息並轉發消息,以及進行消息被委託類執行後的後續處理。

爲了保持行爲的一致性,代理類和委託類通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委託類對象的直接訪問,也可以很好地隱藏和保護委託類對象,同時也爲實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。

 

更通俗的說,代理解決的問題當兩個類需要通信時,引入第三方代理類,將兩個類的關係解耦,讓我們只瞭解代理類即可,而且代理的出現還可以讓我們完成與另一個類之間的關係的統一管理,但是切記,代理類和委託類要實現相同的接口,因爲代理真正調用的還是委託類的方法。

使用場合舉例:

如果需要委託類處理某一業務,那麼我們就可以先在代理類中統一處理然後在調用具體實現類

 

按照代理的創建時期,代理類可以分爲兩種: 

靜態:由程序員創建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。

動態:在程序運行時運用反射機制動態創建而成。

 

下面分別用靜態代理與動態代理演示一個示例:

添加打印日誌的功能,即每個方法調用之前和調用之後寫入日誌

 

靜態代理:

 

具體用戶管理實現類

public class UserManagerImpl implements UserManager {
 
	@Override
	public void addUser(String userId, String userName) {
		System.out.println("UserManagerImpl.addUser");
	}
 
	@Override
	public void delUser(String userId) {
		System.out.println("UserManagerImpl.delUser");
	}
 
	@Override
	public String findUser(String userId) {
		System.out.println("UserManagerImpl.findUser");
		return "張三";
	}
 
	@Override
	public void modifyUser(String userId, String userName) {
		System.out.println("UserManagerImpl.modifyUser");
 
	}
}

代理類--代理用戶管理實現類

public class UserManagerImplProxy implements UserManager {
 
	// 目標對象
	private UserManager userManager;
	// 通過構造方法傳入目標對象
	public UserManagerImplProxy(UserManager userManager){
		this.userManager=userManager;
	}
	@Override
	public void addUser(String userId, String userName) {
		try{
				//添加打印日誌的功能
				//開始添加用戶
				System.out.println("start-->addUser()");
				userManager.addUser(userId, userName);
				//添加用戶成功
				System.out.println("success-->addUser()");
			}catch(Exception e){
				//添加用戶失敗
				System.out.println("error-->addUser()");
			}
	}
 
	@Override
	public void delUser(String userId) {
		userManager.delUser(userId);
	}
 
	@Override
	public String findUser(String userId) {
		userManager.findUser(userId);
		return "張三";
	}
 
	@Override
	public void modifyUser(String userId, String userName) {
		userManager.modifyUser(userId,userName);
	}
 
}

客戶端調用


public class Client {
 
	public static void main(String[] args){
		//UserManager userManager=new UserManagerImpl();
		UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());
		userManager.addUser("1111", "張三");
	}
}

靜態代理類優缺點

 

優點:

 

代理使客戶端不需要知道實現類是什麼,怎麼做的,而客戶端只需知道代理即可(解耦合),對於如上的客戶端代碼,newUserManagerImpl()可以應用工廠將它隱藏,如上只是舉個例子而已。

 

缺點:

1)代理類和委託類實現了相同的接口,代理類通過委託類實現了相同的方法。這樣就出現了大量的代碼重複。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。

2)代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要爲每一種對象都進行代理,靜態代理在程序規模稍大時就無法勝任了。如上的代碼是隻爲UserManager類的訪問提供了代理,但是如果還要爲其他類如Department類提供代理的話,就需要我們再次添加代理Department的代理類。

 

舉例說明:代理可以對實現類進行統一的管理,如在調用具體實現類之前,需要打印日誌等信息,這樣我們只需要添加一個代理類,在代理類中添加打印日誌的功能,然後調用實現類,這樣就避免了修改具體實現類。滿足我們所說的開閉原則。但是如果想讓每個實現類都添加打印日誌的功能的話,就需要添加多個代理類,以及代理類中各個方法都需要添加打印日誌功能(如上的代理方法中刪除,修改,以及查詢都需要添加上打印日誌的功能)

即靜態代理類只能爲特定的接口(Service)服務。如想要爲多個接口服務則需要建立很多個代理類。
 

引入動態代理:

根據如上的介紹,你會發現每個代理類只能爲一個接口服務,這樣程序開發中必然會產生許多的代理類

所以我們就會想辦法可以通過一個代理類完成全部的代理功能,那麼我們就需要用動態代理

在上面的示例中,一個代理只能代理一種類型,而且是在編譯期就已經確定被代理的對象。而動態代理是在運行時,通過反射機制實現動態代理,並且能夠代理各種類型的對象

在Java中要想實現動態代理機制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 類的支持

java.lang.reflect.InvocationHandler接口的定義如下:


//Object proxy:被代理的對象
//Method method:要調用的方法
//Object[] args:方法調用時所需要參數
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

}

java.lang.reflect.Proxy類的定義如下:


//CLassLoader loader:類的加載器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子類的實例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

動態代理:

 

具體實現類


public class UserManagerImpl implements UserManager {
 
	@Override
	public void addUser(String userId, String userName) {
		System.out.println("UserManagerImpl.addUser");
	}
 
	@Override
	public void delUser(String userId) {
		System.out.println("UserManagerImpl.delUser");
	}
 
	@Override
	public String findUser(String userId) {
		System.out.println("UserManagerImpl.findUser");
		return "張三";
	}
 
	@Override
	public void modifyUser(String userId, String userName) {
		System.out.println("UserManagerImpl.modifyUser");
 
	}
 
}

動態創建代理對象的類

//動態代理類只能代理接口(不支持抽象類),代理類都需要實現InvocationHandler類,實現invoke方法。該invoke方法就是調用被代理接口的所有方法時需要調用的,該invoke方法返回的值是被代理接口的一個實現類
   
public class LogHandler implements InvocationHandler {
 
	// 目標對象
	private Object targetObject;
	//綁定關係,也就是關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke方法。            
	public Object newProxyInstance(Object targetObject){
		this.targetObject=targetObject;
		//該方法用於爲指定類裝載器、一組接口及調用處理器生成動態代理類實例  
		//第一個參數指定產生代理對象的類加載器,需要將其指定爲和目標對象同一個類加載器
		//第二個參數要實現和目標對象一樣的接口,所以只需要拿到目標對象的實現接口
		//第三個參數表明這些被攔截的方法在被攔截時需要執行哪個InvocationHandler的invoke方法
		//根據傳入的目標返回一個代理對象
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
				targetObject.getClass().getInterfaces(),this);
	}
	@Override
	//關聯的這個實現類的方法被調用時將被執行
	/*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("start-->>");
		for(int i=0;i<args.length;i++){
			System.out.println(args[i]);
		}
		Object ret=null;
		try{
			/*原對象方法調用前處理日誌信息*/
			System.out.println("satrt-->>");
			
			//調用目標方法
			ret=method.invoke(targetObject, args);
			/*原對象方法調用後處理日誌信息*/
			System.out.println("success-->>");
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("error-->>");
			throw e;
		}
		return ret;
	}

}

被代理對象targetObject通過參數傳遞進來,我們通過targetObject.getClass().getClassLoader()獲取ClassLoader對象,然後通過targetObject.getClass().getInterfaces()獲取它實現的所有接口,然後將targetObject包裝到實現了InvocationHandler接口的LogHandler對象中。通過newProxyInstance函數我們就獲得了一個動態代理對象。
 

客戶端代碼

public class Client {
 
	public static void main(String[] args){
		LogHandler logHandler=new LogHandler();
		UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl());
		//UserManager userManager=new UserManagerImpl();
		userManager.addUser("1111", "張三");
	}
}

可以看到,我們可以通過LogHandler代理不同類型的對象,如果我們把對外的接口都通過動態代理來實現,那麼所有的函數調用最終都會經過invoke函數的轉發,因此我們就可以在這裏做一些自己想做的操作,比如日誌系統、事務、攔截器、權限控制等。這也就是AOP(面向切面編程)的基本原理。
 

插曲:

 

AOP(AspectOrientedProgramming):將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼---解耦。

 

針對如上的示例解釋:

 

我們來看上面的UserManagerImplProxy類,它的兩個方法System.out.println("start-->addUser()")和System.out.println("success-->addUser()"),這是做核心動作之前和之後的兩個截取段,正是這兩個截取段,卻是我們AOP的基礎,在OOP裏,System.out.println("start-->addUser()")、核心動作、System.out.println("success-->addUser()")這個三個動作在多個類裏始終在一起,但他們所要完成的邏輯卻是不同的,如System.out.println("start-->addUser()")裏做的可能是權限的判斷,在所有類中它都是做權限判斷,而在每個類裏核心動作卻各不相同,System.out.println("success-->addUser()")可能做的是日誌,在所有類裏它都做日誌。正是因爲在所有的類裏,核心代碼之前的操作和核心代碼之後的操作都做的是同樣的邏輯,因此我們需要將它們提取出來,單獨分析,設計和編碼,這就是我們的AOP思想。一句話說,AOP只是在對OOP的基礎上進行進一步抽象,使我們的類的職責更加單一。
 

動態代理優點:

 

動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。而且動態代理的應用使我們的類職責更加單一,複用性更強
 

 

總結:

其實所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之前起到中介的作用。

代理對象就是把被代理對象包裝一層,在其內部做一些額外的工作,比如用戶需要上facebook,而普通網絡無法直接訪問,網絡代理幫助用戶先翻牆,然後再訪問facebook。這就是代理的作用了。

縱觀靜態代理與動態代理,它們都能實現相同的功能,而我們看從靜態代理到動態代理的這個過程,我們會發現其實動態代理只是對類做了進一步抽象和封裝,使其複用性和易用性得到進一步提升而這不僅僅符合了面向對象的設計理念,其中還有AOP的身影,這也提供給我們對類抽象的一種參考。關於動態代理與AOP的關係,個人覺得AOP是一種思想,而動態代理是一種AOP思想的實現!
 

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