2

I have a service which refers to a single source.

<bean id="XYZService" class="com.services.impl.DataService1">
    <constructor-arg ref="DataSource1" />
</bean>

<bean id="DataSource1" class="com.source.impl.DataSource1">
        <constructor-arg ref="DBDataSource"/>
        <constructor-arg value="xyz"/>      
</bean>

<bean id="DataSource2" class="com.source.impl.DataSource2">
        <constructor-arg ref="MsgDataSource"/>
        <constructor-arg value="xyz"/>      
</bean>

Now if i want to perform a conditional check and my service should be able listen to particular source based on a input variable something like below.

<bean id="XYZService" class="com.services.impl.DataService1">
    <constructor-arg ref=" $VARIABLE == true ? DataSource1 : DataSource2" />
</bean>

I did tried SPEL however no luck. I am beginner in spring. Any help will be appreciated. Thanks.

Niche
  • 142
  • 1
  • 2
  • 9
  • How is the value of the condition variable set? –  Jun 25 '14 at 13:16
  • I think you should use spring profiles for this purpose. – Kasper Ziemianek Jun 25 '14 at 13:16
  • http://stackoverflow.com/questions/23356757/spring-expression-to-evaluate-os/23358308#23358308 – Andrei Stefan Jun 25 '14 at 13:18
  • @ Lutz Horn : I will pass them via VM args or property file. – Niche Jun 25 '14 at 13:19
  • What version of Spring are you using? If you are using Spring 3+, are you willing to use Java Config? It will simplify your code a whole lot – geoand Jun 25 '14 at 13:21
  • Using 3.1.0 . I was specifically looking at SPEL as current architecture is quite tightly coupled. – Niche Jun 25 '14 at 13:22
  • Does `` work for you? You would set the property using `-DyourName="yourvalue"`. If it does and it fits your architecture let me know so I can add it as answer – geoand Jun 25 '14 at 13:52
  • @Niche Glad to hear it! AndreiStefan's answer is pretty much the same so there is no point in me adding it :) – geoand Jun 26 '14 at 07:32
  • @geoand there is a small, but important I believe, difference between your solution and mine. I used single quotes "'" to enclose the `DataSource` refs. This means that the spel expression, after evaluation, returns a String value ("DataSource1"). If the single quotes were not present, the expression would have been evaluated to an instance of that class. Your expression would have thrown an exception if used. Something like `NoSuchBeanDefinitionException: No bean named 'foo.DBDataSource@123456' is defined`. – Andrei Stefan Jun 26 '14 at 08:42
  • @AndreiStefan That is a good point! I didn't really test it, that is why I only added it as a comment – geoand Jun 26 '14 at 08:45

3 Answers3

2

There are many solutions. Here are two: You can use profiles for this. Define two profiles, define the DataSource beans with the same name but different profiles. (docs)

Alternatively, you can use a single bean and a static factory method (docs).

<bean id="DataSource" class="com.source.impl.DataSourceFactory"
    factory-method="createInstance"/>

Inside of DataSourceFactory.createInstance(), you can check the flag and then create the correct data source in plain Java.

The latter is a bit easier to understand, IMO. Using profiles allows you to keep everything in XML (but you should really consider switching to the Java Configuration). The drawback with profiles is that you must not forget to activate at least one of the bean won't be defined.

A third option is to use three XML files and then modify the list of XML files that should be parsed when you pass it to the ApplicationContext. But that only works if you have control over this part of the code.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
2

Assuming you are using Spring 3.1 or later, Spring Profiles may be the best solution.

Using the example of Production and Dev/QA environments, common bean declarations go in a shared file

<beans>
  <bean id="XYZService" class="com.services.impl.DataService1">
    <constructor-arg ref="DataSource" />
  </bean>
</beans>

A separate configuration contains production references

<beans profiles="prod">
  <bean id="DataSource" class="com.source.impl.DataSource1">
    <constructor-arg ref="DBDataSource"/>
    <constructor-arg value="xyz"/>      
  </bean>
</beans>

Another contains dev references

<beans profile="dev">
  <bean id="DataSource" class="com.source.impl.DataSource2">
    <constructor-arg ref="MsgDataSource"/>
    <constructor-arg value="xyz"/>      
  </bean>
</beans>

To activate the given profile add -Dspring.profiles.active=prod to your JVM arguments

You can find more info here

Another approach uses factory methods.

<bean id="DataSource" class="com.source.impl.DataSourceFactory" factory-method="getInstance">
    <constructor-arg value="#{VARIABLE}" />
</bean>

The above fragment assumes that you want your factory method to explcitly invoke the constructor of each of your services. If you dead set on using Spring to create the instances you can pass each datasource implementation as constructor arguments and use the constructor method as a simple dispatcher.

PaoloV
  • 126
  • 3
1

You need something like this:

<constructor-arg 
     ref="#{systemProperties.variable == 'true' ? 'DataSource1' : 'DataSource2'}" />

where "variable" is set like -Dvariable=true.

Andrei Stefan
  • 51,654
  • 6
  • 98
  • 89