【问题标题】:Adding advice to a mail inbound channel adapter向邮件入站通道适配器添加建议
【发布时间】:2015-11-23 16:01:05
【问题描述】:

我正在集成测试一个 spring 集成流,它从一个邮件入站通道适配器开始。我将测试电子邮件发送到模拟 GreenMail 电子邮件服务器,然后测试预期结果。但是因为邮件是异步的,所以目前只有在我发送邮件后等待,直到流程完成,测试才能通过。

这是邮件适配器配置:

<int-mail:inbound-channel-adapter id="imapAdapter"
      store-uri="#{mailConnectionString}"
      java-mail-properties="javaMailProperties" channel="inboundChannel"
      should-delete-messages="false" should-mark-messages-as-read="true"
      auto-startup="true">
    <int:poller id="emailPoller" max-messages-per-poll="1" fixed-rate="5000">
    </int:poller>
</int-mail:inbound-channel-adapter>

所以,参考这个:Adding Completion Advice,我想我可以简单地等待完成建议,然后继续测试。但是您不能向邮件适配器添加建议:

Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'adviceChain' of bean class [org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean]: Bean property 'adviceChain' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

还尝试了回复生成处理程序(来自上面的链接),但没有找到 bean。

所以。如何向入站邮件适配器添加建议?或者有没有更好的方法在整个流程完成后测试邮件适配器?

回答建议后更新 我更改了测试以在设置中添加建议。

@Autowired
private SourcePollingChannelAdapter emailAdapter;

private MyAdvice imapAdapterCompletionAdvice;

@Before
public void setup() throws Exception
{
    imapAdapterCompletionAdvice = new MyAdvice();
    List<Advice> theAdvice = new ArrayList<Advice>();
    theAdvice.add(imapAdapterCompletionAdvice);
    emailAdapter.setAdviceChain(theAdvice);
    emailAdapter.start();           
} 

但是没有调用建议。我错过了什么吗?

这是 Advice 类:

public class MyAdvice implements MethodInterceptor {

    private final CountDownLatch latch = new CountDownLatch(1);

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object proceed = invocation.proceed();
        System.out.println(proceed);
        if (proceed instanceof Boolean) {
            Boolean mailReceived = (Boolean) proceed;
            if(mailReceived){
                latch.countDown();
            }
        }

        return proceed;
    }

    public CountDownLatch getLatch() {
        return latch;
    }
}

【问题讨论】:

    标签: spring-integration


    【解决方案1】:

    我并不完全清楚您要做什么,但入站通道适配器不是消息处理程序(因此没有处理程序 bean)。轮询的入站适配器是 MessageSource,但通知源无济于事,因为我们调用 receive(),然后将消息发送到流。

    但是,您可以将 Advice 对象添加到 &lt;poller/&gt;advice-chain

    环绕建议将涵盖receive()(来自源代码)和send() 到通道,以便您可以在那里暂停线程。

    您可以直接在轮询器上配置建议。 如果您想以编程方式执行此操作,adviceChain 属性位于工厂 bean 的 pollerMetadata 字段上。

    编辑

    我不会再使用 BFPP 了 - 这个问题/答案已经过时了;现在,我们将处理程序公开为 bean 名称 id.handler(以及类似的消息源),因此不再需要 BFPP

    等待 bean 被创建比尝试将属性注入到 bean 定义中要容易得多。

    我会做这样的事情......

    1. 在测试用例中将 auto-startup 设置为 false(使用属性占位符,以便生产为 true,测试为 false)。
    2. 注入建议链。
    3. 启动适配器...

    .

    @Autowired
    private SourcePollingChannelAdapter adapter;
    
    ...
    
    @Test
    public ... {
    
        this.adapter.setAdviceChain(...);
        this.adapter.start();
        ...
    }
    

    如果您不想使用此技术,请使用 BeanPostProcessorpostProcessAfterInitialization - 在工厂 bean 提供通道适配器之后)而不是 BeanFactoryPostProcessor 来修改建议链。

    您是正确的,即使投票没有结果,也会调用建议。

    您可以使用另一个建议(AbstractMessageSourceAdvice 的子类 - see Smart Polling

    这个建议只建议receive() 方法,并且可以判断轮询的结果是否是消息;然后,它可以在处理完消息后触发您的其他建议。

    EDIT2

    需要重置initialized 标志,以便重新应用建议。这可以使用反射来完成(Spring 有一个方便的DirectFieldAccessor)。

    如果您不习惯使用反射,您可以 start/stop/start 执行相同的操作,但我们需要确保第一个 start 不会真正触发投票。

    反射示例:

    @Autowired
    private SourcePollingChannelAdapter adapter;
    
    @Test
    public void testAdvice() throws Exception {
        List<Advice> adviceChain = new ArrayList<Advice>();
        final AtomicBoolean hasMessage = new AtomicBoolean();
        final CountDownLatch latch = new CountDownLatch(1);
        class MessageDetector extends AbstractMessageSourceAdvice {
    
            @Override
            public boolean beforeReceive(MessageSource<?> source) {
                return true;
            }
    
            @Override
            public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
                hasMessage.set(result != null);
                System.out.println("has message:" + hasMessage.get());
                return result;
            }
    
        }
        adviceChain.add(new MessageDetector());
        class MyAdvice implements MethodInterceptor {
    
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("in myAdvice before, hasmessage:" + hasMessage.get());
                Object proceed = invocation.proceed();
                System.out.println("in myAdvice after, hasmessage:" + hasMessage.get());
                latch.countDown();
                return proceed;
            }
    
        }
        adviceChain.add(new MyAdvice());
        adapter.setAdviceChain(adviceChain);
        new DirectFieldAccessor(adapter).setPropertyValue("initialized", false);
        adapter.start();
        assertTrue(latch.await(10, TimeUnit.SECONDS));
    }
    

    触发器操作示例...

    @Autowired
    private SourcePollingChannelAdapter adapter;
    
    @Test
    public void testAdvice() throws Exception {
        List<Advice> adviceChain = new ArrayList<Advice>();
        final AtomicBoolean hasMessage = new AtomicBoolean();
        final CountDownLatch latch = new CountDownLatch(1);
        class MessageDetector extends AbstractMessageSourceAdvice {
    
            @Override
            public boolean beforeReceive(MessageSource<?> source) {
                return true;
            }
    
            @Override
            public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
                hasMessage.set(result != null);
                System.out.println("has message:" + hasMessage.get());
                return result;
            }
    
        }
        adviceChain.add(new MessageDetector());
        class MyAdvice implements MethodInterceptor {
    
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("in myAdvice before, hasmessage:" + hasMessage.get());
                Object proceed = invocation.proceed();
                System.out.println("in myAdvice after, hasmessage:" + hasMessage.get());
                latch.countDown();
                return proceed;
            }
    
        }
        adviceChain.add(new MyAdvice());
        adapter.setAdviceChain(adviceChain);
        adapter.setTrigger(new Trigger() {
    
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                return null; // never poll
            }
        });
        adapter.start();
        adapter.stop();
        adapter.setTrigger(new PeriodicTrigger(1000));
        adapter.start();
        assertTrue(latch.await(10, TimeUnit.SECONDS));
    }
    

    【讨论】:

    • 感谢 Gary,我对 SI 还很陌生,正在尝试根据我的情况调整报价帖子。无论如何,将 MethodInterceptor 添加到 poller 建议链中是可行的。还以编程方式添加它,仅在测试中需要,但以一种看起来很脏的方式,有兴趣知道如何获得对 pollerMetadata 的引用,我正在使用来自适配器的 getPropertyValues。但主要问题是即使没有收到邮件,适配器也会清除闩锁。所以我们有一个竞争条件,这意味着测试有时通过有时失败。
    • 我喜欢这种技术,它更整洁。但该建议并未被调用。我会用我所拥有的更新问题。
    • 在测试中设置通知链或在 postProcessAfterInitialization 方法中设置都没有效果。适配器轮询器是在这些发生之前创建的,因此设置建议链对轮询器没有影响。可以在 postProcessBeforeInitialization 中修改 SourcePollingChannelAdapterFactoryBean,但是不完全覆盖 PollerMetadata 有点麻烦。
    • 对不起;我忘记了更重要的一步——我们需要重置initialized 标志。我们可以通过反射来做到这一点,或者,如果您在轮询器上设置initialDelaystart(); stop(); start() 将起作用。我会用一个完整的测试用例来编辑我的答案。
    • 就是这个!太谢谢了。如果收到一条消息,轮询器返回 true 很方便,所以我只是在对锁存器进行倒计时之前检查进程值。
    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    • 2016-01-18
    • 1970-01-01
    • 2012-11-03
    • 1970-01-01
    相关资源
    最近更新 更多