1. 程式人生 > >代理模式(多執行緒實現狀態監控)

代理模式(多執行緒實現狀態監控)

【-1】README -1.1)本文部分文字描述轉自“head first 設計模式”,旨在學習  遠端代理物件 的基礎知識; -1.2)多執行緒實現糖果自動售賣機監控程式為原創; -1.3)博文最後,轉載了代理模式的定義; 【0】需求描述 1)problem+solution: 1.1)problem:問題在於監視器和糖果機在同一個 jvm 上面執行,而售賣機的boss的需求是遠端監控糖果售賣機,希望在其桌面上遠端監控這些機器。 1.2)solution:所以我們可以不要變化 CandyMachineMonitor , 不要將糖果機交給 monitor,而是將一個遠端物件的代理交給他;(乾貨——引入遠端代理)
2)遠端代理和本地代表:這是一種物件,活在不同的jvm 堆中(更確切地說,在不同的地址空間執行的遠端物件)。本地代表:這是一種可以由本地方法呼叫的物件,其行為會轉發到遠端物件中;
【1】遠端代理技術相關 1)RMI提供了: 客戶輔助物件,稱為stub(樁)和服務輔助物件,稱為skeleton(骨架);(乾貨——stub==客戶輔助物件, 而skeleton==服務輔助物件)
2)製作遠端服務(換句話說,這些步驟將一個普通的物件變成可以被遠端客戶呼叫的遠端物件)
  • step1)製作遠端介面: 遠端介面定義出可以讓客戶遠端呼叫的方法;
  • step2)製作遠端實現:為遠端介面中定義的遠端方法提供真正的實現;
  • step3)其中RMI registry(rmiregistry): rmiregistry如同電話簿,客戶可以從中查詢到代理的位置;
  • step4)開始遠端服務:你必須讓服務物件開始執行。你的服務實現類會去例項化一個服務的例項,並將這個服務註冊到RMI registry。註冊之後,這個訪問就可以供客戶呼叫了;
3)製作遠端服務(程式碼例項) 3.1)製作遠端介面(服務端)
  • step1)擴充套件Remote;
  • step2)宣告所有的方法都會丟擲  RemoteException;
  • step3)確定變數和返回值是屬於原語型別或可序列化型別: 這不難理解。遠端方法的變數必須被打包並通過網路運送,這要靠序列化來完成。如果你使用原語型別,字串和許多API中定義的型別(包括陣列和集合),這都不會有問題。如果你傳送自定義的類,就必須保證你的類實現了 Serializable ;
  • // 製作遠端介面, 宣告所有的方法都會丟擲  RemoteException
    public interface MyRemote extends Remote{
    	// 確定變數和返回值是屬於原語型別或可序列化型別
    	// String 型別就是可序列化的
    	public String sayHello() throws RemoteException;
    }
3.2)製作遠端實現(服務端)
  • step1)實現遠端介面:你的訪問必須要實現遠端介面,也就是客戶將要呼叫的方法的介面;
  • step2)擴充套件 UnicastRemoteObject:為了要稱為遠端服務物件,你的物件需要某些遠端的功能,最簡單的方式就是擴充套件 UnicastRemoteObject;
  • step3)設計一個不帶變數的構造器,並宣告為 RemoteException: 你的超類UnicastRemoteObject 帶來一個小問題: 它的構造器丟擲一個 RemoteException。解決方法是 為你的遠端實現宣告一個構造器,這樣就有了一個宣告 RemoteException的地方。
  • // 製作遠端實現
    public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
    
    	private static final long serialVersionUID = 2494818195984623711L;
    
    	protected MyRemoteImpl() throws RemoteException {
    		super();
    	}
    
    	@Override
    	public String sayHello() throws RemoteException {
    		
    		return "every body say, xiao tang tang";
    	}
    	
    	public static void main(String[] args) {
    		try {
    			MyRemote service = new MyRemoteImpl();
    			Naming.rebind("hehe", service);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
  • step4)用RMI Registry 註冊此服務 : 現在你已經有一個遠端服務了,必須讓它可以被遠端客戶呼叫。你要做的就是將此服務例項化,然後放進RMI registry中。當註冊這個實現物件時,RMI 系統其實註冊的是stub,因為這是客戶真正需要的。註冊服務使用了 java.rmi.Naming 類的靜態rebind()方法(如上述程式碼);
3.3)執行rmiregistry: 開啟一個終端,啟動 rmiregistry;
3.4)啟動服務:從哪裡啟動?可能是從你的遠端實現類中的main()方法,也可能是從一個獨立的啟動類;
4)客戶如何取得stub物件? 4.1)客戶必須取得stub物件(代理)以呼叫其中的方法,所以我們就需要 RMI Registry的幫忙。 4.2)客戶取得stub物件的steps:
  • step1)客戶到RMI Registry中尋找: 
  • step2)RMI Registry返回stub物件:
  • step3)客戶呼叫stub的方法,就像stub 就是真正的訪問物件一樣;
  • public class MyRemoteClient {
    	
    	public void go() {
    		try {
    			MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/hehe");
    			String s = service.sayHello();
    			System.out.println(s);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public static void main(String[] args) {
    		MyRemoteClient client = new MyRemoteClient();
    		
    		client.go();
    	}
    }
  • 列印結果)

Attention)對於RMI, coders 最常犯的三個錯誤:
  • A1)忘了在啟動遠端服務前先啟動 rmiregistry;
  • A2)忘了讓變數和返回值的型別稱為可序列化的型別;
  • A3)忘了給客戶提供stub類;
【2】將代理模式應用到糖果自動售賣機的監控系統中(多執行緒實現監控) 2.1)建立一個遠端介面
// 建立遠端服務介面(伺服器介面)
public interface CandyMachineRemote extends Remote{
	int getCount() throws RemoteException;
	String getLocation() throws RemoteException;
	State getState() throws RemoteException;
}
2.2)提供該遠端介面的實現類
public class CandyMachineProxyServer {
	public static void main(String[] args) throws RemoteException {
		CandyMachine machine = new CandyMachine("chengdu", 50);
		
		try {
			Naming.rebind("machine", machine);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		
		new Thread(new Runnable() { // 開啟一個執行緒實現client迴圈投幣
			@Override
			public void run() {
				System.out.println("\n\n ====== 下面進入迴圈測試(中獎率為20%) ======");
				for (int i = 0; i < 5; i++) {
					machine.insertQuarter();
					machine.turnCrank();
					System.out.println(machine);
					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();;
	}
}
2.3)啟動rmi registry 登錄檔服務;
E:\bench-cluster\cloud-data-preprocess\designPattern\src>rmiregistry
2.4)將實現類新增到 登錄檔服務以便client 獲取它;
E:\bench-cluster\cloud-data-preprocess\designPattern\src>java com.designpattern.chapter11_proxy.CandyMachineProxyServer
2.5)編寫監控器程式
public class CandyMachineProxyMonitor {
	CandyMachineRemote remoteMachine;
	
	public CandyMachineProxyMonitor(CandyMachineRemote remoteMachine) {
		this.remoteMachine = remoteMachine;
	}
	
	public void report() {
		int counter = 0;
		
		try {
			while(true) {
				System.out.println("round" + (++counter));
				System.out.println("machine.location = " + remoteMachine.getLocation());
				System.out.println("machine.count = " + remoteMachine.getCount());
				System.out.println("machine.state = " + remoteMachine.getState());
				if(counter == 5) {
					break;
				}
				Thread.sleep(10000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

2.5)client 獲取該遠端物件,之後,就如一般物件那樣使用(client==CEO==桌面監視器);
<pre name="code" class="java">//client
public class CandyMachineProxyMonitorTest {
	public static void main(String[] args) throws RemoteException {
		CandyMachineRemote remoteMachine;
		try {
			remoteMachine = (CandyMachineRemote) Naming.lookup("rmi://127.0.0.1/machine");
			CandyMachineProxyMonitor monitor = new CandyMachineProxyMonitor(remoteMachine);
			monitor.report();
		} catch (MalformedURLException | NotBoundException e) {
			e.printStackTrace();
		}
	}
}
列印結果)

【3】代理模式 3.1)定義:代理模式為另一個物件提供了一個替身或佔位符以控制對這個物件的訪問; 3.2)使用代理模式建立代表物件:讓代表物件控制某物件的訪問,被代理的物件可以是遠端物件、建立開銷大的物件或 需要安全控制的物件;