Apache CXF with Spring Integration

Apache CXF is an easy way to expose a business class as a web service via REST (jaxrs) or SOAP (jaxws). In some cases you may just want to separate the client and business logic across multiple servers. Spring Integration comes into play because it will add a nice abstraction from the web service interface into your core classes. The goal is to layer the web service on top of existing classes without the need for any changes to existing code.

Application Setup
This example is based on a previous post using Spring Data. For this example that app was split into two project, CXF-client & CXF-service. All web files retained for Client. Service has the DAO and models. Exceptions, DTO and new services are in each.

Dependencies

The first step is to add the Apache and Spring dependencies to the project pom.xml. SI and Dozer are only needed for the service side.

  <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-frontend-jaxws</artifactId>
      <version>2.7.5</version>
  </dependency>
  
  <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-frontend-jaxrs</artifactId>
      <version>2.7.5</version>
  </dependency>

  <dependency>
      <groupId>javax.ws.rs</groupId>
      <artifactId>javax.ws.rs-api</artifactId>
      <version>2.0-m10</version>
  </dependency>

  <dependency>
      <groupId>org.springframework.integration</groupId>
      <artifactId>spring-integration-xml</artifactId>
      <version>2.2.3.RELEASE</version>
  </dependency>

  <!-- This will be used by SI to transform messages -->
  <dependency>
      <groupId>net.sf.dozer</groupId>
      <artifactId>dozer</artifactId>
      <version>5.4.0</version>
  </dependency>

Next define the interface for the web service. Although it’s possible to directly annotate your business class it’s good practice to keep them separate. This will allow you to only expose those methods you need and make it flexible to change the method parameters.

Design Consideration
This example interface is the defined interface that apache CXF will use to build the endpoints. If you are splitting an application client/service and using CXF for both sides, then the easy path would be to use this interface for both. One option is to copy the file and manually keep them in sync after changes. Another (my preferred) would be to put the interface and any dependent types in a new module then include the jar in the client and service app.

Service Interfaces

// REST Setup (Follows JAX-RS)
@Path("/sampleService")
@Produces("application/json")
public interface SampleServiceREST {

    @POST
    @Consumes("application/json")
    @Produces("application/json")
    @Path("/saveForm")
    public SignupForm saveFrom(SignupForm signupForm);

}

REST
@Path is the URL that will serve the resource. @Produces is the response type, @POST = Http POST verb, @Consumes is the expected content type and @Path extends the URL to expose this method.


// SOAP Setup (Follows JAX-WS)
@WebService
public interface SampleServiceSOAP {

    public SignupForm saveFrom(@WebParam(name="signupForm") SignupForm signupForm) throws InvalidUserException;
}

SOAP
@WebService is used by CXF to expose this interface as a WSDL. @WebParam give the parameter a friendly name in the WSDL. The default is arg0. More info can be found on the apache site about the annotations. Specific reference to the InvalidUserException is needed to jaxws will send the correct exception. Any undefined exceptions will be translated to a SOAPFault. If you do use a custom exception then make sure it exists in both the service and client application.

Now we have a nice interface but it doesn’t do anything yet and nothing implements it. Before exposing it as a CXF servlet we are going to use Spring Integration to implement the interface and route the calls to our business layer. At first pass these routes will be simple but could always be extended to do transformations, logging or anything else you may need.

Spring Integration Setup

Link the Service Interface to the business service methods

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:int="http://www.springframework.org/schema/integration"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">

    <!-- wire rest with SI -->
    <int:gateway id="sampleServiceRESTGateway"
                 service-interface="com.luckyryan.sample.ws.SampleServiceREST"
                 default-request-channel="saveFormChannelREST">
        <int:method name="saveForm" request-channel="saveFormChannelREST" />
    </int:gateway>

    <!-- wire soap with SI -->
    <int:gateway id="sampleServiceSOAPGateway"
                 service-interface="com.luckyryan.sample.ws.SampleServiceSOAP"
                 default-request-channel="saveFormChannelSOAP">

        <int:method name="saveForm" request-channel="saveFormChannelSOAP"/>
    </int:gateway>

    <int:channel id="saveFormChannelSOAP"/>
    <int:channel id="saveFormChannelREST"/>

    <int:chain input-channel="saveFormChannelSOAP">
        <int:transformer ref="userTransformer" method="signupFormToUser"/>
        <int:service-activator ref="sampleService" method="saveUser"/>
        <int:transformer ref="userTransformer" method="userToSignupForm"/>
    </int:chain>

    <int:chain input-channel="saveFormChannelREST">
        <int:transformer ref="userTransformer" method="signupFormToUser"/>
        <int:service-activator ref="sampleService" method="saveUser"/>
        <int:transformer ref="userTransformer" method="userToSignupForm"/>
    </int:chain>

</beans>

Note that we only need one Spring integration file to handle both SOAP & REST. Just like any spring config each file can support multiple bean definitions. In this case we want both the SOAP & REST calls to call the same business service method but use independent routes and processing chains.

Spring Integration Gateways become and implementation of an interface. SampleServiceSOAP or SampleServiceREST both are only interfaces with let CXF know that to expect and how to act. Spring Integration will automatically create bean implementations of those interfaces for us. But since it won’t know what to do for each service call, then we direct SI to put the massage (payload) on a set channel then direct it to our business class for processing. The chain is a connivence spring offers that saves XML. Without it each step, transformation, service, logging would need to follow channels. When using the chain SI infers the channels automatically for us. Note that message processing is sequential and synchronous. The message payload will start at the first chain entry and work down until the end at which point it is sent back to the web service.

Example Chain

SampleForm is posted from the client. Our business class needs a User object for persistence. So it needs transformation first, then the correct service method can be called us the user object. The service will return back a User object updated with the DB id. It then needs to be transformed back into a SampleForm so the client will know how to handle it.

The transformations are done with Dozer. Dozer out of the box will copy any duplicate named properties between two POJO’s. Since SampleForm and User have the same field names we can use the easy setup of dozer. This makes it easy to separate object & concerns across layers of the application. A lot of times it is easy to just use the same POJO all the way through the app which can make things messy as an application grows.

private DozerBeanMapper mapper = new DozerBeanMapper();

public User signupFormToUser(SignupForm signupForm) {
    User user = mapper.map(signupForm, User.class);
    return user;
}

public SignupForm userToSignupForm(User user) {
    SignupForm signupForm = mapper.map(user, SignupForm.class);
    return signupForm;
}

Link CXF Services to Spring Integration

At this point the interfaces are now backed by SI and both when invoked will forward the payload to the business class. The next step is to wire the newly defined gateways into CXF which will expose them as a web servlet.

Define the CXF servlet in the WEB-INF folder

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">

    <!-- these are included in the dependency jar -->
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>

    <!-- rest container -->
    <jaxrs:server id="sampleSerivceREST" address="/rest">
        <jaxrs:serviceBeans>
            <ref bean="sampleServiceRESTGateway"/>
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
            <bean class="com.luckyryan.sample.service.ExceptionHandler"/>
        </jaxrs:providers>
    </jaxrs:server>

    <!-- soap container -->
    <jaxws:endpoint
            id="sampleServiceSOAP"
            implementor="#sampleServiceSOAPGateway"
            address="/soap"
            serviceName="sampleSoapService"/>
</beans>

Here we define two endpoints with a unique URL that will invoke out SI gateways when called. Notice the SOAP container uses the #bean ref to access the spring bean. That is not needed for the REST container since it has the ref tag.

Next add the CXF servlet to the web.xml so we can expose the endpoints.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <display-name>Sample App</display-name>
    <distributable/>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:/applicationContext.xml
            classpath:/applicationContext-dao.xml
            classpath:/applicationContext-si-cxf.xml
            WEB-INF/cxf-servlet.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <description>Apache CXF Endpoint</description>
        <display-name>cxf</display-name>
        <servlet-name>cxf</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cxf</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>

</web-app>

Now the app should start and we can verify that the services are now exposed.

cxf-service-index

You can test the interface with a REST client (FireFox plugin or curl) or SOAP UI and see that the business service is invoked as expected.

Setup the client

The client application is a Spring MVC app that uses Java Config. Below is the entry to load a jaxrs & jaxws client service as a spring bean.

@Bean
public JacksonJsonProvider getJacksonJsonProvider() {
    return new JacksonJsonProvider();
}

@Bean
public ExceptionHandler getExceptionHandler() {
    return new ExceptionHandler();
}

@Bean
public SampleServiceREST getSampleServiceRestClient() {
    List providers = new ArrayList();
    providers.add(getJacksonJsonProvider());
    providers.add(getExceptionHandler());
    return JAXRSClientFactory.create("http://localhost:8090/services/rest",SampleServiceREST.class,providers);
}

@Bean
public SampleServiceSOAP getSampleServiceSoapClient() {
    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
    factory.setServiceClass(SampleServiceSOAP.class);
    factory.setAddress("http://localhost:8090/services/soap");
    return (SampleServiceSOAP) factory.create();
}

The xml equivalent is

<!-- rest container -->
<jaxrs:client id="sampleServiceREST"
              serviceClass="com.luckyryan.sample.ws.SampleServiceREST"
              address="http://localhost:8090/services/rest">
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
		<bean class="com.luckyryan.sample.exception.ExceptionHandler"/>
    </jaxrs:providers>
</jaxrs:client>

<!-- soap container -->
<jaxws:client id="sampleServiceSOAP"
              serviceClass="com.luckyryan.sample.ws.SampleServiceSOAP"
              address="http://localhost:8090/services/soap"/>

This is about it for the client. It will also need the POM dependencies listed above but not the controller can use these services instead of “sampleService”.

JAXRS Exception Handling
There is some special considerations if using custom exceptions with REST/jaxrs. See this post (Apache CXF Exception Handler) about an option to pass exceptions via rest.

19 Comments

  1. Neha Mehta August 22, 2017
  2. Atul July 28, 2015
  3. rahul May 22, 2014
    • Ryan May 28, 2014
  4. poornachandra April 9, 2014
    • Ryan May 17, 2014
  5. razeeb rahman April 4, 2014
    • Ryan April 4, 2014
  6. Leandro April 2, 2014
    • Leandro April 2, 2014
      • Leandro April 2, 2014
        • Ryan April 4, 2014
  7. Leandro March 25, 2014
    • Ryan March 26, 2014
      • Leandro March 26, 2014
  8. Sandhya January 13, 2014
    • Ryan January 13, 2014
  9. hitesh January 4, 2014
  10. Naik September 26, 2013

Leave a Reply

Your email address will not be published. Required fields are marked *