1. 程式人生 > >JAVA設計模式之----------代理模式(Proxy)

JAVA設計模式之----------代理模式(Proxy)

這裡只是簡單的介紹下最基本的代理的使用。

代理,通俗點說 :就是一個人或者一個機構代表另一個人或者另一個機構採取行動。

在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶

端和目標物件之前起到中介的作用。代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。

UML圖

從上面的圖我們能看到代理涉及的角色:

抽象物件角色:聲明瞭目標物件和代理物件的共同介面,

這樣一來在任何可以使用目標物件的地方都可以使用代理物件。

  目標物件角色:定義了代理物件所代表的目標物件。(也就是要代理的是誰)

            代理物件角色:代理物件內部含有目標物件的引用,從而可以在任何時候

操作目標物件;代理物件提供一個與目標物件相同的介面,以便可以在任何時候替代目標物件。

代理物件通常在客戶端呼叫傳遞給目標物件之前或之後,執行某個操作,而不是單純地將呼叫

傳遞給目標物件。    

我們在這裡演示一個房東,中介和客戶之間的關係(中介就是代理)

1,我們需要一個抽象的物件角色:(介面)

public interface IRenter {
	public abstract void rent();
}

2,我們需要的目標物件:(就是實現的介面的類)

public class Renter implements IRenter{
	@Override
	public void rent() {
		System.out.println("我是房東,開始收費咯咯");
	}
}

3,代理物件的角色(就是我們的代理)

我們需要用到這個Java中代理使用的類

Object o =Proxy.newProxyInstance(loader, interfaces, h)

public class Client {
	
	@Test
	public void Test2(){
		 final Renter r =new Renter();//被代理的物件
		//o是中介代理之後的物件
		Object o =Proxy.newProxyInstance(Client.class.getClassLoader(),
								new Class[]{IRenter.class},new InvocationHandler() <span style="white-space:pre">								</span>{
									@Override
									public Object invoke(Object proxy, Method method, Object[] args)
											throws Throwable {
		//從這裡開始就是開始攔截要控制的內容了,如if(method.getName.equals('rent')){將方法名為rent的函式,做出你想要的事情}
//										System.out.println("aaaa");//這裡只是簡單的每個方法之前輸出
										System.out.println("收點費用");
										return method.invoke(r, args); //<span style="font-family: 'ms shell dlg';">return method.invoke(要代理的物件, 引數傳進來什麼引數就放出什麼引數,這裡相當於全部放開);

									}
								} );
		IRenter ir =(IRenter) o;//最後在使用的時候需要  將物件  強轉為介面型別的
		ir.rent();
	}
}

通過上面的簡單介紹,能發現,一個代理類的所有方法或函式,都要經過代理之後,才會進行操作,指定操作的方法做我們想做的事情,沒有指定的直接放行

上面的只是一個簡單的介紹例子,下面介紹一個在實際中的使用例子。

我們在連線資料庫的時候,都是採用的連結池的方式,但是連結池裡面的連線都是有限個的,

所以我們需要每個用完之後就將其放回池中,但是單獨去寫一個函式將其放回池中,不太好用,

所以,我們就將 con.close()關閉連線的時候,不去關,而是直接的放回池中,

讓其他的執行緒來呼叫,也就是需要攔截con裡面的close方法。

所需要的三個角色:

1,介面物件  connection介面

2,實現物件   Connectioncon=DriverManager.getConnection(url, user, password);//在進行連線的時候可以。

3,需要我們的代理類。

下面這是整個連線池的程式碼:

public class hibernateFactory2 {
	private static final int NUM=3;
	private static List<Connection> pool =new ArrayList<Connection>();
	static{
		//讀取配置檔案
		Properties p =new Properties();		
			try {
				p.load(hibernateFactory.class.getClassLoader().getResourceAsStream("jdbc.properties"));
				//讀取裡面的值,一直修改配置檔案即可
				String driver=p.getProperty("driver");
				String url=p.getProperty("url");
				String user=p.getProperty("username");
				String password=p.getProperty("password");
				System.out.println(driver+url+user+password);
				Class.forName(driver);
				for(int i=0;i<NUM;i++){
					final Connection	con=DriverManager.getConnection(url, user, password);
					//採用動態代理開始進行對connection介面實現代理,對con.close,實現換回去
					</span><span style="color:#ff0000;">Object o =Proxy.newProxyInstance(hibernateFactory2.class.getClassLoader(), new Class[]{Connection.class},
							new InvocationHandler() {
								@Override
								public Object invoke(Object proxy, Method method, Object[] args)
										throws Throwable {
									if(method.getName().equals("close")){ //攔截close方法
										pool.add((Connection)(proxy));//將連線還回池
										System.out.println("換回來了。。。");
										return null;
									}
									return method.invoke(con, args);//其餘的全部放行
								}
							});
					pool.add((Connection)o);
				}
			//	System.out.println("初始化完畢"+con);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
	}
	//下面是獲取連線。
	public static synchronized Connection getCon() throws Exception{
		if(pool.size()<=0){
			System.out.println("pool中已經沒有連線");
			return getCon() ;
		}
		System.out.println("使用一個。。");
		return  pool.remove(0); 
		//兩種方法都是可以的
//		while(pool.size()<=0){
//			System.out.println("池中已經乜有連線");
//			Thread.sleep(1000);
//		}
//		return pool.remove(0);
//	}
	
}}

在上面的基礎上,我們寫了一個通用的版本

public class proxyUtils {
	
	public static Object getProxy(final Object srcobj){
		Object o = Proxy.newProxyInstance(proxyUtils.class.getClassLoader(),
							srcobj.getClass().getInterfaces(), //這句可以修改
							new InvocationHandler() {
								
								@Override
								public Object invoke(Object proxy, Method method, Object[] args)
										throws Throwable {
									System.out.println("被攔擊了。。。。");//這裡採用攔截
									return  method.invoke(srcobj, args);//返回值我們也可以進行一些列的操作
								}
							}
				);
		return o;
	}
}
但是我們在呼叫的這個工具的時候,必須傳進來一個例項物件,也就是實現了的類

如 IA 為介面 A 為實現類

A a =new A();

IA ia =(IA)proxyUtils.getProxy(a);

通過ia 的物件呼叫方法就可以。

上面方法的另一種實現形式

public class proxyUtils2 implements InvocationHandler{
	private Object srcobj;
	public proxyUtils2(Object srcobj) {
		this.srcobj=srcobj;
	}	
	public static Object getProxy(final Object srcobj){
		Object o = Proxy.newProxyInstance(proxyUtils2.class.getClassLoader(),
							srcobj.getClass().getInterfaces(), //這句可以修改 ,在不知道的情況下,這種方法最好 
							new proxyUtils2(srcobj));				
		return o;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
			System.out.println("在這裡採用攔截");
			Object obj =method.invoke(srcobj, args);//在這裡我們還可以進行對返回的值進行操作,這也是代理帶來的好處
			return obj;	
	}
}

上面的這些都是採用代理設計模式實現的,其實 代理模式就相當於一個祕書,能幫你做你想做的事情。

代理應用方面(從別人哪裡看到的)

 代理模式的應用形式

(1)遠端代理(Remote Proxy) -可以隱藏一個物件存在於不同地址空間的事實。也使得客戶端可以訪問在遠端機器上的物件,遠端機器可能具有更好的計算效能與處理速度,可以快速響應並處理客戶端請求。

(2)虛擬代理(Virtual Proxy) – 允許記憶體開銷較大的物件在需要的時候建立。只有我們真正需要這個物件的時候才建立。

(3)寫入時複製代理(Copy-On-Write Proxy) – 用來控制物件的複製,方法是延遲物件的複製,直到客戶真的需要為止。是虛擬代理的一個變體。

(4)保護代理(Protection (Access)Proxy) – 為不同的客戶提供不同級別的目標物件訪問許可權

(5)快取代理(Cache Proxy) – 為開銷大的運算結果提供暫時儲存,它允許多個客戶共享結果,以減少計算或網路延遲。

(6)防火牆代理(Firewall Proxy) – 控制網路資源的訪問,保護主題免於惡意客戶的侵害。

(7)同步代理(SynchronizationProxy) – 在多執行緒的情況下為主題提供安全的訪問。

(8)智慧引用代理(Smart ReferenceProxy) - 當一個物件被引用時,提供一些額外的操作,比如將對此物件呼叫的次數記錄下來等。

(9)複雜隱藏代理(Complexity HidingProxy) – 用來隱藏一個類的複雜集合的複雜度,並進行訪問控制。有時候也稱為外觀代理(Façade Proxy),這不難理解。複雜隱藏代理和外觀模式是不一樣的,因為代理控制訪問,而外觀模式是不一樣的,因為代理控制訪問,而外觀模式只提供另一組介面。