3

Well, very likely there isn't any mystery but I am just not smart enough to figure out what my problem is. However usually it is the mystery after all!

Sorry for the introduction, my problem is that the prototype scope doesn't seem to work for me. I have created a REST service with a Spring Integration flow (there is a http inbound gateway in the front of the flow). The scopes of most of the beans are prototype. I tested the flow by calling it ten times with threads. Also I logged the bean references (just print the 'this' in the object being called) and I saw the same reference ten times!

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

To my knowledge it means that no new instance is being created for the XmlToMapConverter but using the same instance ten times. Am I right?
Very likely I configured the Spring incorrectly but I just cannot find out what I missed.

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>

flow configuration

<?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>

So I defined the scope of XmlToMapConverter is prototype but still I can't have new object at a new request. The situation is the same for convertRestToWorkflowBean which is the first service call in the flow (service-activator). Could you please explain to me where the problem is?
Thanks, V.

Viktor
  • 1,325
  • 2
  • 19
  • 41

2 Answers2

1

Prototype scoped beans will be created every time you call ApplicationContext.getBean(...)

You've included the bean definition but haven't shown how other services reference it. My guess is it's injected into a singleton service once during initialization hence there's only one. Perhaps you need to call ApplicationContext.getBean() each time to get a new instance.

There are other solutions involving dynamic proxies that ultimately invoke getBean(), I'm on my mobile at the moment so too hard to find a link for you.

lance-java
  • 25,497
  • 4
  • 59
  • 101
  • Not 100% correct: When you try to put a prototype into a singleton, Spring will inject a proxy and every time you access the field/bean/proxy, you will get a new instance. But I agree that we need to see his code since the XML above looks correct. – Aaron Digulla Apr 14 '15 at 09:04
  • Hi Lance, thanks for the answer! I cannot call the ApplicationContext.getBean() explicitiy as the Spring Integration flow (int:service-activator) calls the bean. – Viktor Apr 14 '15 at 09:06
  • The use of proxies is configurable and you can't assume it's switched on. – lance-java Apr 14 '15 at 09:07
  • You could inject ApplicationContext into the ServiceActivator by implementing ApplicationContextAware and then explicitly lookup the bean. It's a bit crap, I know since you have a spring dependency in your class. There's also the dynamic proxy solution. – lance-java Apr 14 '15 at 09:08
  • First I tried with the request scopes but later I realized that I don't need request scope as I don't have to store anything during the request lifecycle (all the info in the message that travels in the flow). So I thought I need completely stateless beans so let's do the work with the prototype scope. I assume the prototype should work in SI flow, shouldn't it? – Viktor Apr 14 '15 at 09:16
  • If the bean is immutable/stateless, there's no need for prototype scope. Stateless services should be singletons – lance-java Apr 14 '15 at 09:23
  • Hi, thank you for the help. Artem's spEL answer solved my problem. Regards,V. – Viktor Apr 14 '15 at 14:25
  • Yes, in Artem's spEL based solution the spEL expression will be evaluated each time the ServiceActivator is invoked, ultimately calling getBean(...) each time. This has the desired effect of creating a new prototype scoped bean. Unlike my solution, this xml based solution would require an integration test if you want coverage. My solution could be covered by a simpler unit test. Horses for courses I guess :) – lance-java Apr 15 '15 at 07:41
1

I don't see xmlToMapConverter usage, but I see this:

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

where you use this:

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

The issue you are facing is called scope impendance. That's because <int:service-activator> populates several singleton beans, hence the reference to your prototype becomes as singleton, too.

One way to overcome that to use SpEL from there:

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

In this case your convertRestToWorkflowBean is retrieved from the BeanFactory on each call.

Another trick to go ahead looks like:

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

In this case your bean will be wrapped to the ScopedProxyFactoryBean and all invocation will be delegated to your prototype on demand.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Brilliant, the spEL solved my problem! thank you! V. – Viktor Apr 14 '15 at 14:24
  • What is the DSL equivalent of this . Could not find a way to define expression on IntegrationFlow. I need a new object instance of a prototype scoped bean for every call. IntegrationFlows.from("channelOne") .handle(someHandler, "handle") .nullChannel() – user68883 Sep 15 '21 at 03:49
  • The `handle()` doesn't have a SpEL variant, but there is a `transform(String expression)`, so you still are good to do something like this in Java DSL: `transform("@convertRestToWorkflowBean.convert(payload)")`. Or just use a `BeanFactory.getBean()` in the `handle()` to obtain a fresh instance of your `prototype` bean! – Artem Bilan Sep 15 '21 at 12:57
  • Though ***transform(String expression)*** does create a new instance for every call , issue is when a prior handle does NOT return the payload wrapped in ***org.springframework.messaging.Message*** i see this exception ***Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method persistMessage(java.lang.String) cannot be found on type persistService*** . Where as when i use ***handle()*** payload gets wrapped in ***Message*** . – user68883 Sep 20 '21 at 14:09
  • As per your other suggestion of using BeanFactory in ***handle()*** method i tried doing this but it still uses the same same instance of prototyped bean. Is this what you meant ? ***handle(beanFactory.getBean(PersistService.class),"persistMessage")*** – user68883 Sep 20 '21 at 14:09
  • The first question sounds more like its own SO thread. The answer for the second one is "no" : with that code you still in the setup phase, so it is indeed autowired with a single instance. you must call that `beanFactory.getBean(PersistService.class)` and its method on every single message. Might be something like this: `handle(m -> beanFactory.getBean(PersistService.class).persistMessage(m))`. – Artem Bilan Sep 20 '21 at 14:14