Perhaps it was only me who did not get this right in the first place, but here is what I learned last week.
Inside the same Spring configuration file I configured a CXF bus instance for client side failover and a plain Java bean that was going to use this CXF bus instance for making an external Web Services invocation. This configuration was to be deployed into Apache ServiceMix 4.3 as an OSGI bundle.
Here is the straight-forward Spring configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" >
<!-- Via these imports a CXF bus instance will be made available -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-http.xml" />
<!-- Instantiates my Java bean -->
<bean id="testBean" class="org.tmielke.cxf.failovertest.TestBean"/>
<!-- CXF bus failover configuration: -->
<!-- List of alternative addresses -->
<util:list id="addressList">
<value>http://localhost:9000/MyService </value>
<value>http://localhost:9001/MyService </value>
</util:list>
<!-- CXF failover strategy to use above address list -->
<bean id="RandomAddresses"
class="org.apache.cxf.clustering.RandomStrategy">
<property name="alternateAddresses">
<ref bean="addressList"/>
</property>
</bean>
<!-- CXF bus to use failover strategy -->
<jaxws:client name="{http://com.fusesource/MyService}MyServiceSoap"
createdFromAPI="false">
<jaxws:features>
<clustering:failover>
<clustering:strategy>
<ref bean="RandomAddresses"/>
</clustering:strategy>
</clustering:failover>
</jaxws:features>
</jaxws:client>
</beans>
This example configures a JAX-WS client configuration for failover using a random strategy for selecting a failover server. It uses the bus instance that was made available by the cxf imports.
It also instantiates a plain Java bean called testBean.
In this Java bean I simply want to use JAX-WS APIs to make an invocation to an external Web Service and I want this failover configuration to be in effect.
The JAX-WS client code reads similar to this:
package org.tmielke.cxf.failovertest;
import com.fusesource.test.MyService;
import com.fusesource.test.MyServiceSoap;
public class TestBean {
public void goForIt() {
MyService service = new MyService();
MyServiceSoap proxy = service.getMyServiceSoap();
proxy.callWhateverBusinessMethod();
}
}
My assumption was that because I configured both the Java bean and the CXF bus instance inside the same Spring configuration file, that my Java bean use this pre-configured bus instance when making an outgoing JAX-WS invocation.
However, that is not the case!
The problem is that when deploying into OSGi, there is a particular thread used at deployment time that parses the Spring configuration files. Then at runtime another thread is used for executing the Java bean and the JAX-WS code.
The SpringDeployer thread at deployment time creates a CXF bus instance and configures it for failover according to my Spring configuration. However this CXF bus instance is only bound to the thread context of this SpringDeployer thread.
At runtime when the above JAX-WS code gets executed, a different thread will run this code and that thread will have a different CXF thread context assigned. This CXF thread context is not connected to the CXF bus instance created at deployment time, but to a default CXF bus instance that has not got any additional configuration. Hence no failover will happen in this case!
So wondering about the solution?
You may guess it already; a simple solution is to inject the TestBean with the CXF bus instance created at deployment time.
<bean id="testBean" class="org.tmielke.cxf.failovertest.TestBean">
<property name="bus" ref="cxf"/>
</bean>
This requires the Java bean to expose a setter method for the CXF bus instance:
package org.tmielke.cxf.failovertest;
import com.fusesource.test.MyService;
import com.fusesource.test.MyServiceSoap;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
public class TestBean {
//store the configured CXF bus internally
private Bus bus = null;
public void setBus(Bus bus) {
this.bus = bus;
}
public Bus getBus() {
return bus;
}
public void goForIt() {
BusFactory.setThreadDefaultBus(bus);
MyService service = new MyService();
MyServiceSoap proxy = service.getMyServiceSoap();
proxy.callWhateverBusinessMethod();
}
}
Before using any JAX-WS APIs in my Java bean I need to call BusFactory.setThreadDefaultBus() to assign my preconfigured bus instance to the current thread context.
Now I am ready to make the external Web Service invocation with the failover configuration being in effect.
Note, without explicitly setting the CXF bus instance to be used, I would use the instance that gets returned from BusFactory.getDefaultBus(). BusFactory.getDefaultBus() is called by the CXF JAX-WS implementation to set the bus on the CXF server or client.
BusFactory.getDefaultBus() basically returns a default bus instance without any failover configuration, unless I explicitly assign a different bus instance using BusFactory.setThreadDefaultBus().
2 comments:
And what would happen if multiple concurrent threads at the same time invoke the goForIt business method?
The thread context on the Bus could then be changed when another thread invokes the business method.
And on top of that your business logic is no polluted with CXF logic which it really should not be.
logic is no polluted -> is *now* polutted
Post a Comment