Subscribing to an Azure Service Bus Topic with Spring Boot and AMQP

时间:2017-03-22 18:50:55

标签: java spring azure azureservicebus

I have an Azure Service bus topic set up called "state-changed" and it has a subscription called "reverb". I'm trying to set up a method with @JmsListener to subscribe to the topic but am getting an error:

2017-03-22 18:34:41.049  WARN 23356 --- [enerContainer-6] o.s.j.l.DefaultMessageListenerContainer  : Setup of JMS message listener invoker failed for destination 'state-changed' - trying to recover. Cause: The messaging entity 'sb://[MySERVICEBUS].servicebus.windows.net/state-changed' could not be found. TrackingId:d2b442f79e0f44bdb449861ea57155ce_G44, SystemTracker:gateway6, Timestamp:3/22/2017 6:34:37 PM

javax.jms.JMSException: The messaging entity 'sb://[MySERVICEBUS].servicebus.windows.net/state-changed' could not be found. TrackingId:d2b442f79e0f44bdb449861ea57155ce_G44, SystemTracker:gateway6, Timestamp:3/22/2017 6:34:37 PM
    at org.apache.qpid.amqp_1_0.jms.impl.TopicSubscriberImpl.createClientReceiver(TopicSubscriberImpl.java:111) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
    at org.apache.qpid.amqp_1_0.jms.impl.MessageConsumerImpl.<init>(MessageConsumerImpl.java:129) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
    at org.apache.qpid.amqp_1_0.jms.impl.TopicSubscriberImpl.<init>(TopicSubscriberImpl.java:46) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
    at org.apache.qpid.amqp_1_0.jms.impl.SessionImpl.createDurableSubscriber(SessionImpl.java:544) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
    at org.apache.qpid.amqp_1_0.jms.impl.SessionImpl.createDurableSubscriber(SessionImpl.java:59) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.createConsumer(AbstractMessageListenerContainer.java:870) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.createListenerConsumer(AbstractPollingMessageListenerContainer.java:215) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.initResourcesIfNecessary(DefaultMessageListenerContainer.java:1189) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1165) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1158) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1055) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at java.lang.Thread.run(Unknown Source) [na:1.8.0_77]

I have been using this blog post to try and get everything up and running: http://ramblingstechnical.blogspot.co.uk/p/using-azure-service-bus-with-spring-jms.html

I can add messages to the topic with JmsTemplate and read messages from it using the plain old Java JMS libraries outlined in the Azure docs: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-java-how-to-use-jms-api-amqp so I know my topic does work and is accessable, it just seems to be when I configure it with Spring that I'm doing something wrong.

My configuration looks like:

@Configuration
public class JmsConfiguration
{

    @Bean
    public JmsListenerContainerFactory topicJmsListenerContainerFactory() throws NamingException
    {
        DefaultJmsListenerContainerFactory returnValue = new DefaultJmsListenerContainerFactory();

        Context context = context();
        ConnectionFactory cf = connectionFactory(context);

        returnValue.setConnectionFactory(cf);
        returnValue.setSubscriptionDurable(Boolean.TRUE);
        return returnValue;
    }

    private Context context() throws NamingException
    {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.amqp_1_0.jms.jndi.PropertiesFileInitialContextFactory");
        env.put(Context.PROVIDER_URL, "src/main/resources/servicebus.properties");
        Context context = new InitialContext(env);
        return context;
    }



    /**
     * @param context
     * @return
     * @throws NamingException
     */
    private ConnectionFactory connectionFactory(Context context) throws NamingException
    {
        ConnectionFactory cf = (ConnectionFactory) context.lookup("SBCF");
        return cf;
    }

}

servicebus.properties (with username and password etc redacted):

# servicebus.properties - sample JNDI configuration

# Register a ConnectionFactory in JNDI using the form:
# connectionfactory.[jndi_name] = [ConnectionURL]
connectionfactory.SBCF=amqps://[USER]:[PASSWORD]@[MYSERVICEBUS]

# Register some queues in JNDI using the form
# queue.[jndi_name] = [physical_name]
# topic.[jndi_name] = [physical_name]
queue.workflow = workflow
topic.state-changed = stage-changed

And finally my listener class:

@Component
public class TestListener
{
    Logger logger = LoggerFactory.getLogger(LoggingWorkflowEventHandler.class);

    @JmsListener(destination = "state-changed", containerFactory = "topicJmsListenerContainerFactory", subscription = "reverb")
    public void onMessage(String message)
    {
        logger.info("Received message from topic: {}", message);
    }
}

If anyone has ever managed to get this working I'd be grateful for some pointers.

3 个答案:

答案 0 :(得分:0)

您的错误消息表明找不到您的目的地名称(未找到消息实体)。 请注意,您需要以特定方式告知Azure您的订阅名称:

<TopicName>/Subscriptions/<SubscriptionName>

在你的情况下:

state-changed/Subscriptions/reverb

希望有所帮助

干杯         SEB

答案 1 :(得分:0)

如果您使用 Spring Boot,则可以使用准备好的 Azure ServiceBus JMS Spring Boot Starter,它可以开箱即用。

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-servicebus-jms-spring-boot-starter</artifactId>
    <version>2.3.5</version>
</dependency>

查看教程页面 https://docs.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-service-bus

答案 2 :(得分:-2)

创建或更改Trustrore:

由于我们要建立与服务总线的安全amqps连接,我们需要将所有必需的SSL证书存储在信任库中。由于似乎没有现有的证书包含所需的证书我 - 为了透明度 - 创建了一个像这样的新证书:

访问https://<URL-Of-Your-Servicebus>获取所需的证书,例如浏览器中的https://XXXXX.servicebus.cloudapi.de。然后点击&#34;锁定&#34;在URL中(或您的浏览器显示的任何安全连接)并从那里打开证书。

保存当前证书:

[Certificate provide]

当要求输出格式时,选择&#34; DER二进制&#34;并将其保存为&#34; .cer&#34;文件,例如&#34; 1.cer&#34;

您很可能会看到您的证书基于证书链,这意味着它取决于其他证书。对于每个点击&#34;显示证书&#34;:

[Certificate provide]

以与之前相同的方式保存那个。重复,直到到达根证书。在此示例中,您将得到三个* .cer文件。如需进一步参考,我将称之为1.cer,2.cer和3.cer

您现在应该为这些证书创建一个新的Truststore文件

/opt/webMethods9/jvm/jvm/bin/keytool -import -file
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/1.cer -keystore azureTruststore.jks -alias "D-TRUST Root Class 3 CA 2 2009"

/opt/webMethods9/jvm/jvm/bin/keytool -import -file 
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/2.cer -keystore azureTruststore.jks -alias "D-TRUST SSL Class 3 CA 1 2009"

/opt/webMethods9/jvm/jvm/bin/keytool -import -file 
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/3.cer -keystore azureTruststore.jks -alias "servicebus.cloudapi.de"

首次要求您为此新创建的信任库设置密码。现在将信任库移动到/opt/webMethods9/IntegrationServer/config/certs/trusted(供以后参考)。您可以将其作为信任库添加到IS(通过使用admin-UI&#34; Security&gt; Keystore&#34;以及&#34;创建Truststore Alias&#34;),但是没有技术需要这样做,就像在我们的例子中,IS没有使用信任库 - 它仅由QPID使用。

为JNDI创建属性文件您需要创建一个servicebus.properties文件作为伪JNDI服务器的数据源。你可以在技术上将文件放在任何你想要的地方,但我建议把它放在&#34;资源&#34; &#34; XXXXXXConnection&#34;的文件夹包。这应该是该文件的内容:

# servicebus.properties - sample JNDI configuration
# Register a ConnectionFactory in JNDI using the form:
# connectionfactory.[jndi_name] = [ConnectionURL]
connectionfactory.SBCF = amqps://XXXXXX.servicebus.cloudapi.de?jms.username=xxxxx&jms.password=xxxxxxx&amqp.idleTimeout=120000&jms.receiveLocalOnly=true&transport.trustStoreLocation=/opt/webMethods9/IntegrationServer/config/certs/trusted/azureTruststore.jks
# Register some queues in JNDI using the form 
# queue.[jndi_name] = [physical_name] 
# topic.[jndi_name] = [physical_name]
queue.QUEUE = myqueue
​

一些解释:

  1. SBCF将是连接工厂的JNDI-Lookup名称。稍后需要在JMS-Connection
  2. 中使用此名称
  3. xxxxxx.servicebus.cloudapi.de是服务总线的网址
  4. jms.username将由友好的Azure管理员提供
  5. jms.password将由您友好的Azure管理员提供。但请注意,您需要对来自管理员的内容进行URL编码,然后才能在此URL中使用它。例如,可以通过在Designer中手动调用IS服务pub.string:URLEncode来完成。
  6. amqp.idleTimeout需要设置为120000(或更高),因为否则您无法连接到SB
  7. jms.receiveLocalOnly需要设置为true,因为否则您无法连接到SB
  8. transport.trustStoreLocation需要指向包含创建与SB的安全(AMQPS)连接所需的所有SSL证书的信任库
  9. queue.QUEUE:QUEUE是稍后将在JMS-Client中用于发送消息的JNDI-Lookup名称,或者在JMS-Trigger中用于接收它们。你应该设置一个更有意义的东西。此值(&#34; myqueue&#34;在示例中)是SB上队列的名称,必须由Azure管理员提供。
  10. [JNDI context factory class]

    唯一的两个重要值是:

    1. &#34;初始上下文工厂&#34;:org.apache.qpid.jms.jndi.JmsInitialContextFactory
    2. &#34;提供商URL&#34;:必须指向您创建的servicebus.properties,例如file:/opt/webMethods9/IntegrationServer/instances/default/packages/XXXXXXConnection/resources/servicebus.properties