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

