使用小老鼠處理傾聽器


了解 動態代理 的使用後,接著以一個實際的例子來示範讀取標註資訊並應用的例子。首先,請你看這篇 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);