如何让Spring连接我的JmsComponent

时间:2013-06-04 16:59:07

标签: spring scala jboss jms apache-camel

我正在编写一个使用Akka,Akka-Camel和Spring进行配置的应用程序。应用程序需要充当针对各种应用程序服务器的独立JMS客户端,为此需要使用JNDI设置JMS连接工厂。我正在使用jBoss进行测试。我和jBoss 5和6有同样的问题(这似乎是客户端Spring问题,与jBoss无关)。

我正在用这个xml配置Spring bean:

<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"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
         http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
         ">

    <camelContext id="camel" trace="false" xmlns="http://camel.apache.org/schema/spring">
        <jmxAgent id="agent" disabled="true"/>
    </camelContext>

    <jee:jndi-lookup id="jmsConnectionFactory" jndi-name="ConnectionFactory">
        <jee:environment>
            java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
            java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
            java.naming.provider.url=jnp://192.168.0.109:1099
        </jee:environment>
    </jee:jndi-lookup>

    <bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
        <property name="connectionFactory" ref="jmsConnectionFactory" />
    </bean>

</beans>

如你所见,我正在设置:

  • 通过JNDI初始化的ConnectionFactory,名为jmsConnectionFactory
  • JmsComponent,其connectionFactory属性设置为上一个bean

使用此配置,我的应用程序在启动时失败:

java.lang.IllegalArgumentException: connectionFactory must be specified
    at org.apache.camel.util.ObjectHelper.notNull(ObjectHelper.java:294) ~[camel-core.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.createConnectionFactory(JmsConfiguration.java:1053) ~[camel-jms.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.getConnectionFactory(JmsConfiguration.java:416) ~[camel-jms.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.createListenerConnectionFactory(JmsConfiguration.java:1062) ~[camel-jms.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.getListenerConnectionFactory(JmsConfiguration.java:435) ~[camel-jms.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.configureMessageListenerContainer(JmsConfiguration.java:889) ~[camel-jms.jar:2.10.4]
    at org.apache.camel.component.jms.JmsConfiguration.createMessageListenerContainer(JmsConfiguration.java:379) ~[camel-jms.jar:2.10.4]

这来自JmsConfiguration.java中的代码:

protected ConnectionFactory createConnectionFactory() {
    ObjectHelper.notNull(connectionFactory, "connectionFactory");
    return null;
}

所以看起来Spring bean初始化无法按照此处的说明关联/连接bean(从先前粘贴的完整XML Spring配置中提取):

    <bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
        <property name="connectionFactory" ref="jmsConnectionFactory" />
    </bean>

我还尝试创建一个中间JmsConfiguration bean,并设置JmsComponent的配置属性,而不是直接设置connectionFactory属性,但我在两个设置中得到相同的结果。

顺便说一句,我可以通过代码连接bean。我是说这个:

val connFactory = springContext.getBean[javax.jms.ConnectionFactory]("jmsConnectionFactory", classOf[javax.jms.ConnectionFactory])
camelContext.addComponent("jms", JmsComponent.jmsComponentAutoAcknowledge(connFactory))

完美无缺。所以我知道我从JNDI获取ConnectionFactory,只是因为我无法使用正确的Spring配置将其连接到XML中。

我需要这个应用程序是非常可配置的,无需重新编译,因此让XML工作对我来说是必须的。

如果不清楚,问题是:如何让Spring设置我的JmsComponent bean,并将其connectionFactory设置为JNDI获取的工厂?

编辑:使用Camel的意义在于它应该允许我将此组件交换为另一种不同类型的组件。所以今天我正在使用JMS,也许明天我将使用TCP。这就是能够用XML定义所有内容的重要原因。

2 个答案:

答案 0 :(得分:1)

我认为问题在于Akka使用它自己的CamelContext而不是Spring配置中定义的CamelContext。在早期版本的Akka中似乎有可能to set the context used by Akka,但在最新版本中似乎不再可能。

我遇到了同样的问题,我在Java(而不是Scala)中使用基于注释的配置,并使用此代码解决了这个问题:

@Bean
public ActorSystem getCamelActorSystem(ConnectionFactory factory) {

    ActorSystem system = ActorSystem.create("some-system");

    Camel camel = CamelExtension.get(system);
    CamelContext camelContext = camel.context();
    camelContext.addComponent("jms", JmsComponent.jmsComponentAutoAcknowledge(factory));

    return system;
}

这会注入其他地方定义的ConnectionFactory依赖项,并使用它将jms组件添加到Akka使用的camelContext中。

另一个解决方案可能是以某种方式扩展CamelExtension代码以允许注入camelContext依赖项,如前所述。但是,我假设他们有充分的理由进行改变,所以我不管它。我认为这样他们可以确保上下文无法更改,因此Actor System始终使用相同的Camel上下文,这基于以下内容:

  

每个ActorSystem只加载一次CamelExtension,   这样可以安全地在你的任何一点调用CamelExtension   用于获取与其关联的Apache Camel对象的代码。有   每个ActorSystem都有一个CamelContext和一个ProducerTemplate   使用CamelExtension。

http://doc.akka.io/docs/akka/current/scala/camel.html#CamelExtension

答案 1 :(得分:0)

由于您的查找似乎正在运行,这可能比您正在寻找的更多,但这里是我如何使用jndiTemplate(spring 3.1)通过jndi获得连接工厂。注意,我通过配置(通过事务管理器)提供竞争工厂。对不起任何错别字,但你会明白的。

<bean id="jndiDestinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
     <property name="location">
        <value>classpath:connection.properties</value>
     </property>
</bean>
<bean name="jms" class="org.apache.camel.component.jms.JmsComponent">
    <property name="configuration" ref="jmsConfig" />
</bean>
<bean id="txManager" class="org.springframework.jms.connection.JmsTransactionManager">
   <property name="connectionFactory" ref="springConnectionFactory"/>
<bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
   <property name="connectionFactory" ref="springConnectionFactory" />
   <property name="transactionManager" ref="txManager" />
   <property name="testConnectionOnStartup" value="true" />
   <property name="destinationResolver" ref="jndiDestinationResolver" />
</bean>
<bean id="springConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
   <property name="clientId" ref="SubCid" />
   <property name="reconnectOnException" ref="true" />
   <property name="targetConnectionFactory">
        <bean parent="jndiObjectFactory"/>
   </property>
   <property name="sessionCacheSize" value="1"/>
 </bean>

 <bean id="jndiObjectFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiName" value="TopicConnectionFactory"/>
   <property name="jndiTemplate">
        <ref bean="jndiTemplate"/>
   </property>
 </bean>
 <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
       <property name="environment">
           <props>
                <prop key="java.naming.provider.url">${db.jms.JNDIServerName}</prop>
           </props>
       </property>
  </bean>