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 "";
}
相反,我使用 BeanPostProcessor
和 ApplicationContext
来创建 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
在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 "";
}
相反,我使用 BeanPostProcessor
和 ApplicationContext
来创建 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