了解 動態代理 的使用後,接著以一個實際的例子來示範讀取標註資訊並應用的例子。首先,請你看這篇 Observer
模式。
假設你希望讓使用者無需實作ClientListener介面就能定義傾聽器,方法名稱也可由使用者自行定義,只要使用者在方法上標註@ClientAdded及@ClientRemoved就可以了。那該如何進行?
為了清楚說明,先將該篇文件的相關API再最於此處:
package cc.openhome;
public class Client { private String ip; private String name; // ... 其它資料... public Client(String ip, String name) { this.ip = ip; this.name = name; } public void setIp(String ip) { this.ip = ip; } public void setName(String name) { this.name = name; } public String getIp() { return ip; } public String getName() { return name; } // ... 其它方法... }
package cc.openhome;
public class ClientEvent { public final String ip; public final String name; public ClientEvent(Client client) { this.ip = client.getIp(); this.name = client.getName(); } }
package cc.openhome;
public interface ClientListener { void clientAdded(ClientEvent event); void clientRemoved(ClientEvent event); }
package cc.openhome;
import java.util.LinkedList; import java.util.List;
public class ClientQueue { private List<Client> clients = new LinkedList<Client>(); private List<ClientListener> listeners = new LinkedList<ClientListener>(); public void addClientListener(ClientListener listener) { listeners.add(listener); } public void removeClientListener(ClientListener listener) { listeners.remove(listener); } public void notifyAdded(Client client) { ClientEvent event = new ClientEvent(client); for(ClientListener listener : listeners) { listener.clientAdded(event); } } public void notifyRemoved(Client client) { ClientEvent event = new ClientEvent(client); for(ClientListener listener : listeners) { listener.clientRemoved(event); } } public void add(Client client) { clients.add(client); notifyAdded(client); } public void remove(Client client) { clients.remove(client); notifyRemoved(client); } // 還有一些客戶管理佇列的其它職責.... }
接著定義@ClientAdded及@ClientRemoved:
package cc.openhome;
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface ClientAdded {}
package cc.openhome;
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface ClientRemoved {}
你希望的作用是使用者可以這麼標註:
package cc.openhome; public class ClientLogger { @ClientAdded public void clientAdded(ClientEvent event) { System.out.println(event.ip + " added..."); } @ClientRemoved public void clientRemoved(ClientEvent event) { System.out.println(event.ip + " removed..."); } }
這需要一個專門處理標註並安裝為傾聽器的物件,如下:
package cc.openhome;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class ClientListenerInstaller { private ClientQueue queue; private Map<String, Method> methods = new HashMap<String, Method>();
public ClientListenerInstaller(ClientQueue queue) throws Exception { this.queue = queue; } public void install(Class<?> clz) throws Exception { // 找出標註的方法 for(Method method : clz.getMethods()) { ClientAdded clientAdded = method.getAnnotation(ClientAdded.class); if(clientAdded != null) { methods.put("clientAdded", method); } ClientRemoved clientRemoved = method.getAnnotation(ClientRemoved.class); if(clientRemoved != null) { methods.put("clientRemoved", method); } } final Object listener = clz.newInstance(); // 建立代理物件 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理物件的方法被呼叫時 // 呼叫實際的傾聽器方法 Method mth = methods.get(method.getName()); return mth.invoke(listener, args); } }; Object listenerProxy = Proxy.newProxyInstance( ClientListener.class.getClassLoader(), new Class[] { ClientListener.class }, handler); // 用代理物件作註冊 Method addclientListener = queue.getClass().getMethod( "addClientListener", ClientListener.class); addclientListener.invoke(queue, listenerProxy); } }
客戶端現在可以這麼使用:
ClientQueue queue = new ClientQueue();
ClientListenerInstaller installer = new ClientListenerInstaller(queue);
installer.install(ClientLogger.class);
Client c1 = new Client("127.0.0.1", "caterpillar");
Client c2 = new Client("192.168.0.2", "justin");
queue.add(c1);
queue.add(c2);
queue.remove(c1);
queue.remove(c2);
|