添加围绕目标方法执行的AOP建议后,Spring云流测试失败

时间:2019-01-04 11:45:29

标签: unit-testing spring-boot spring-aop spring-test spring-cloud-stream

我有一个Spring Cloud Streams应用程序,其中的流侦听器使用来自输入通道的事件。一切运行顺利,直到我添加了AOP建议以记录处理方法的执行情况(在应用程序中的其他应用程序之间)。之后,测试开始失败并显示以下错误:

  

org.springframework.messaging.MessagingException:调用com.acme.fx.exchangerate.store.infrastructure.entrypoint.messaging.ExchangeRateStoreStreamListener $$ EnhancerBySpringCGLIB $$ 9795881e#handle [1 args];时引发的异常嵌套异常是java.lang.IllegalStateException:映射的处理程序方法类'com.acme.fx.exchangerate.store.infrastructure.entrypoint.messaging.ExchangeRateStoreStreamListener $$ EnhancerBySpringCGLIB $$ 9795881e $ MockitoMock $ 1733324661'并不是实际终结点的实例bean类'com.acme.fx.exchangerate.store.infrastructure.entrypoint.messaging.ExchangeRateStoreStreamListener $$ EnhancerBySpringCGLIB $$ 9795881e $$ EnhancerBySpringCGLIB $$ 2a2d55ce'。如果端点需要代理(例如,由于@Transactional),请使用基于类的代理。   HandlerMethod详细信息:   ...

接收器定义:

应用程序代码如下:

public interface ExchangeRateStoreStreamSink {
        String NEWEXCHANGERATE="new-exchange-rate";

        @Input(NEWEXCHANGERATE)
        SubscribableChannel newExchangeRate();
}

带有注释方法的流侦听器:

@EnableBinding(ExchangeRateStoreStreamSink.class)
public class ExchangeRateStoreStreamListener {
    private CommandBus commandBus;

    @Autowired
    public ExchangeRateStoreStreamListener(CommandBus commandBus) {
        this.commandBus = commandBus;
    }

    @Loggable(operationName="ExchangeRateConsumption")
    @StreamListener(ExchangeRateStoreStreamSink.NEWEXCHANGERATE)
    public void handle(NewExchangeRateMessage newExchangeRateMessage) {
        AddExchangeRateCommand addExchangeRateCommand = new AddExchangeRateCommand(newExchangeRateMessage.from,
                newExchangeRateMessage.to, newExchangeRateMessage.amount, newExchangeRateMessage.date);
        commandBus.dispatch(addExchangeRateCommand);
    }
}

测试:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class ExchangeRateStoreStreamListenerTest {
    @Autowired
    private ExchangeRateStoreStreamSink streamSink;

    @SpyBean
    private ExchangeRateStoreStreamListener streamListener;


    @Test
    public void test() {
        SubscribableChannel input = streamSink.newExchangeRate();
        NewExchangeRateMessage exchangeRateMessage = NewExchangeRateMessageFactory.aNewExchangeRateMessage();
        input.send(new GenericMessage<>(exchangeRateMessage));

        verify(streamListener).handle(any(NewExchangeRateMessage.class));
    }
}

AOP方面:

@Aspect
@Component
public class LoggingAspect {
    private static final String API_DOMAIN = "fx";

    @Pointcut(value = "@annotation(loggable) && execution(* *(..))", argNames = "loggable")
    public void loggableMethod(Loggable loggable) { }

    @Around(value = "loggableMethod(loggable)", argNames = "pjp,loggable")
    public Object logAccess(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
        final Signature signature = pjp.getSignature();

        final Logger logger = LogManager.getLogger(signature.getDeclaringType());

        logger.info( "api_domain={} _operation={} _message=\"Start operation\"",
                API_DOMAIN, loggable.operationName());
        try {
            return pjp.proceed();
        } catch (DomainError domainError) {
            // Some logic here
        }
    }
}

任何帮助都非常欢迎。预先感谢!

1 个答案:

答案 0 :(得分:3)

这是因为StreamListener已经是代理。有太多的要解释的地方,可能写博客是一个不错的话题。 。 。 无论如何,我很高兴,尽管您已经描述了您要解决的实际问题,可以通过一种更简单的方法来解决,这就是通过引入@Bean @GlobalChannelInterceptor public ChannelInterceptor channelInterceptor() { return new ChannelInterceptor() { @Override public Message<?> preSend(Message<?> msg, MessageChannel mc) { System.out.println("Before send to channel: " + mc); return msg; } @Override public void afterSendCompletion(Message<?> msg, MessageChannel mc, boolean bln, Exception excptn) { System.out.println("After send completion to channel: " + mc); } @Override public void postSend(Message<?> msg, MessageChannel mc, boolean bln) { System.out.println("After send to channel: " + mc); } }; } 来实现的-实际上,该问题可以作为消息处理程序调用的环绕建议。 基本上是一个例子:

Before send to channel: input
Before send to channel: integrationFlowCreator.channel#0
===> SOME LOG MESSAGE INSIDE YOUR CODE 
After send to channel: integrationFlowCreator.channel#0
After send completion to channel: integrationFlowCreator.channel#0
After send to channel: input
After send completion to channel: input

。 。 。这是它将产生的输出:

GlobalChannelInterceptor

您只需在配置中声明它,就可以了。它将应用于所有通道,因此您(使用逻辑)仅监视一次您想要的通道。 有关更多详细信息,请参考Javadoc中的command: mongod --port 27018。 希望有帮助