【问题标题】:Mystery around Spring Integration and prototype scopeSpring Integration 和原型范围之谜
【发布时间】:2015-06-19 19:18:34
【问题描述】:

嗯,很可能没有任何谜团,但我不够聪明,无法弄清楚我的问题是什么。但通常它毕竟是谜!

抱歉介绍,我的问题是原型范围似乎不适合我。我创建了一个带有 Spring Integration 流的 REST 服务(流的前面有一个 http 入站网关)。大多数bean的范围都是原型。我用线程调用它十次来测试这个流。我还记录了 bean 引用(只需在被调用的对象中打印“this”),我看到相同的引用十次!

 e.g. org.protneut.server.common.utils.XmlToMapConverter@755d7bc2  

据我所知,这意味着没有为 XmlToMapConverter 创建新实例,而是使用同一个实例十次。我说的对吗?
很可能我错误地配置了 Spring,但我无法找出我错过了什么。

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/SpringIntegration-servlet.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>SpringIntegration</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringIntegration</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

SpringIntegration-servlet.xml

<beans ...>
    <mvc:annotation-driven/>
    <context:component-scan base-package="org.protneut.server.controller, org.protneut.server.common.persistence.service" />
    <jpa:repositories base-package="org.protneut.server.common.persistence.repository"/>
    <!-- ********************* importing the mysql config ********************* -->
    <import resource="/mysql-config-context.xml"/>
    <!-- ********************* importing the message flow ********************* -->
    <import resource="classpath:META-INF/spring/integration/processing_req_wokflow.xml"/>
    <tx:annotation-driven />
    <!-- ************************************************************************* -->
    <!-- ******************************** for JPA ******************************** -->
    <!-- ************************************************************************* -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="org.protneut.server.common.persistence.model" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
            </bean>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <!-- ********************* the used property files ********************* -->
    <context:property-placeholder location="classpath:protneut-server-config.properties"/>
    <!-- 
    ****************************************************************************************
    **********************  Beans used in the Spring Integration flow ********************** 
    ****************************************************************************************
    -->
    <!-- it has to be prototype, it cannot be request as it is in an async call so it is not in the request! -->
    <bean id="logManager" class="org.protneut.server.log.LogManager" scope="prototype"></bean>
    <bean id="convertRestToWorkflowBean" class="org.protneut.server.rest.ConvertRestMessageToWorkflowBean" scope="prototype"/>
    <bean id="xmlToMapConverter" class="org.protneut.server.common.utils.XmlToMapConverter" scope="prototype"/>
    <bean id="serviceStorageManager" class="org.protneut.server.cache.ServiceStorageManager" scope="singleton">
        <property name="cacheBeanDAO" ref="cacheBeanDAO"/>
    </bean>
    <bean id="serviceCall" class="org.protneut.server.call.ServiceCall" scope="prototype">
        <property name="httpClient" ref="httpClient"/>
    </bean>
    <bean id="xmlResponseExtractor" class="org.protneut.server.extract.XmlResponseExtractor" scope="prototype">
        <property name="xmlToMapConverter" ref="xmlToMapConverter"/>
    </bean>
    ...
</beans>

流程配置

<?xml version="1.0" encoding="UTF-8"?>
<beans ..>
    <task:executor id="async_executor" pool-size="50"  />
    <!-- ***************************************************************************************************** -->
    <!-- ************************************* WORKFLOW STARTING ********************************************* -->
    <!-- ***************************************************************************************************** -->
    <int-http:inbound-gateway id="receivingRest-inboundGateway"
        supported-methods="POST" path="/service/get"
        request-payload-type="java.lang.String" reply-timeout="10000"
        request-channel="arrivedRestReq_channel" auto-startup="true"
        error-channel="error_channel" reply-channel="restResponse-channel" >
    </int-http:inbound-gateway>
    <int:channel id="arrivedRestReq_channel" scope="prototype"></int:channel>
    <int:json-to-object-transformer type="java.util.Map"
        input-channel="arrivedRestReq_channel"
        output-channel="fromConvertToActivator-channel"
        id="convertJsonToMap_">
    </int:json-to-object-transformer>
    <int:channel id="fromConvertToActivator-channel"></int:channel>
    <int:service-activator
        input-channel="fromConvertToActivator-channel"
        output-channel="toCallChain-channel"
        id="convertRestToWorkflowBean-serviceActivator"
        ref="convertRestToWorkflowBean" method="convert">
    </int:service-activator>
    <int:channel id="toCallChain-channel"></int:channel>
    <int:chain input-channel="toCallChain-channel" id="call_chain">
        <int:service-activator
            id="serviceStorageManager-serviceActivator"
            ref="serviceStorageManager" method="getServiceInfo">
        </int:service-activator>
        <int:service-activator id="serviceRequestCreator-serviceActivator" ref="serviceRequestCreator" method="create"/>
        <int:service-activator id="call-serviceActivator"
            ref="serviceCall" method="call">
        </int:service-activator>
        <int:router expression="payload.extractType.name()"
            id="responseExtractor-router">
            <int:mapping value="XPATH" channel="xmlResponse-channel"/>
            <int:mapping value="JSONPATH" channel="jsonResponse-channel"/>
        </int:router>
    </int:chain>
    ...
    <int:service-activator id="xmlResponseExtractor-serviceActivator"
    ref="xmlResponseExtractor" method="extract" input-channel="xmlResponse-channel" output-channel="toRestResponseCreator_chain"></int:service-activator>
</beans>

所以我将 XmlToMapConverter 的范围定义为原型,但我仍然不能在新请求中拥有新对象。 convertRestToWorkflowBean 的情况也是如此,它是流中的第一个服务调用(服务激活器)。 你能解释一下问题出在哪里吗?
谢谢,V。

【问题讨论】:

  • 我没有看到你在任何地方使用CmlToMapConverter。向我们展示如何访问 bean 的代码(字段?setter?@AutowiregetBean()?)。
  • 嗨亚伦,你是对的,对不起。我更新了帖子。谢谢!

标签: java spring spring-integration


【解决方案1】:

我没有看到 xmlToMapConverter 的用法,但我看到了:

<int:service-activator
    input-channel="fromConvertToActivator-channel"
    output-channel="toCallChain-channel"
    id="convertRestToWorkflowBean-serviceActivator"
    ref="convertRestToWorkflowBean" method="convert">

你在哪里使用这个:

<bean id="convertRestToWorkflowBean" class="org.protneut.server.rest.ConvertRestMessageToWorkflowBean" scope="prototype"/>

您面临的问题称为scope impendance。这是因为&lt;int:service-activator&gt; 填充了几个singleton bean,因此对prototype 的引用也变为singleton

克服这一问题的一种方法是从那里使用 SpEL:

<int:service-activator
    input-channel="fromConvertToActivator-channel"
    output-channel="toCallChain-channel"
    id="convertRestToWorkflowBean-serviceActivator"
    expression="@convertRestToWorkflowBean.convert(payload)"/>

在这种情况下,您的convertRestToWorkflowBean 会在每次调用时从BeanFactory 中检索到。

另一个技巧是这样的:

<bean id="convertRestToWorkflowBean" class="org.protneut.server.rest.ConvertRestMessageToWorkflowBean" scope="prototype">
    <aop:scoped-proxy/>
</bean>

在这种情况下,您的 bean 将被包装到 ScopedProxyFactoryBean,并且所有调用都将按需委托给您的 prototype

【讨论】:

  • 太棒了,spEL 解决了我的问题!谢谢!五、
  • this 的 DSL 等价物是什么。找不到在 IntegrationFlow 上定义表达式的方法。对于每个调用,我都需要一个原型作用域 bean 的新对象实例。 IntegrationFlows.from("channelOne") .handle(someHandler, "handle") .nullChannel()
  • handle() 没有 SpEL 变体,但有一个 transform(String expression),所以你仍然可以在 Java DSL 中做这样的事情:transform("@convertRestToWorkflowBean.convert(payload)")。或者只是在handle() 中使用BeanFactory.getBean() 来获取prototype bean 的新实例!
  • 虽然 transform(String expression) 确实为每个调用创建了一个新实例,但问题是当先前的句柄不返回包裹在org.springframework.messaging.Message 我看到这个异常Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method调用:在类型 persistService 上找不到方法 persistMessage(java.lang.String)。当我使用 handle() 有效负载被包装在 Message 中时。
  • 根据您在 handle() 方法中使用 BeanFactory 的其他建议,我尝试这样做,但它仍然使用相同的原型 bean 实例.这是你的意思吗? handle(beanFactory.getBean(PersistService.class),"persistMessage")
【解决方案2】:

每次调用 ApplicationContext.getBean(...) 时都会创建原型范围的 bean

您已包含 bean 定义,但尚未显示其他服务如何引用它。我的猜测是它在初始化期间被注入到单例服务中,因此只有一个。也许您每次都需要调用 ApplicationContext.getBean() 来获取一个新实例。

还有其他涉及最终调用 getBean() 的动态代理的解决方案,我现在在手机上,很难为您找到链接。

【讨论】:

  • 不是 100% 正确:当您尝试将原型放入单例中时,Spring 将注入一个代理,并且每次您访问字段/bean/proxy 时,您都会获得一个新实例。但我同意我们需要查看他的代码,因为上面的 XML 看起来是正确的。
  • 嗨兰斯,感谢您的回答!我无法显式调用 ApplicationContext.getBean(),因为 Spring Integration 流(int:service-activator)调用了 bean。
  • 代理的使用是可配置的,你不能假设它已经打开了。
  • 您可以通过实现 ApplicationContextAware 将 ApplicationContext 注入到 ServiceActivator 中,然后显式查找 bean。有点废话,我知道,因为您的班级中有弹簧依赖项。还有动态代理解决方案。
  • 首先我尝试了请求范围,但后来我意识到我不需要请求范围,因为我不需要在请求生命周期中存储任何内容(消息中的所有信息都在流)。所以我认为我需要完全无状态的 bean,所以让我们使用原型范围进行工作。我假设原型应该在 SI 流程中工作,不是吗?
猜你喜欢
  • 2011-09-22
  • 1970-01-01
  • 2012-03-28
  • 1970-01-01
  • 1970-01-01
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
相关资源
最近更新 更多