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)
1@WebService 2public interface RecommendationService { 3 @WebMethod 4 public Products recommendProducts( 5 @WebParam(name="user") 6 User user, 7 @WebParam(name="genre") 8 Genre genre 9 ); 10}
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:
1<servlet:connector name="servletConnector" 2 servletUrl="http://localhost:8080/mule/services/recommendation?wsdl" />
Next up is the config of the service itself using cxf:
1<model name="recommendationServiceModel"> 2 <service name="recommendation"> 3 <inbound> 4 <cxf:inbound-endpoint address="servlet://recommendation" synchronous="true" /> 5 </inbound> 6 <component> 7 <spring-object bean="RecommendationService" /> 8 </component> 9 </service> 10</model>
And of course the service:
1<spring:bean id="RecommendationService" 2 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.
1http://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:
1wsdl2java -client -d src/main/java -p de.codecentric.client 2 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
1RecommendationServiceImplService ss = new RecommendationServiceImplService(wsdlURL, SERVICE_NAME); 2RecommendationService 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.
1private final GenericObjectPool recommendationServicePool; 2 3RecommendationServiceFactory recommendationServiceFactory = new RecommendationServiceFactory(); 4recommendationServicePool = 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:
1public class RecommendationServiceFactory implements PoolableObjectFactory {
2public Object makeObject() throws Exception {
3 RecommendationServiceImplService service = new RecommendationServiceImplService();
4 RecommendationService port = service.getRecommendationServiceImplPort();
5 return port;
6}
7public boolean validateObject(Object arg0) {
8 // consider all controllers valid objects
9 return true;
10}
11public void destroyObject(Object arg0) throws Exception {}
12public void activateObject(Object arg0) throws Exception {}
13public void passivateObject(Object arg0) throws Exception {}
14}
Now we can invoke our service like that:
1RecommendationService port = (RecommendationService) recommendationServicePool.borrowObject(); 2try { 3 Products products = port.recommendProducts(user, genre); 4} finally { 5 recommendationServicePool.returnObject(port); 6}
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.
More articles
fromFabian Lange
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Fabian Lange
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.