Springs ApplicationEvents 的自定义过滤器

Custom Filter for Springs ApplicationEvents

在Spring中,当我写一个EventHandler时,我可以设置一个条件,来过滤掉不感兴趣的事件,像这样:

// I use lombok
public class TopicEvent extends ApplicationEvent {
    @Getter @Setter private String topic;
    @Getter @Setter private PayloadObject payload;
}

...

@EventListener(condition = "#event.topic eq \"ecology\"")
public void onEcologyTopicEvent(TopicEvent e) {
    ...
}

这已经很不错了。但它对

没有什么好处
@EventListener
public void onEcologyTopicEvent(TopicEvent e) {
    if (!e.getTopic().equals("ecology") { return; }
    ...
}

我想为我的 TopicEvent 用户提供的是注释

@TopicEventListener(topic = "ecology")
public void onEcologyTopicEvent(TopicEvent e) {
    ...
}

对此我有三个想法:

1: Spring 提供合成的注解和@AliasFor。也许可以使用

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventListener
public @interface TopicEventListner {

    @AliasFor(annotation = EventListener.class, /* can I tweak topic to the string #event.topic eq $topic? */)
    String topic;
}

2: (什么看起来更合理) 我可以注册一些基础设施组件,也许是自定义的 ApplicationEventMulticaster 或添加在运行时过滤到 EventListeners?如果是这样,从哪里开始比较好,即 哪个 将是 class/component 我需要实施以注册它 哪里?,分别 - 我可以挂接到哪里?

3: 在编译时将 @TopicEventListener(topic = "ecology") 替换为 @EventListener(condition = "#event.topic eq \"ecology\"")。但是这种方法似乎...可能有点矫枉过正,我对这种事情一无所知,预计它会非常复杂。
...但这可能是我用 C++ 解决它的方式(使用宏)

如何定义@EcologyTopicEventListener

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@EventListener(condition = "#event.topic eq ecology")
public @interface EcologyTopicEventListener {
}

如果您有 pre-defined 个主题列表,那么这种方法甚至比 @TopicEventListener(topic="ecology") 更好,因为它消除了 "ecology"

中可能存在的问题

如果您在编译时不知道这个列表,那么您可能无法采用您介绍的第一种方法。

在这种情况下,如果您想在运行时定义 bean(更准确地说是在应用程序上下文启动期间),您可以使用 bean 工厂 post 处理器。简而言之,它们允许以动态方式将 bean 定义注册到应用程序上下文中。 所以你可以自己创建监听器bean,甚至动态生成它们。

至于第三种方法,如果你问我的话,我也认为它有点矫枉过正:)

感谢 Mark Bramnik 的想法,我草拟了这个解决方案:

注解不继承@EventListener

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyEventAnnotation
{
   String topic() default "";
}

相反,我使用 BeanPostProcessorApplicationContext 来创建 ApplicationListeners 并为创建的每个 Bean 添加它们:

@Component
public class MyEventAnnotationBeanPostProcessor implements BeanPostProcessor
{
   private static Logger logger = LoggerFactory.getLogger(MyEventAnnotationBeanPostProcessor.class);

   @Autowired
   AbstractApplicationContext ctx;

   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
   {
      for (Method method: bean.getClass().getMethods()) {
         if (method.isAnnotationPresent(MyEventAnnotation.class)) {
            MyEventAnnotation annotation = method.getAnnotation(MyEventAnnotation.class);
            ctx.addApplicationListener(createApplicationListener(method, bean, annotation.topic()));
         }
      }
      return bean;
   }

   private ApplicationListener<MyEvent> createApplicationListener(Method m, Object bean, String topic) {
      return (MyEvent e) -> {
         if (topic.equals("") || e.getTopic().equals(topic)) { // Filter here!
            try {
               m.invoke(bean, e);
            } catch (IllegalAccessException e1) {
               e1.printStackTrace();
            } catch (InvocationTargetException e1) {
               e1.printStackTrace();
            }
         }
      };
   }
}

This is just a rough sketch of the idea, and might contain unsafe operations. I am but a beginner in Java so don't blame me, if you copy this code. But feel free to suggest fixes :D