2013-10-04 3 views
3

В некоторых случаях нам нужно писать в базу данных в приложении 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")); 
    } 
} 
+0

Это тот же пример слушателя? Кстати, большой вопрос! –

+0

Я побежал, и я получил только одно событие. –

+0

Кроме того, в вашем примере кода ничего не проксировано. –

ответ

2

Я не могу говорить, если это ошибка или ожидаемое поведение, но вот загрязнен:

Декларирование боб как

@Component 
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice 
public class TestEventListenerImpl implements TestEventListener 
{ 

Создает два BeanDefinition экземпляров:

  1. A RootBeanDefinition Описание бобы.
  2. A ScannedGenericBeanDefinition Описание фактического объекта.

ApplicationContext будет использовать эти определения боб для создания двух компонентов:

  1. A ScopedProxyFactoryBean боб. Это FactoryBean, который обертывает объект TestEventListenerImpl в прокси.
  2. A TestEventListenerImpl bean. Фактический объект TestEventListenerImpl.

Часть процесса инициализации состоит в регистрации компонентов, реализующих интерфейс ApplicationListener. Компонент TestEventListenerImpl создается с нетерпением (сразу) и зарегистрирован как ApplicationListener.

ScopedProxyFactoryBean ленив, боб (прокси), который предполагается создать, генерируется только по запросу. Когда это происходит, оно также регистрируется как ApplicationListener. Вы видите только это, когда вы явно запросить его

TestEventListener listener = appContext.getBean(TestEventListener.class); 

или неявно с помощью @Autowired вводить его в другой компонент. Обратите внимание, что добавляется реальный целевой объект, а не прокси.

+0

Спасибо за понимание! Мне нужно будет проверить, будут ли наши слушатели автоматически открываться или извлекаться из контекста где-нибудь по какой-либо причине и посмотреть, смогу ли я обойти его, если это может помешать повторным вызовам прокси-пользователей. Это не так уж и важно, но я предпочел бы, чтобы слушатель вызывал DAO-интерфейс с @Transactional непосредственно в тривиальных случаях (например, событие, запускающее обновление записи базы данных или подобное), чем весь процесс прослушивания -> сервисный интерфейс -> реализация сервиса -> DAO -dance, поскольку это кажется просто ненужным шаблоном. – esaj

+0

@esaj Я не совсем понял, зачем вам '@ Scope', если он будет прототипом/синглом в любом случае. –

+0

В реальном прецеденте слушатели получены из абстрактного базового слоя, и некоторые реализации должны быть проксимированы для работы @ Transactional-annotations. Без проксирования транзакции не создаются. С «нормальным» ScopedProxyMode.INTERFACES контейнер приложения не запускается, жалуясь на AbstractApplicationEventMulticaster (или аналогичный), ожидающий beener-beans абстрактного типа класса, а не прокси (например, «[$ Proxy123]»). С TARGET_CLASS -proxies все, казалось, работало нормально, пока мы не заметили, что события запускаются дважды на прокси-прослушивателях: P – esaj

Смежные вопросы