Simple and Fast Webservices with Mule ESB and Apache CXF - codecentric AG Blog

:

In this blog post, I want to show you how we at codecentric are using Mule ESB and Apache CXF in our projects to create webservices very easily, and what you can do to make them faster, because they are pretty slow out of the box.
So why a webservice at all? This is a good question, and perhaps the most critical for performance. Webservices are good if you want to make your Interface or Service public, or want to use them internally where other transports (like RMI) are not available, either due to firewall configuration or cross programming language environments. People struggling with a good setup might often not be able to change this at all, so lets take it for granted.

We use the Mule Enterprise Service Bus in some projects, but it might not be your product of choice. Documentation is only available when registering on their site, and their release policy is a bit unclear. I am not that happy with it, but it works pretty well when you found your hooks and patched a few places. To expose Webservices you can handcode them, use Apache Axis or Apache CXF. I prefer CXF because I find its API and generated code cleaner, also it is still alive and maintained by mule people and default for mule as well. For the service component we use plain spring pojos.

So lets have a look at our Webservice Interface. To make it a little more interesting, we make a nontrival service, which takes and returns domain objects (but be aware of the http request size on large object trees)

@WebService
public interface RecommendationService {
	@WebMethod
	public Products recommendProducts(
		@WebParam(name="user")
		User user,
		@WebParam(name="genre")
		Genre genre
	);
}

@WebService public interface RecommendationService { @WebMethod public Products recommendProducts( @WebParam(name="user") User user, @WebParam(name="genre") Genre genre ); }

There is an implementation for this service existing as well. The next step is then to wire it up in mule.
First we need to configure mule to accept webservice calls. Because we start mule in an WAR file, we use the servlet connector, but you can use the jetty connector as well when running standalone:

<servlet:connector name="servletConnector" 
                   servletUrl="http://localhost:8080/mule/services/recommendation?wsdl" />

<servlet:connector name="servletConnector" servletUrl="http://localhost:8080/mule/services/recommendation?wsdl" />

Next up is the config of the service itself using cxf:

<model name="recommendationServiceModel">
	<service name="recommendation">
		<inbound>
			<cxf:inbound-endpoint address="servlet://recommendation" synchronous="true" />
		</inbound>
		<component>
			<spring-object bean="RecommendationService" />
		</component>
	</service>
</model>

<model name="recommendationServiceModel"> <service name="recommendation"> <inbound> <cxf:inbound-endpoint address="servlet://recommendation" synchronous="true" /> </inbound> <component> <spring-object bean="RecommendationService" /> </component> </service> </model>

And of course the service:

<spring:bean id="RecommendationService"
             class="de.codecentric.RecommendationServiceImpl" />

<spring:bean id="RecommendationService" class="de.codecentric.RecommendationServiceImpl" />

While you can mash all into one file, I recommend to split your mule and component configuration to multiple files, so that you do not get lost in the masses of xml. You could separate them by type (service, component, mule config) or by service.
That is it already for the mule configuration part, so lets try to invoke it. Because we do not have an easy way to pass the domain objects yet, we just try to read the wsdl to veryify it is working.

http://localhost:8080/mule/services/recommendation?wsdl

http://localhost:8080/mule/services/recommendation?wsdl

Watch out for any s which tell you that the listing is not complete, but is available on a seperate url geven as attribute in the import.

Generating a java client for accessing the service is very easy using the wsdl2java command from CXF:

wsdl2java -client -d src/main/java -p de.codecentric.client 
  http://localhost:8080/mule/services/recommendation?wsdl

wsdl2java -client -d src/main/java -p de.codecentric.client http://localhost:8080/mule/services/recommendation?wsdl

If you would be an external party, you now could work with the stuff that has been generated. Internally however you most likely would prefer continue working with your domain objects User, Products and Genre. This will help you dealing with updates happening in your developmentcycle and provide domain methods you implemented on the model, but are not generated. Because CXF is really smart we can just delete the following generated classes:

  • Genre
  • ObjectFactory
  • package-info
  • Products
  • User

Fix the imports by using your domain objects instead and delete the @XmlSeeAlso({ObjectFactory.class}) reference.

This should leave you with the interface and implementation of the service and two wrapper objects for request and response, and a dummy client. Running the dummy client (with CXF on the classpath) should now invoke the webservice.

What it does behind the scenes when you are using

RecommendationServiceImplService ss = new RecommendationServiceImplService(wsdlURL, SERVICE_NAME);
RecommendationService port = ss.getRecommendationServiceImplPort();

RecommendationServiceImplService ss = new RecommendationServiceImplService(wsdlURL, SERVICE_NAME); RecommendationService port = ss.getRecommendationServiceImplPort();

is that it creates a dynamic proxy using reflection from the remote wsdl.

We could stop here now. We have a dynamic Webservice client that uses the domain objects. All is fine, but the performance really sucks.

The WSDL is read over the wire, and translated to the proxy class. We could add the WSDL locally, but that would require downloading it everytime the domain objects change. External clients of course need to do that, but we want to be less affected by the changes incremental development introduces. Also still the proxy class generation would be slow. We measured the wall time spent in the whole stack, and proxy generation by far outnumbered every other code.

To improve this we create a pool, using commons pool GenericObjectPool.

private final GenericObjectPool recommendationServicePool;
 
RecommendationServiceFactory recommendationServiceFactory = new RecommendationServiceFactory();
recommendationServicePool = new GenericObjectPool(recommendationServiceFactory, new Config());

private final GenericObjectPool recommendationServicePool;RecommendationServiceFactory recommendationServiceFactory = new RecommendationServiceFactory(); recommendationServicePool = new GenericObjectPool(recommendationServiceFactory, new Config());

So the pool needs a factory to make instances and a configuration. The confguration can be tweaked, but defaults should be fine for now.The factory implementation is straightforward:

public class RecommendationServiceFactory implements PoolableObjectFactory  {
public Object makeObject() throws Exception {
  RecommendationServiceImplService service = new RecommendationServiceImplService();
  RecommendationService port = service.getRecommendationServiceImplPort();
  return port;
}
public boolean validateObject(Object arg0) {
  // consider all controllers valid objects
  return true;
}
public void destroyObject(Object arg0) throws Exception {}
public void activateObject(Object arg0) throws Exception {}
public void passivateObject(Object arg0) throws Exception {}
}

public class RecommendationServiceFactory implements PoolableObjectFactory { public Object makeObject() throws Exception { RecommendationServiceImplService service = new RecommendationServiceImplService(); RecommendationService port = service.getRecommendationServiceImplPort(); return port; } public boolean validateObject(Object arg0) { // consider all controllers valid objects return true; } public void destroyObject(Object arg0) throws Exception {} public void activateObject(Object arg0) throws Exception {} public void passivateObject(Object arg0) throws Exception {} }

Now we can invoke our service like that:

RecommendationService port = (RecommendationService) recommendationServicePool.borrowObject();
try {
  Products products = port.recommendProducts(user, genre);
} finally {
  recommendationServicePool.returnObject(port);
}

RecommendationService port = (RecommendationService) recommendationServicePool.borrowObject(); try { Products products = port.recommendProducts(user, genre); } finally { recommendationServicePool.returnObject(port); }

Please do not forget to return the Service you have borrowed.

Wrap up
We used MuleESB to configure and deploy a Spring component based Webservice which uses Domain objects. We exposed the service using Apache CXF and used it to generate a client as well. Afterwards we tweaked the generated client to use our domain objects instead of generated clients. Then we introduced an object pool to avoid creating the proxy classes over and over again.

perhaps you want to know if there was a real performance gain. I strongly advise to profile it yourself. The simplest method is to measure milliseconds orund our last codeblock, executing it more than once. The very first invocation of the whole stack took 360ms on my machine. All subsequent calls were down to 4-6ms. This is a 100x improvement. Remember, that this time includes a lot: Calling over HTTP (even on localhost) a ESB running inside a WAR on JBoss, finding the correct service endpoint based on the URL, invoking an instance of that Service, and all the way back.

Do not judge premature. Web Services can be pretty fast. And thanks to a lot of frameworks, also easy to setup.