【问题标题】:Generic, annotation-driven event notification frameworks通用的、注释驱动的事件通知框架
【发布时间】:2010-09-15 18:36:18
【问题描述】:

虽然 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");
   }

}

这样做的问题在于,由于它的注解反射性质,编写框架以支持这类事情的过程非常复杂。

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

【问题讨论】:

    标签: java events annotations notifications


    【解决方案1】:

    我已经让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 中的每个侦听器都可以使用注释。

    【讨论】:

      【解决方案2】:

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

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

      而且它非常快速且资源高效。看看这个benchmark,它展示了使用 Guava 或 mbassador 的不同场景的性能图表。

      【讨论】:

        【解决方案3】:
        【解决方案4】:

        这是一个类似的项目,名为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));
        }
        }
        

        我认为这很容易使用。

        【讨论】:

          【解决方案5】:

          您今天已经可以使用 EventBus 完成此操作。

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

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

          类似的项目是ELF (Event Listener Framework),但似乎不太成熟。

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

          【讨论】:

          • 我知道这样的东西会存在于某个地方:)
          • 您也可以查看 MBassador github.com/bennidi/mbassador 它是注解驱动的,速度非常快,使用弱引用(因此很容易集成到由 spring 或 guice 或 somethign 等框架完成对象生命周期管理的环境中)。
          【解决方案6】:

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

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

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

          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 上的注册方法之一匹配(基本上,可以在注册时执行相同的检查)。

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

          【讨论】:

            【解决方案7】:

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

            这就是接口对我来说对 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。也可能会给你一些其他的想法。

            【讨论】:

            • 您对缺少静态类型检查是绝对正确的,但在某些情况下,这是一个值得权衡的选择。这里有一个无类型的机制允许您将通知机制(例如线程)与通知本身分离。强类型接口会限制这种解耦。
            【解决方案8】:

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

            1. 调试的噩梦
            2. 难以遵循(从维护的角度来看,或者有人试图在 6 个月后进行更改)
            3. 充满了if (event instanceof NodeCreatedEvent) 之类的代码。为什么这比继承 adapter 更好我不知道!

            【讨论】:

            • 1.我看不出它比解耦接口差多少,当然也不比我们现在(或应该)习惯的重 AOP 框架差。
            • 2.我不明白怎么做。您的 IDE 将向您显示对注释的引用,就像向您显示对接口的引用一样容易。
            • 3.因为您只能获得一次子类化的机会,而适配器是使用它的糟糕方式。如果你已经子类化了怎么办?您要么需要接受打击并使用空存根实现每个方法,要么引入一个委托对象来处理事件。
            • 更改一些拦截逻辑(或方法名称)太容易了,并且“错过”您的程序突然无法按预期工作的事实。就像我说的,“复杂”和“聪明”不是同义词
            猜你喜欢
            • 2023-03-31
            • 1970-01-01
            • 1970-01-01
            • 2021-04-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-06-10
            相关资源
            最近更新 更多