了解 動態代理 的使用後,接著以一個實際的例子來示範讀取標註資訊並應用的例子。首先,請你看這篇 Observer 模式。
假設你希望讓使用者無需實作ClientListener介面就能定義傾聽器,方法名稱也可由使用者自行定義,只要使用者在方法上標註@ClientAdded及@ClientRemoved就可以了。那該如何進行?
為了清楚說明,先將該篇文件的相關API再最於此處:
- Client.java
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; }
// ... 其它方法...
}
- ClientEvent.java
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();
}
}
- ClientListener.java
package cc.openhome;
public interface ClientListener {
void clientAdded(ClientEvent event);
void clientRemoved(ClientEvent event);
}
- ClientQueue.java
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:
- ClientAdded.java
package cc.openhome;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface ClientAdded {}
- ClientRemoved.java
package cc.openhome;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface ClientRemoved {}
你希望的作用是使用者可以這麼標註:
- ClientLogger.java
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...");
}
}
這需要一個專門處理標註並安裝為傾聽器的物件,如下:
- ListenerInstaller.java
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);