3

在某些情况下,我们需要在 Spring 中的应用程序中写入数据库,因此我们需要在侦听器中使用-annotationApplicationListener进行事务处理。@Transactional这些侦听器是从抽象基类扩展而来的,所以正常ScopedProxyMode.INTERFACES不会这样做,因为 Spring 容器抱怨期望一个抽象类类型的 bean,而不是“[$Proxy123]”。但是,使用Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),侦听器会收到两次相同的事件。我们使用的是 Spring 版本 3.1.3.RELEASE。(编辑:仍然在版本 3.2.4.RELEASE 中发生)

使用调试器深入研究 Spring 源代码,我发现org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners返回 aLinkedList包含两次相同的监听器(相同[com.example.TestEventListenerImpl@3aa6d0a4, com.example.TestEventListenerImpl@3aa6d0a4]的实例:),如果监听器是ScopedProxyMode.TARGET_CLASS.

现在,我可以通过将代码处理数据库写入一个单独的类并将其放在@Transactional那里来解决这个问题,但我的问题是,这是 Spring 中的错误还是预期的行为?是否有任何其他解决方法,因此即使是最简单的情况,我们也不需要创建单独的服务类(即在侦听器中处理事务,但不会两次获得相同的事件)?

下面是一个显示问题的小例子。

@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 范围的 bean 两次插入到侦听器列表中。

例子:

应用程序上下文.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"));
    }
}
4

1 回答 1

3

如果这是一个错误或预期的行为,我不能说,但这是肮脏的:

声明一个像

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

创建两个BeanDefinition实例:

  1. RootBeanDefinition描述 Scoped bean 。
  2. AScannedGenericBeanDefinition描述实际对象。

ApplicationContext使用这些 bean 定义来创建两个 bean:

  1. 一颗ScopedProxyFactoryBean豆子。这是一个将对象FactoryBean包装TestEventListenerImpl在代理中的方法。
  2. 一颗TestEventListenerImpl豆子。实际TestEventListenerImpl对象。

初始化过程的一部分是注册实现ApplicationListener接口的bean。bean被TestEventListenerImpl急切地(立即)创建并注册为ApplicationListener.

是惰性的ScopedProxyFactoryBean,它应该创建的 bean(代理)仅在请求时生成。发生这种情况时,它也会注册为ApplicationListener. 只有在您明确请求时才会看到它

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

或者通过使用将其隐式@Autowired注入另一个bean。请注意,添加的是实际目标对象,而不是代理。

于 2013-10-07T15:29:10.833 回答