EJB3 vs. Spring: Simple SOAP Services

nvisia is an award-winning software development partner driving competitive edge for clients.

This is the second article in the EJB3 vs Spring series.  I will be focused on the differences between Spring and EJB3 when exposing a simple SOAP service.  In case you missed it, there is an explanation of my maven project layout in my first article, EJB3 vs Spring: Rest Services.  As always, all of the source code is available for your perusal on github at https://github.com/jgitter/fwc.  In order to get the most out of this article, you'll want to be familiar with SOAP and JAX-WS.  Two good places to get started are http://www.soapuser.com/basics1.html and http://docs.oracle.com/javase/7/docs/technotes/guides/xml/jax-ws/

If you wish to test out any of the services mentioned for yourself, there are two ways to do so.  Simply clone the git repository, run a maven build, and deploy it to a EJB3-enabled container of your choice (some assembly may be required for your container of choice).  I have supplied a web interface which can be reached at http://<yourhost>/fwc where you can run all 4 versions of the findInstitutions service and examine the results and I have also committed a SoapUI (5.0.0) project in the parent module that has a request defined for each.

Note that I am using WSDL version 1.1, not the newer 1.2/2.0 due to the general lack of adoption and tool support.

Contract-First or Code-First?

I was hoping to avoid this question when I undertook writing this article since I'm not interested in taking part in a holy war.  I wanted to use a code-first approach for my examples simply because I am more comfortable defining my service contract in code.  There are several tools available for generating a wsdl from service code, with Apache CXF likely emerging as the best of those tools, though it does much more than that.  I completed the JEE version of this service using this approach quite effectively.  Then I turned my attention to Spring-WS only to be greeted with the following statement in their reference documentation (http://docs.spring.io/spring-ws/docs/2.2.0.RELEASE/reference/htmlsingle/#why-contract-first):

When creating Web services, there are two development styles: Contract Last and Contract First. When using a contract-last approach, you start with the Java code, and let the Web service contract (WSDL, see sidebar) be generated from that. When using contract-first, you start with the WSDL contract, and use Java to implement said contract.

Spring-WS only supports the contract-first development style, and this section explains why.

This seems like an odd place for Spring to choose to take a stance on how something must be done, when from what I've seen they are usually aggressively flexible.  However, let it be known that in my examples I used a code-first approach for the JEE implementation.  I then lifted the generated schema in that wsdl to generate the Spring wsdl so I could come as close to a code-first approach as possible.  While not the most correct of approaches, I believed I was able to achieve my goal.

After I was finished, it was brought to my attention that I wasn't truly comparing apples to apples.  To that end, in my github project I have shown how to configure a maven project to use the CXF codegen plugin to build a service implementation from a static wsdl.  If you run 'mvn generate-sources' on the fwc-ejb-web module, the generated classes will appear in the target/generated-sources folder.  You would then update the service implementation class to add your implementation to any service methods that were stubbed out there.  On the flip-side of that coin, I have also set up the cxf java2wsdl plugin to show an example of how you can generate a wsdl from an implementation.  I'm not using the results of either of these, but hopefully they can be educational.

Taking advantage of JBoss

I'm going to be taking advantage of some of the features offered by the JBoss Application Server so, as promised, I intend to highlight those features to prevent skewing the analysis toward one framework or the other.  Specifically, in my EJB3 annotated SOAP services, I'm letting the container generate my wsdl for me.  It does this by using the aforementioned Apache CXF under the hood.

Alternatively, it is possible to generate your own wsdl and host it.  As I mentioned earlier, my project in github shows an example of how this would be done in the plugin configuration for the fwc-ejb-web module.  During the generate-resources phase it generates a wsdl from a service implementation and places it in the generated-resources directory.  Then, during the compile phase you could copy that wsdl into the WEB-INF directory via the maven resources plugin.  It can be referenced from there in the wsdlLocation attribute of the @WebService annotation on the service implementation.  I left the sample generation there for instructive purposes, but I'm not using it.

Another way JBoss is also helping me out by rewriting the soap:address in the wsdl when it is requested.  This is done by setting <modify-wsdl-address/> to true in the jboss configuration under the webservices subsystem.  The Spring container is also doing this for me, but it requires a little more work, which I'll describe in detail.

EJB3 / JAX-WS SOAP Services

For a SOAP service created with JAX-WS annotations, no real configuration is necessary.  For my JBoss EAP container, I only need to have the webservices sub-system enabled which uses Apache CXF to publish your service endpoints.  Your classpath will be scanned for any classes annotated with @WebService and, in my case, also generates the wsdl as I mentioned above.  Here is my service implementation.

SOAP Service Implementation

//snip - not all imports shown here
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
 
@Stateless
@WebService(name = "InstitutionService", serviceName = "soap-institution",
        targetNamespace = InstitutionSoapService.TARGET_NAMESPACE)
public class InstitutionSoapServiceImpl implements InstitutionSoapService {
 
    @Inject
    private InstitutionService service;
 
    @Override
    @WebMethod
    @WebResult(targetNamespace = InstitutionSoapService.TARGET_NAMESPACE,
            name = FindInstitutionsResponse.NAME)
    public FindInstitutionsResponse findInstitutions(
            @WebParam(targetNamespace = InstitutionSoapService.TARGET_NAMESPACE,
                    name = FindInstitutionsRequest.NAME) FindInstitutionsRequest request) {
 
        FindInstitutionsResponse response = new FindInstitutionsResponse();
 
        // snip
 
        return response;
    }
}

A breakdown of the annotations:

  1. The @Stateless annotation marks this as a stateless session bean
  2. The @WebService annotation is required on any Service Endpoint Implementation or endpoint interface.  It informs the container that it is a JAX-WS endpoint.
    1. The 'name' attribute specifies the name of the web service and is used as the name of the portType in the wsdl.
    2. The 'serviceName' attribute specifies the service name of this web service and is used as the name of the wsdl:service in the wsdl.  It is also used as the url prefix to access methods defined on this service: /<context_root>/fwc-ejb/soap-institution/InstitutionService
    3. The 'targetNamespace' attribute specifies the namespace of the service which must match the namespace on any incoming requests
  3. The @Inject annotation comes from CDI and injects a @RequestScoped service bean for me as discussed in my Rest Service article and is not relevant to the subject at hand
  4. The @WebMethod annotation marks a method to be exposed on a @WebService endpoint.
  5. The @WebResult annotation describes the SOAP response, which in this case is the FindInstitutionsResponse
    1. The 'name' attribute specifies the name of the xml element for the response object
    2. The 'targetNamespace' attribute specifies the namespace of the xml element for the response object
  6. The @WebParam annotation describes the expected SOAP request, which will be matched against any incoming requests on this endpoint.  The 'name' and 'targetNamespace' attributes work exactly the same for this annotation as they do for the @WebResult annotation.  If the incoming request does not contain the exact same elements and namespaces, the request will be rejected.

There is slightly more to it yet, as I still need to define my request and response objects.  If you're looking for them in the code, you'll find them in the fwc-common module.  When a request comes in, the XML request will be unmarshaled to the FindInstitutionsRequest object, and the response XML is obtained by marshaling the FindInstitutionsResponse back to XML.  The names and namespaces of the elements is specified via JAXB annotations.  They are fairly self-explanatory so I won't go into detail describing them.

Transfer Objects 

@XmlRootElement(namespace = "http://fwc.gitter.org/services/")
@XmlType(name = "findInstitutionsRequest")
public class FindInstitutionsRequest {
    public static final String NAME = "findInstitutionsRequest";
 
    private String keyword;
 
    @XmlElement(name = "keyword")
    public String getKeyword() {
        return keyword;
    }
 
    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }
}
 
----------------------------------------------------------------
 
@XmlRootElement(namespace = "http://fwc.gitter.org/services/")
@XmlType(name = "institutionList")
public class FindInstitutionsResponse {
    public static final String NAME = "institutionList";
 
    private List institutions;
 
    @XmlElement(name = "institution")
    public List getInstitutions() {
        return institutions;
    }
 
    public void setInstitutions(List institutions) {
        this.institutions = institutions;
    }
}
 
----------------------------------------------------------------
 
public class Institution {
    private Map<string, string=""> institutionData;
    @XmlElement
    public String getAddress() {
        // I'm cheating - normally you wouldn't do this in a transfer object, but I
        // wanted the SOAP response to be aesthetically pleasing and haven't
        // implemented a domain layer.
        return new StringBuilder().append(institutionData.get("ADDR")).append(" ")
                .append(institutionData.get("CITY")).append(", ")
                .append(institutionData.get("STABBR")).append(" ")
                .append(institutionData.get("ZIP")).toString();
    }
    @XmlTransient
    public Map<string, string=""> getInstitutionData() {
        return institutionData;
    }
    @XmlElement
    public String getInstitutionName() {
        return institutionData.get("INSTNM");
    }
    public void setInstitutionData(Map<string, string=""> institutionData) {
        this.institutionData = institutionData;
    }
}

As an example, if I send this request:

SOAP Request

<!-- HTTP headers omitted -->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <fwc:findInstitutions xmlns:fwc="http://fwc.gitter.org/services/">
            <fwc:findInstitutionsRequest>
                <keyword>Milwaukee WI</keyword>
            </fwc:findInstitutionsRequest>
        </fwc:findInstitutions>
    </soap:Body>
</soap:Envelope>

I should receive a response like this:

SOAP Request

<!-- HTTP headers omitted -->

    <!-- HTTP headers omitted -->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:findInstitutionsResponse xmlns:ns2="http://fwc.gitter.org/services/">
      <ns2:institutionList>
        <institution>
          <address>500 Silverspring Rd Ste K340 Glendale, WI 53217</address>
          <institutionName>Bryant & Stratton College-Bayshore</institutionName>
        </institution>
        <institution>
          <address>10950 W Potter Road Wauwatosa, WI 53226</address>
          <institutionName>Bryant & Stratton College-Wauwatosa</institutionName>
        </institution>
        <institution>
          <address>4425 N Port Washington Rd Glendale, WI 53212</address>
          <institutionName>Columbia College of Nursing</institutionName>
        </institution>
        <!-- snip ... -->
      </ns2:institutionList>
    </ns2:findInstitutionsResponse>
  </soap:Body>
</soap:Envelope>

If you have deployed the application, the wsdl will be visible at URL http://<yourhost>/fwc-ejb/soap-institution/InstitutionService?wsdl.  If you look at the soap:address of the wsdl:service, you'll notice what I was mentioning earlier.  That address will be rewritten by the JBoss webservices subsystem to point at the exposed host and port binding for your server.  This allows you to use the same WSDL for multiple servers, for example in a clustered environment, without having to know where it will be deployed at compile time.

Spring SOAP Services with Spring-WS

For this example, I'm using spring-ws (2.2.0).  As opposed to the EJB3 and JAX-WS method, which is ultimately published by Apache CXF in JBoss, Spring SOAP Services are published by the Spring Container.  This also means that the JBoss webservices subsystem is completely unaware of my deployed Spring-WS soap services.  In other words, I can't depend on JBoss to generate my wsdls, or rewrite the soap:address for me as above automagically.  The first thing we have to do is add a mapping for the Soap requests to the spring-ws dispatcher servlet in the web.xml deployment descriptor.

web.xml

 <web-app>
    <!-- snip -->
    <servlet>
        <servlet-name>webservices</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <init-param>
            <param-name>transformWsdlLocations</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>webservices</servlet-name>
        <url-pattern>/soap/*</url-pattern>
    </servlet-mapping>
</web-app>
 

The transformWsdlLocations parameter informs Spring-WS that we wish for the soap:address to be rewritten to the current binding address.  This performs the same job for us as the JBoss webservices subsystem as laid out above.  In addition to mapping the SpringWS dispatcher servlet, we will have to configure the application context.

applicationContext.xml


<beans>
    <!-- snip -->
    <sws:annotation-driven />
 
    <sws:dynamic-wsdl id="InstitutionService"
        portTypeName="InstitutionService"
        serviceName="InstitutionService"
        locationUri="/soap/institution/InstitutionService">
        <sws:xsd location="/schemas/InstitutionService.xsd" />
    </sws:dynamic-wsdl>
</beans>

The <sws:annotation-driven/> allows me to configure my endpoint via annotations.  In addition to this, I need to setup the wsdl generation via the <sws:dynamic-wsdl> configuration.  This is where you configure the name for the portType and serviceName as well as the endpoint URL.  I also had to configure the static location to a schema document that is used to generate the wsdl.  I built this schema document myself rather than generating it, but there are numerous tools you can use to accomplish this.  In fact, I cheated and simply copied the schema that was produced by CXF from my EJB3 example.  There is a warning in the Spring-WS documentation about the use of the dynamic-wsdl configuration.  Since the generation of wsdls from one version of Spring to the next is not guaranteed to be the same, they recommend that you only use it in development.  Once you're ready to release, they hint that you should copy the generated wsdl and place it in your project to use with the static-wsdl configuration to prevent unintentional changes to your wsdl contract.

That is enough to configure a simple web service.  The rest of the configuration is in the service implementation itself.

SOAP Service Implementation

/* snip - not all imports shown */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
 
@Endpoint
public class InstitutionSoapServiceImpl implements InstitutionSoapService {
 
    private InstitutionService service;
 
    @Autowired
    public InstitutionSoapServiceImpl(InstitutionService service) {
        this.service = service;
    }
 
    @Override
    @PayloadRoot(localPart = "findInstitutionsRequest",
            namespace = InstitutionSoapService.TARGET_NAMESPACE)
    public @ResponsePayload FindInstitutionsResponse findInstitutions(
            @RequestPayload FindInstitutionsRequest request) {
        FindInstitutionsResponse response = new FindInstitutionsResponse();
 
        /* snip */
 
        return response;
    }
}
Here is a breakdown of the annotations:
  1. The @Endpoint annotation acts similarly to the @Component annotation, marking this class a service endpoint implementation and is picked up thanks to the sws:annotation-driven configuration element.  It is analagous to the @WebService annotation.
  2. The @Autowired annotation configures the injection point for the context InstitutionService bean
  3. The @PayloadRoot annotation is the counterpart to the JAX-WS @WebMethod annotation.  Similarly, it sets up the namespace and name of the expected type for a request.
  4. The @ResponsePayload annotation indicates that the method's return value should be bound to the response payload.
  5. The @RequestPayload annotation indicates that a method parameter should be bound to the request payload.

As you may have noticed, this service uses the same transfer objects as it's EJB3/JAX-WS cousin.  It is therefore using the same JAXB configuration for marshaling the request and response payloads.  This, then, is all that is needed.  Here is an example request and response for this Spring-WS service.

Request


<!-- HTTP Headers omitted -->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <fwc:findInstitutionsRequest xmlns:fwc="http://fwc.gitter.org/services/">
            <fwc:keyword>Milwaukee WI</fwc:keyword>
        </fwc:findInstitutionsRequest>
    </soap:Body>
</soap:Envelope>

Response


 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <ns3:findInstitutionsResponse xmlns:ns3="http://fwc.gitter.org/services/" xmlns="">
      <institution>
        <address>500 Silverspring Rd Ste K340 Glendale, WI 53217</address>
        <institutionName>Bryant &amp; Stratton College-Bayshore</institutionName>
      </institution>
      <institution>
        <address>10950 W Potter Road Wauwatosa, WI 53226</address>
        <institutionName>Bryant &amp; Stratton College-Wauwatosa</institutionName>
      </institution>
      <institution>
        <address>4425 N Port Washington Rd Glendale, WI 53212</address>
        <institutionName>Columbia College of Nursing</institutionName>
      </institution>
      <!-- Snip ... -->
    </ns3:findInstitutionsResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Analysis

There are some notable things missing from this article, including some of the reasons that would compel you to use SOAP services instead of REST in the first place.  This includes the entire WS-* stack: reliable messaging, use of transports other than HTTP, support for signed/encrypted/authenticated service calls, and transactionality of service calls.  I decided to break these concepts out into separate articles so I could do them justice.  I'm not sure the gap between EJB3/JAX-WS and Spring-WS is wide enough for the configuration of a simple SOAP service to enable us to make a decision on this alone.  However, I did notice a couple things that are at least worth pointing out.

First, I have a confession.  When I initially wrote the code for the Spring-WS service, I spent several frustrated hours trying to hammer out a working service, specifically when trying to write up a service implementation that would work with the WSDL that I had cobbled together.  My frustration was really due to two things.  First, I chose not to do "Contract-First" service development, which would have, more or less, guaranteed that the implementation would have matched the WSDL.  Second, my lack of experience with Spring-WS configuration had me battling myself for the first few hours.  When I finished getting the service to work, I went through several more passes to understand how the moving parts of Spring-WS were working together and slowly trimmed my configuration down to just the pieces that were necessary to make it work, discovering that some of what I had done was unnecessary or redundant.  So hopefully my analysis remains untainted from my early frustrations.

I noticed that configuring the URL of the endpoint in Spring appeared to be more flexible.  This may differ slightly when using other containers, but the endpoint of my EJB3/JAX-WS service was /<context-root>/<WebService.serviceName>/<WebService.name> or /fwc-ejb/soap-institution/InstitutionService.  Initially, my serviceName was soap/Institution, but that is not a legal name for a wsdl:service in the WSDL.  I got away with it in JBoss, but it would have failed validation, so I changed it.  On the other hand, all the Spring SOAP calls were getting routed through the Spring-WS MessageDispatcherServlet which allows any service endpoint mappings you want.  I mapped the servlet to "/soap/*" and then in my application context, mapped my wsdl endpoint to "/soap/institution/InstitutionService".  What seemed to be a point for Spring-WS turned out to be a side-effect of my choice to use "Contract-Last" development, so it wasn't enough to sway me.  If I had generated the wsdl myself, I could have set the endpoint to whatever I wanted.

So which is better?  There really weren't any notable differences in the two versions of the implementation.  While Spring-WS is certainly a viable option, I was very put off by the fact that my desired Code-First approach wasn't supported.  This alone might make me choose to go with JEE.  In fact, if you choose to use Spring for other reasons, it is possible to plug in Apache CXF to publish your Soap Services and host the JAX-WS service implementation in the first example via CXF in the Spring container.  Honestly, I wasn't expecting to notice any great divergence yet, but I anticipate that I will begin to see some major differences as I delve deeper down the rabbit hole.

 

 

 

 

Related Articles