三颗纽扣

世界上最宽广的是海洋,比海洋更宽广的是天空,比天空更宽广的是人的胸怀

导航

java 事件处理,一个事件申明以及监听器模式

整理一段以前的代码,顺便将这段对于事件处理总结的模式写下来。

首先申明一个 事件类,用来进行事件的声明

package javae.events;


/**
 * 事件接口,用来进行事件的申明,一般使用方法如下:
 * 
 * <pre>
 * private EventImpl&lt;EventArgs&gt; event1;
 * 
 * public Event&lt;EventArgs&gt; Event1() {
 *        if (event1 == null) {
 *            event1 = new EventImpl&lt;EventArgs&gt;();
 *        }
 *     return event1;
 * }
 * </pre>
 * 
 * 
@see EventImpl
 * 
@see EventArgs
 * 
 * 
@author Jumbo Shwen
 
*/


public interface Event<extends EventArgs> {
//    在这个类的定义中使用的泛型参数 T 申明了监听器所对应的事件类型
    
    
/**
     * 注册一个事件监听器
     * 
     * 
@param listener the listener
     * 
     * 
@return the event listener<? super t>
     
*/

    
public EventListener<? super T> addListener(EventListener<? super T> listener);
    
// 这个方法的申明中使用了 <? super T> 泛型约束,这样可确保事件能被发送到实现了其超类监听的监听器上。
    
// 例如:Event<EventArgs> 监听器,理论上应可以处理一切类型的事件。
    
    
/**
     * 移除指定的事件监听器
     * 
     * 
@param listener the listener
     
*/

    
public void removeListener(EventListener<? super T> listener);
}

事件中用到的事件参数,应该从EventArgs 类继承

package javae.events;

/**
 * 事件参数类的基类,所有的自定义事件参数必须从此类继承,一般而言一个具体的事件类别对应本类的一个子类。
 * 
 * 
@see EventLisener
 * 
 * 
@author Jumbo Shwen
 
*/

public class EventArgs {
//    这个定义使用类而非接口,因为一些事件可能不需要特定类型的事件参数,这时候可以直接传递本类的一个实例而无需实现一个接口。
}

 

监听器接口定义

 

package javae.events;

/**
 * 事件监听器的接口
 * 
 * 
@see Event
 * 
@see EventArgs
 * 
 * 
@author Jumbo Shwen
 
*/

public interface EventListener<extends EventArgs> {
//    在这个类的定义中使用的泛型参数 T 申明了监听器所对应的事件类型
    
    
/**
     * 当事件触发时将调用这个回调接口
     * 
     * 
@param source the source
     * 
@param e the e
     
*/

    
public void onEvent(Object source, T e);
}

 

 前面申明的事件接口,应该有一个缺省的实现。

 

package javae.events;

import java.util.WeakHashMap;

import org.apache.commons.lang.NullArgumentException;

/**
 * 事件类的实现。
 * 
 * 
@see DemoEventSource
 * 
 * 
@author Jumbo Shwen
 
*/

public class EventImpl<extends EventArgs> implements Event<T> {
//    只所以将实现和接口分开,主要考虑是要隔离 invoke 方法。
//    一般而言,invoke 方法只应被事件源来调用,事件的关注者只能使用事件的 addListener 以及 removeListener 方法。
//    因此 invoke 方法只在事件实现中出现。

    
// 已注册的监听器列表,使用一个弱引用字典,首先以监听器对象本身作为字典的 key 可保证一个监听器不会被重复注册;
    
// 其次,事件源不应该成为监听器的强制保持者,一个监听器对象,如果除了在事件中进行注册外没有其他任何的引用,那么这个
    
// 监听器对象就应该可以被垃圾回收,否则这可能导致事件对象引用了大量的监听器而导致大量的垃圾对象在系统中无法被回收。
    
// 虽然使用字典导致事件触发时,回调监听器接口的次序不一定和监听器注册的次序一致,但对于事件处理来说,这个不一致是没有关系的。
    
// 这个列表对象是延迟创建的,因为大部分事件可能都没有对应的监听器。
    private WeakHashMap<EventListener<? super T>, Object> listeners;
    
    
/**
     * 引发这个事件
     * 
     * 
@param source the source
     * 
@param e the e
     
*/

    
public void invoke(Object source, T e) {
        
if (listeners != null && !listeners.isEmpty()) {
            
for (EventListener<? super T> listener : listeners.keySet()) {
                listener.onEvent(source, e);
            }

        }

    }

    
    
/**
     * Gets the listeners.
     * 
     * 
@return the listeners
     
*/

    
protected WeakHashMap<EventListener<? super T>, Object> getListeners() {
        
if (listeners == null{
            listeners 
= new WeakHashMap<EventListener<? super T>, Object>();
        }

        
return listeners;
    }

    
    
/* (non-Javadoc)
     * @see javae.events.Event#addListener(javae.events.EventListener)
     
*/

    
public EventListener<? super T> addListener(EventListener<? super T> listener) {
        
if (listener == null{
            
throw new NullArgumentException("listerner");
        }

        getListeners().put(listener, 
null);
        
return listener;
    }

    
    
/* (non-Javadoc)
     * @see javae.events.Event#removeListener(javae.events.EventListener)
     
*/

    
public void removeListener(EventListener<? super T> listener) {
        
if (listener == null{
            
throw new NullArgumentException("listerner");
        }

        
if (listeners != null{
            listeners.remove(listener);
        }

    }

}

 

具体这几个类是什么意思,看一个Demo可能更加直观,建立一个 示例的事件源,既可以抛出事件的东东

package javae.events;

import javae.events.EventImpl;

/**
 * 事件源类的示例,既这个类会抛出一些事件。
 * 
 * 
@author Jumbo Shwen
 
*/

public class DemoEventSource {
    
    
/** 
     * 这是第一个事件,event1,声明为 EventImpl 这样才能调用其 invoke 方法抛出事件。
     * 事件对象一般要延迟创建,因为只有当外部通过 addListener 方法添加了监听器以后,事件对象才有 invoke 的意义。
     *  
     
*/

    
private EventImpl<DemoEventArgs> event1;

    
/**
     * 这是 event1 的公开接口,声明为 Event, 只允许外部 addListener 以及 removeListener
     * 
     * 
@return the event< demo event args>
     
*/

    
public Event<DemoEventArgs> event1() {
        
if (event1 == null{
            event1 
= new EventImpl<DemoEventArgs>();
        }

        
return event1;
    }

    
    
/**
     * 抛出 event1,这个方法不一定要设置为 public 的,视需要而定
     * 
     * 
@param msg the msg
     
*/

    
public void raiseEvent1(String msg) {
        
if (event1 != null{
            event1.invoke(
thisnew DemoEventArgs(msg));
        }

    }

    
    
/** The event2. */
    
private EventImpl<DemoEvent2Args> event2;

    
/**
     * Event2.
     * 
     * 
@return the event< demo event2 args>
     
*/

    
public Event<DemoEvent2Args> event2() {
        
if (event2 == null{
            event2 
= new EventImpl<DemoEvent2Args>();
        }

        
return event2;
    }

    
    
/**
     * Raise event2.
     * 
     * 
@param msg the msg
     
*/

    
public void raiseEvent2(String msg) {
        
if (event2 != null{
            event2.invoke(
thisnew DemoEvent2Args(msg));
        }

    }

}


 这个事件源中,声明了两个事件 event1 以及 event2,可见除了 延迟加载的代码外,事件声明还是蛮容易的,当然,把申明、延迟加载、触发的代码做成 eclipse 模板,那样用起来更方便了。

这里用到的两个事件参数类:

Code

最后,看看使用这两个事件的方法,这是一个单元测试:

 

package javae.events;

import static org.junit.Assert.*;

import javae.events.EventListener;

import org.junit.Test;

/**
 * DemoEvent 的测试
 
*/

public class TestEventListener {

    
/** The msg. */
    
private String msg = null;

    
/**
     * DemoEvent 事件的处理方法,这是标准的事件处理方法的方法签名,两个参数,一个表示事件的发生源,一个表示事件参数。
     * 
     * 
@param source the source
     * 
@param e the e
     
*/

    
private void onDemoEvent(Object source, DemoEventArgs e){
        msg 
= e.getMsg();
    }

    
    
/**
     * DemoEvent2 事件处理方法
     * 
     * 
@param source the source
     * 
@param e the e
     
*/

    
private void onDemoEvent2(Object source, EventArgs e) {
        msg 
= "hello";
    }


    
/** 事件处理器列表,具体说明见下文 */
    @SuppressWarnings(
"unused")
    
private Object[] eventListeners;

    
/**
     * Test on event.
     * 
     * 
@throws Exception the exception
     
*/

    @Test
    
public void testOnEvent() throws Exception {        
        DemoEventSource source 
= new DemoEventSource();
        
        
// 这是一个反例,这样注册事件监听器是有问题的,由于对监听器是弱引用,因此注册的监听器其实在垃圾回收后会被回收掉的。
        source.event1().addListener(new EventListener<DemoEventArgs>(){
            
public void onEvent(Object source, DemoEventArgs e) {
                onDemoEvent(source, e);
            }

        }
);
        
        
// 强制进行一次垃圾回收
        Runtime.getRuntime().gc();
        Thread.sleep(
1000);
        
        
// 测试结果表明前面注册的监听器被回收掉了,并没有执行到 onDemoEvent 方法去,msg 依然是空值
        source.raiseEvent1("hello world");
        assertEquals(
null, msg);
        
        
// 正确的注册方法是这样的,将所有注册的监听器在一个列表中保持强引用,这样监听器将一直在本对象被回收前有效。
        eventListeners = new Object[] {
                source.event1().addListener( 
new EventListener<DemoEventArgs>() {
                    
public void onEvent(Object source, DemoEventArgs e) {
                        onDemoEvent(source, e);
                    }

                }
)
                ,
                
                
// 这里使用了一个超类监听器,用 EventArgs 监听器来处理 DemoEvent2 事件,因为 EventArgs 是 DemoEvent2 的超类,所以完全是可以这样处理的
                source.event2().addListener(new EventListener<EventArgs>(){
                    
public void onEvent(Object source, EventArgs e) {
                        onDemoEvent2(source, e);
                    }

                }
)
        }
;

        Runtime.getRuntime().gc();
        Thread.sleep(
1000);

        
// 现在事件监听器真正起作用了
        source.raiseEvent1("hello world");
        assertEquals(
"hello world", msg);
        
        source.raiseEvent2(
"hello world");
        assertEquals(
"hello", msg);
    }



}

 这里比较烦的地方就是 java 中没有委托,所以必须用监听器对象,而监听器对象在事件注册中又是弱引用的,所以必须用一个数组来保持这些对象的强引用以避免它们被垃圾回收。

总的来说,使用这个模式申明和处理事件的代码还是比较简洁的,当然和 DotNet 比还是麻烦很多,不过 java 的内部类、泛型 ? 参数,在这里发挥了强大的作用,不知道 DotNet 为什么不直接支持内部类以及 泛型 ? 参数呢?为什么 java 到 6.0 了还不支持委托呢? 这两个语言真是烦人。

posted on 2009-04-02 10:40  三颗纽扣  阅读(2195)  评论(0)    收藏  举报