7

虽然 Java 中简单的、接口驱动的事件通知框架自寒武纪之前就已经存在(例如 java.beans.PropertyChangeSupport),但使用注释驱动的事件通知代替框架变得越来越流行。

例如,请参阅JBossCache 2.2。侦听器类对其侦听器方法进行了注释,而不是遵循严格的接口。这更容易编程,也更容易阅读,因为您不必编写您不感兴趣的侦听器回调的空实现(是的,我知道侦听器适配器超类)。

这是来自 JBossCache 文档的示例:

@CacheListener
public class MyListener {
   @CacheStarted
   @CacheStopped
   public void cacheStartStopEvent(Event e) {
         switch (e.getType()) {
            case Event.Type.CACHE_STARTED:
               System.out.println("Cache has started");
               break;    
            case Event.Type.CACHE_STOPPED:    
               System.out.println("Cache has stopped");
               break;    
         }
   }    

   @NodeCreated    
   @NodeRemoved
   @NodeVisited
   @NodeModified
   @NodeMoved
   public void logNodeEvent(NodeEvent ne) {
         log("An event on node " + ne.getFqn() + " has occured");
   }

}

这样做的问题在于,由于它具有注释反射性质,因此编写框架以支持此类事情的过程更加复杂。

所以,在我开始编写通用框架的道路之前,我希望有人已经做到了。有没有人遇到过这样的事情?

4

8 回答 8

8

您现在已经可以使用EventBus做到这一点。

以下示例来自EventBus 入门指南。基于已发布事件更新的状态栏,无需将状态栏控件/小部件注册为发布者的侦听器。如果没有 EventBus,则需要将状态栏作为侦听器添加到许多类。状态栏也可以随时创建和销毁。

public StatusBar extends JLabel {
    public StatusBar() {
        AnnotationProcessor.process(this);
    }
    @EventSubscriber(eventClass=StatusEvent.class)
    public void updateStatus(StatusEvent statusEvent) {
        this.setText(statusEvent.getStatusText();
    }
}

一个类似的项目是ELF(事件侦听器框架),但它似乎不太成熟。

我目前正在研究Publish-Subscribe Event Driven Programming |上的事件通知框架。Kev 的 Spring vs Java EE Dev和后续文章。

于 2009-12-30T13:23:00.243 回答
4

我已经制作了http://neoevents.googlecode.com来处理这种基于注释的事件处理程序。

@actionPerformed
private void onClick() {
    //do something
}

protected void initComponents() {
    JButton button = new JButton("Click me!!!");
    button.addActionListener(new ActionListener(this) );
}

它看起来和我预期的一样简单。注释可用于 J2SE 中的每个侦听器。

于 2010-01-10T22:56:12.797 回答
3

不要把复杂误认为聪明。在我看来,这将是:

  1. 调试的噩梦
  2. 难以遵循(从维护的角度来看,或者有人试图在 6 个月后改变某些东西)
  3. 充满了if (event instanceof NodeCreatedEvent)类似的代码。为什么这比子类化更好,adapter我不知道!
于 2008-10-07T15:30:14.580 回答
2

我在这里看到的主要问题是方法参数,它限制了哪些方法实际上可以用于哪些事件,并且没有编译时帮助。

这就是接口对我来说对 Java 事件模型等观察者模式实现具有吸引力的原因。像 eclipse 这样的工具可以自动生成方法存根,这样你就不会弄错签名。在您的示例中,很容易使用错误的参数类型并且在事件发生之前永远不会知道它(这可能是几个月后的错误情况)

您可能会尝试的一件事是我的注释和处理器,用于实现观察者和空对象实现。假设你有

package a.b.c;

public interface SomeListener {
    void fee();
    void fie();
    void fo();
    void fum();
}

并想创建一个监听器实例。你可以写

package x.y.z;

import a.b.c.SomeListener;
import com.javadude.annotation.Bean;
import com.javadude.annotation.NullObject;

@Bean(nullObjectImplementations = {@NullObject(type = SomeListener.class) })
public class Foo extends FooGen implements SomeListener {
    @Override
    public void fie() {
        // whatever code you need here
    }
}

要为这些事件创建源,您可以编写

package a.b.c;

import com.javadude.annotation.Bean;
import com.javadude.annotation.Observer;

@Bean(observers = {@Observer(type = SomeListener.class)})
public class Source extends SourceGen {
    // SourceGen will have add/remove listener and fire methods
    //   for each method in SomeListener
}

如果您有兴趣,请参阅http://code.google.com/p/javadude/wiki/Annotations 。也可能给你一些其他的想法。

于 2008-10-07T15:53:22.583 回答
2

Google Guava v11添加了一个使用这种样式的 EventBus 组件。他们还解释了为什么他们决定使用注释而不是接口

于 2012-01-24T11:24:44.197 回答
1

我也一直在考虑一个通用的注释驱动的事件框架。我喜欢静态类型提供的好处,但是当前的接口驱动事件模型使用起来很痛苦(丑陋的代码)。是否可以使用自定义注释处理器进行一些编译时检查?这可能有助于增加我们已经习惯的一些缺失的“安全性”。

许多错误检查也可以在侦听器向事件生产者“注册”时完成。因此,应用程序会提前失败(当注册监听器时),甚至可能在启动时失败。

这是我一直在玩弄的通用框架的示例:

public class ExampleProducer {

    private EventSupport<ActionEvent> eventSupport;

    public ExampleProducer() {
        eventSupport = new EventSupport<ActionEvent>(this);
    }

    @AddListenersFor(ActionEvent.class)
    public void addActionListener(Object listener)
    {
        eventSupport.addListener(listener);
    }

    @RemoveListenersFor(ActionEvent.class)
    public void removeActionListener(Object listener)
    {
        eventSupport.removeListener(listener);
    }

    public void buttonClicked() {
        eventSupport.fire(new ActionEvent(this, 
                              ActionEvent.ACTION_PERFORMED, "Click"));
    }
   }

生产者使用EventSupport,它使用反射来调用事件。如前所述,EventSupport可以在注册事件侦听器时执行一些初始检查。

public class ExampleListener
{   
  private ExampleProducer submitButton;

  public ExampleListener()
  {
    submitButton = new ExampleProducer();
    EventSupport.autoRegisterEvents(this);
  }

  @HandlesEventFor("submitButton")
  public void handleSubmitButtonClick(ActionEvent event)
  {
    //...some code to handle the event here
  }
}

在这里,EventSupport有一个静态方法,它使用反射向事件生产者自动注册侦听器。这消除了手动向事件源注册的需要。自定义注解处理器可用于验证@HandlesEventFor注解是否引用了ExampleListener. 注释处理器也可以进行其他检查,例如确保事件处理程序方法签名与注册方法之一匹配ExampleProducer(基本上,可以在注册时执行相同的检查)。

你怎么看?这值得花一些时间来充分开发吗?

于 2009-05-13T10:39:15.283 回答
1

这是一个名为SJES的类似项目。

public class SomeController {

private Calculator c1 = new Calculator();
private Calculator c2 = new Calculator();

public SomeController() {
    c1.registerReceiver(this);
    c2.registerReceiver(this);
    c1.add(10, 10);
    c2.add(20, 20);
}

@EventReceiver(handleFor="c1")
public void onResultC1(Calculator.Event e) {
    System.out.println("Calculator 1 got: " + e.result);
}

@EventReceiver(handleFor="c2")
public void onResultC2(Calculator.Event e) {
    System.out.println("Calculator 2 got: " + e.result);
}

@EventReceiver
public void onResultAll(Calculator.Event e) {
    System.out.println("Calculator got: " + e.result);
}
}

public class Calculator {

private EventHelper eventHelper = new EventHelper(this);

public class Event {

    long result;

    public Event(long result) {
        this.result = result;
    }
}

public class AddEvent extends Event {

    public AddEvent(long result) {
        super(result);
    }
}

public class SubEvent extends Event {

    public SubEvent(long result) {
        super(result);
    }
}

public void unregisterReceiver(Object o) {
    eventHelper.unregisterReceiver(o);
}

public void registerReceiver(Object o) {
    eventHelper.registerReceiver(o);
}

public void add(long a, long b) {
    eventHelper.fireEvent(new AddEvent(a + b));
}

public void sub(long a, long b) {
    eventHelper.fireEvent(new SubEvent(a - b));
}

public void pass(long a) {
    eventHelper.fireEvent(new Event(a));
}
}

我认为这很容易使用。

于 2010-05-03T16:40:19.583 回答
0

您还可以查看MBassador它是注释驱动的,非常轻量级并使用弱引用(因此很容易集成到对象生命周期管理由诸如 spring 或 guice 或 somethign 之类的框架完成的环境中)。

它提供了一种对象过滤机制(因此您可以订阅 NodeEvent 并附加一些过滤器以将消息处理限制为仅一组特定类型)。您还可以定义自己的注释来自定义处理程序的声明。

而且它非常快速且资源高效。查看这个基准,显示使用 Guava 或 mbassador 的不同场景的性能图。

于 2012-10-23T09:36:44.967 回答