Пользовательский фильтр для Springs ApplicationEvents - PullRequest
1 голос
/ 25 июня 2019

Весной, когда я пишу 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 во время выполнения? Если так, то где было бы хорошее место для начала, то есть , который был бы классом / компонентом, который я должен был бы реализовать, чтобы зарегистрировать его где? , соответственно - где я мог бы подключиться к

3: Заменить @TopicEventListener(topic = "ecology") на @EventListener(condition = "#event.topic eq \"ecology\"") во время компиляции. Но этот подход кажется ... может быть, немного излишним, и я не имею ни малейшего понятия о таких вещах, и я ожидаю, что это будет очень сложно.
... Но я мог бы решить это в C ++ (с макросом)

Ответы [ 2 ]

1 голос
/ 25 июня 2019

Как насчет определения @EcologyTopicEventListener?

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

Если у вас есть заранее определенный список тем, этот подход может быть даже лучше, чем @TopicEventListener(topic="ecology"), поскольку он устраняет возможные проблемы в «экологии»

Если вы неЭтот список известен во время компиляции, поэтому, вероятно, вы не сможете использовать первый из представленных вами подходов.

В этом случае, если вы хотите определить bean-компоненты во время выполнения (ну, если быть более точным во времязапуск контекста приложения), вы можете использовать постпроцессоры bean factory.В двух словах, они позволяют динамически регистрировать определения компонентов в контексте приложения.Таким образом, вы можете сами создавать бины слушателей и даже генерировать их динамически.

Что касается третьего подхода, я также считаю излишним, если вы спросите меня:)

0 голосов
/ 25 июня 2019

Благодаря идеям Марк Брамник Я набросал это решение:

Аннотация не наследует @EventListener

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

Вместо этого я использую BeanPostProcessor и ApplicationContext, чтобы создать ApplicationListeners и добавить их для каждого созданного Бина:

@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();
            }
         }
      };
   }
}

Это всего лишь приблизительный набросок идеи, который может содержать небезопасные операции. Я новичок в Java, поэтому не вините меня, если вы скопируете этот код. Но не стесняйтесь предлагать исправления: D

...