我是Spring Event Listeners的问题在我的Web应用程序中,任何直接的帮助都将受到赞赏。
事件监听器已注册并被调用两次,如果我有循环依赖。
我有服务类,这在另一个方法上有@transaction注释
@Service(PBSTaskService.BEAN_NAME)
public class PBSTaskServiceImpl extends StandardServiceImpl<ITask> implements PBSTaskService,ApplicationListener<SurveyDefinitionPublishedEvent>
{
@Autowired
private AutoSelectTaskSliceRouteSyncService autoSelectTaskSliceRouteSyncService; // CYCLIC Dependency
@Override
public void onApplicationEvent(SurveyDefinitionPublishedEvent event)
{
System.out.println("PBSTSImpl"); // THIS IS CALLED TWICE
}
... Other method with @Transaction Annotation
}
@Service(AutoSelectTaskSliceRouteSyncService.BEAN_NAME)
public class AutoSelectTaskSliceRouteSyncServiceImpl implements AutoSelectTaskSliceRouteSyncService
{
@Autowired private PBSTaskService pbsTaskService; // CYCLIC dependency
}
现在如果我从First Class中删除AutoSelectTaskSliceRouteSyncService依赖项,则调用OnApplicationEvent一次,否则调用两次。
我调试并发现了 SimpleApplicationEventMulticaster.getApplicationListeners(myEvent):有两个代理对象,一个用Cglib包装,另一个用默认的。但它只有两个,如果它有循环依赖。如果我删除了Cyclic依赖项,它只有一个代理对象,那一个是CGLIB的enahnces。 我的Tx注释: 我曾尝试使用proxy-target-class =&#34; true或false&#34;但没有运气。
您可能想查看
答案 0 :(得分:2)
ApplicationEvent侦听器在我们的Web应用程序中的更多位置被调用两次。这是我们追上的一个场景。
原因: 听众注册两次。在一个侦听器实例上返回两个代理。返回的代理是1.动态Jdk接口代理2. Cglib代理,当我们有@transactions注释时。
重新创建这三点必须:
我创建了一个单独的项目,我可以使用spring和hibernate重现它。如果2和3不在一起,那么我们是安全的。
解 我尝试了许多弹簧和事务配置的调整,但没有运气。最后,当我将事务代码移动到另一个类时,我的演示项目,以便侦听器没有任何@transaction注释,然后它适用于我。
答案 1 :(得分:1)
在实现ApplicationListener接口的@Service或@Component中分配的Spring类将接收重复事件。要解决此问题,只需要接收单个事件,只需删除@Service或@Compontent注释。
答案 2 :(得分:0)
从Spring 4.2开始,您可以取消实现ApplicationListener并在任何托管Bean中的方法上使用新的@EventListener批注。这应该可以帮助您避免任何冲突。
以下是https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2
中的示例@Component
public class MyListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
...
}
}
答案 3 :(得分:0)
在Spring bean之间存在循环依赖的情况下,Spring Beans机制可能(在某些情况下)会将同一个bean的两个版本(bean本身及其Advised
包装器)放入ApplicationListener
处理的ApplicationEventMulticaster
列表。
但是,您可以实现自定义ApplicationEventMulticaster
并修复此错误(对我来说似乎是个错误)。
在自定义实现下方的摘要中,将Spring的SimpleApplicationEventMulticaster
子类化,忽略了非Advised
的bean副本,并将Advised
的版本保留在ApplicationListener
的列表中s(最有可能希望调用Advised
方法的onApplicationEvent
版本-在使用@Transactional
或AOP建议的情况下进行注释的情况,但是如果需要,可以使用算法的改变很小)
@Component
public class AdviceAwareApplicationEventMulticaster extends SimpleApplicationEventMulticaster {
@Override
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
Map<ApplicationListener<?>, ApplicationListener<?>> listenersByNakedInstances = new LinkedHashMap<>();// because superclass returns sorted listeners
Collection<ApplicationListener<?>> applicationListeners = super.getApplicationListeners(event, eventType);
for (ApplicationListener<?> listener : applicationListeners) {
boolean advised = false;
ApplicationListener<?> nakedListener = null;
if (listener instanceof Advised) {
try {
nakedListener = (ApplicationListener<?>) ((Advised)listener).getTargetSource().getTarget();
} catch (Exception e) {
// TODO
}
advised = true;
} else
nakedListener = listener;
if (advised || !listenersByNakedInstances.containsKey(nakedListener))
listenersByNakedInstances.put(nakedListener, listener);
}
return listenersByNakedInstances.values();
}
}
您无需以任何方式让Spring知道您的自定义实现,就足以让它成为Spring bean,Spring Application Context会选择它。
此外,请不要忘记,如果应用程序中有多个Spring Application Contexts,则可能会为每个监听器调用 ,但这完全是不同的故事。
答案 4 :(得分:0)
我的一个服务遇到了同样的问题,使用相同的事件创建了另一个监听器,但只调用了一次。
所以@SimonH 所写的是并非总是如此,只是在某些情况下我无法重现:
<块引用>在用@Service 或@Component 注释的Spring 类中,实现ApplicationListener 接口的类将接收重复的事件。
就我而言,这会导致对 onApplicationEvent 方法的两次调用。
@Service
public class TestClass implements ApplicationListener<MyEvent>{
@Override
public void onApplicationEvent(MyEvent event){
// called twice
}
}
代替上面的代码,我可以通过将事件侦听器创建为内部类,然后调用父类的事件方法来解决。
@Service
public class TestClass {
@Component
private class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
TestClass.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(MyEvent event){
//Have fun with a single event here
}
}