В некоторых случаях нам нужно писать в базу данных в приложении Spring в формате ApplicationListener
, поэтому нам нужны транзакции в прослушивателе с использованием @Transactional
-nnotation. Эти слушатели расширены из абстрактного базового слоя, поэтому нормальный ScopedProxyMode.INTERFACES
не будет делать, поскольку Spring-контейнер жалуется на ожидание компонента абстрактного типа класса, а не «[$ Proxy123]». Однако, используя Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
, слушатель получает одно и то же событие дважды. Мы используем Spring версии 3.1.3.RELEASE. (Edit: Еще протекающий с версией 3.2.4.RELEASE)Повторяющееся событие, полученное при прослушивании при использовании ScopedProxyMode.TARGET_CLASS
Порывшись в источник Spring с отладчиком, я узнал, что org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners возвращает LinkedList
, который содержит тот же слушатель дважды (тот же экземпляр: [[email protected], [email protected]]
), если слушателем является ScopedProxyMode.TARGET_CLASS
.
Теперь я могу обойти это, поместив базу данных обработки кода в отдельный класс и разместив там @Transactional
, но мой вопрос в том, является ли это ошибкой весной или ожидаемым поведением? Существуют ли какие-либо другие обходные пути, поэтому нам не нужно создавать отдельные классы обслуживания (т. Е. Обрабатывать транзакцию в слушателе, но не получать одно и то же событие дважды) даже для самых простых случаев?
Ниже представлен небольшой пример проблемы.
С @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
в TestEventListenerImpl, выход следующим образом:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
Got event com.example.TestEvent[source=Main]
С @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
удалены из TestEventListenerImpl, выход:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
Таким образом, кажется, что TARGET_CLASS -scoped бобы вставляются в два раза в список слушателей.
Пример:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.example/**"/>
</beans>
com.example.TestEvent
public class TestEvent extends ApplicationEvent
{
public TestEvent(Object source)
{
super(source);
System.out.println("Event " + this + " created by " + source);
}
}
com.example.TestEventListener
public interface TestEventListener extends ApplicationListener<TestEvent>
{
@Override
public void onApplicationEvent(TestEvent event);
}
com.example.TestEventListenerImpl
@Component
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
@Override
public void onApplicationEvent(TestEvent event)
{
System.out.println("Got event " + event);
}
}
com.example.ListenerTest
public class ListenerTest
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
SimpleApplicationEventMulticaster eventMulticaster = appContext.getBean(SimpleApplicationEventMulticaster.class);
//This is also needed for the bug to reproduce
TestEventListener listener = appContext.getBean(TestEventListener.class);
eventMulticaster.multicastEvent(new TestEvent("Main"));
}
}
Это тот же пример слушателя? Кстати, большой вопрос! –
Я побежал, и я получил только одно событие. –
Кроме того, в вашем примере кода ничего не проксировано. –