/, Spring Boot, Web Services/SOAP Web Services with Apache CXF and Spring Boot

This post is based on one I wrote a few years ago about building contract first web services with Apache CXF and Spring. The previous post didnt use Spring Boot and most of the Spring and CXF configuration was via XML. This post moves things forward a bit by using the latest version of CXF and Spring Boot.

Sample App

We’re going to build a simple Spring Boot app that exposes SOAP web service using Apache CXF.  The service will have a single operation that takes an account number and returns bank account details. If you’re impatient and want to jump ahead you can grab the full source code from GitHub.

Defining the Data Model

When building web services I always take a contract first approach. That means defining the service contract as a WSDL before writing the service implementation. We’ll begin that process by creating an XSD file with the types that the Account Service will use. The first type we’ll create is Account – the diagram below shows an XSD snippet for Account as well as a visual representation taken from XML Spy. Note: you don’t need XML Spy to define XSDs but a visual editor can be very handy if you’re designing complex domain models.

Account Type

Next, we’ll define a request type for the service that encapsulates service parameters.  In this case, we have only one parameter, accountNumber, but its good practice to define a request type as its a nice way of wrapping multiple parameters and keeps the interface clean.

AccountDetailsRequest Type

Finally, we’ll define the response type AccountDetailsResponse which is a simple wrapper for Account we created earlier. Again, we could have simply returned Account but I think it’s generally a good idea to use wrappers for the response type as it means that we can easily add other data to the response wrapper in the future.

AccountDetailsResponse Type

The full schema definition is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://com/blog/samples/webservices/accountservice" xmlns:account="http://webservices.samples.blog.com" targetNamespace="http://com/blog/samples/webservices/accountservice" elementFormDefault="qualified">
 <xsd:complexType name="Account">
  <xsd:sequence>
   <xsd:element name="AccountNumber" type="xsd:string"/>
   <xsd:element name="AccountName" type="xsd:string"/>
   <xsd:element name="AccountBalance" type="xsd:double"/>
   <xsd:element name="AccountStatus" type="EnumAccountStatus"/>
  </xsd:sequence>
 </xsd:complexType> 
 <xsd:simpleType name="EnumAccountStatus">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="Active"/>
   <xsd:enumeration value="Inactive"/>
  </xsd:restriction>
 </xsd:simpleType>
 <xsd:element name="AccountDetailsRequest">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="accountNumber" type="xsd:string"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
 <xsd:element name="AccountDetailsResponse">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="AccountDetails" type="Account"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
</xsd:schema>

Defining the Service WSDL

Now that we’ve defined the service types via XSD, its time to create a WSDL to define the public facing service contract. A WSDL is an XML document that describes a SOAP Web Service and how clients can interact with it. The Account Service WSDL is defined as follows.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap12/"
 xmlns:tns="http://www.briansjavablog.com/Accounts/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Accounts"
 targetNamespace="http://www.briansjavablog.com/Accounts/"
 xmlns:accounts="http://com/blog/samples/webservices/accountservice">
 <wsdl:types>
  <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:import namespace="http://com/blog/samples/webservices/accountservice"
    schemaLocation="../schema/AccountsService.xsd">
   </xsd:import>
  </xsd:schema>
 </wsdl:types>
 <wsdl:message name="AccountDetailsRequest">
  <wsdl:part element="accounts:AccountDetailsRequest" name="parameters" />
 </wsdl:message>
 <wsdl:message name="AccountDetailsResponse">
  <wsdl:part element="accounts:AccountDetailsResponse" name="parameters" />
 </wsdl:message>
 <wsdl:portType name="Accounts">
  <wsdl:operation name="GetAccountDetails">
   <wsdl:input message="tns:AccountDetailsRequest" />
   <wsdl:output message="tns:AccountDetailsResponse" />
  </wsdl:operation>
 </wsdl:portType>
 <wsdl:binding name="AccountsServiceSoapBinding" type="tns:Accounts">
  <soap:binding style="document"
   transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="GetAccountDetails">
   <soap:operation
    soapAction="http://www.briansjavablog.com/Accounts/GetAccountDetails" />
   <wsdl:input>
    <soap:body use="literal" />
   </wsdl:input>
   <wsdl:output>
    <soap:body use="literal" />
   </wsdl:output>
  </wsdl:operation>
 </wsdl:binding>
 <wsdl:service name="AccountsService">
  <wsdl:port binding="tns:AccountsServiceSoapBinding" name="AccountsPort">
   <soap:address
    location="http://localhost:8080/apache-cfx-demo/services/accounts" />
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>

The WSDL contains 5 key pieces of information

  • Types – <wsdl:types> defines the domain model used by the service. The model is defined via XSD and can be included inline, in the WSDL or imported from a separate XSD. Line 9 above imports the XSD file we created earlier.
  • Message – <wsdl:message> defines the request and response messages used by the service. The nested <wsdl:part> section defines the domain types of the request and response messages.
  • PortType – <wsdl:portType> defines the service operations, parameters and response types exposed to clients.
  • Binding – <wsdl:binding> defines the protocol and data format.
    • The binding type attribute refers to the portType defined earlier in the WSDL.
    • The soap binding style can be either RPC or document.
    • The transport attribute indicates that the service will be exposed over HTTP. Other options (less common) include JMS and SMTP.
    • The operation element defines each operation that we exposed through the portType.
    • Binding – <wsdl:binding> defines the protocol and data format.
  • Service – <wsdl:service> defines the exposed service using the portType and binding we defined above.

Generating the Service Interface and Domain Model

At this point, we have a WSDL that defines a contract for the service we’re going to build. The next step is to use the WSDL to generate Java classes for the domain model and service interface. We’re going to use the CXF codegen plugin to run a WSDL2Java job as part of the build. The codegen plugin is defined in the project POM as follows.

<plugin>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-codegen-plugin</artifactId>
  <executions>
    <execution>
      <id>generate-sources</id>
      <phase>generate-sources</phase>
      <configuration>
        <sourceRoot>src/generated/java</sourceRoot>
        <wsdlOptions>
          <wsdlOption>
            <wsdl>${basedir}/src/main/resources/wsdl/Accounts.wsdl</wsdl>
          </wsdlOption>
        </wsdlOptions>
      </configuration>
      <goals>
        <goal>wsdl2java</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The sourceRoot element tells the plugin to put the generated java classes in a new source directory src/generated/java. This separates the generate classes from classes we’ll write ourselves.  The wsdl element points to the WSDL file we created earlier.

To run the code generation open a command window and run mvn generate-sources.

Refresh your IDE workspace and you’ll see 2 new packages. By default, the package names are based on the namespaces in the WSDL. The contents of both packages are described below.

  • com.blog.demo.webservices.accountservice – contains the 4 domain objects, AccountAccountDetailsRequest,  AccountDetailsResponse and EnumAccountStatus. These are the core types we defined in the XSD earlier and will form the main building blocks of the service.
  • com.briansdevblog.accounts – contains the Service Endpoint Interface AccountService. This interface is a Java representation of the service operation defined in the WSDL.  AccountService_Service. is a web service client and can be used to invoke the service.

Service Endpoint Interface

The generated Service Endpoint Interface AccountService contains a method for each operation defined in the WSDL. As you’d expect, the method parameter and return type are those defined in the XSD and referenced in the WSDL.
@WebService(targetNamespace = "http://www.briansdevblog.com/Accounts/", name = "AccountService")
@XmlSeeAlso({com.blog.demo.webservices.accountservice.ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface AccountService {

    @WebMethod(operationName = "GetAccountDetails", action = "http://www.briansjavablog.com/Accounts/GetAccountDetails")
    @WebResult(name = "AccountDetailsResponse", targetNamespace = "http://com/blog/demo/webservices/accountservice", partName = "parameters")
    public com.blog.demo.webservices.accountservice.AccountDetailsResponse getAccountDetails(

        @WebParam(partName = "parameters", name = "AccountDetailsRequest", targetNamespace = "http://com/blog/demo/webservices/accountservice")
        com.blog.demo.webservices.accountservice.AccountDetailsRequest parameters
    );
}
  • @WebService – Marks the class as defining a Web Service interface from a WSDL. The namespace should match the namespace defined in the WSDL and the name should match the WSDL PortType.
  • @XmlSeeAlso – Lets JAXB know what other classes need to be registered with the JAXB context for serialization and deserialization.
  • @SoapBinding – Describes mapping from web service operations to SOAP protocol
  • @WebMethod – Maps a service operation to a Java method. The operation name references the operation defined in the WSDL and the target namespace uses the namespace associated with the WSDL operation.
  • @WebResult – Maps a service operation response message to a Java return type. The name refers to the response message name defined in the WSDL. The target namespace uses the namespace associated with the WSDL message and the partName refers to wdl:part name in the WSDL.
  • @WebParam – Maps a service operation request message to a Java parameter type. The name refers to the request message name defined in the WSDL. The target namespace uses the namespace associated with the WSDL message and the partName refers to wdl:part name in the WSDL.

Writing the Service Endpoint

Next, we’re going to create the service endpoint by implementing the AccountService interface.

@Service
public class AccountServiceEndpoint implements AccountService {
  
  @Override
  public AccountDetailsResponse getAccountDetails(AccountDetailsRequest parameters) {

    ObjectFactory factory = new ObjectFactory();
    AccountDetailsResponse response = factory.createAccountDetailsResponse();
    
    Account account = factory.createAccount();
    account.setAccountNumber("12345");
    account.setAccountStatus(EnumAccountStatus.ACTIVE);
    account.setAccountName("Joe Bloggs");
    account.setAccountBalance(3400);
    
    response.setAccountDetails(account);		
    return response;
  }
  
}

The endpoint implements the getAccountDetails method on the AccountService interface. To keep things simple the method body returns some hard-coded Account data wrapped in a AccountDetailsResponse.

Configuring the Service with Spring

In the past, we would have configured the service endpoint via XML, but now that we’re using Spring Boot we can move all our configuration to Java. XML config was ok, but it’s hard to beat configuring Spring with Java. The ApplicationConfig class below contains all the configuration required to run the service. I’ll describe each bean below.

@Configuration
public class ApplicationConfig {

  @Bean
  public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
    return new ServletRegistrationBean<CXFServlet>(new CXFServlet(), "/soap-api/*");
  }
  
  @Bean
  @Primary
  public DispatcherServletPath dispatcherServletPathProvider() {
      return () -> "";
  }

  @Bean(name=Bus.DEFAULT_BUS_ID)
  public SpringBus springBus(LoggingFeature loggingFeature) {
    
    SpringBus cxfBus = new  SpringBus();
    cxfBus.getFeatures().add(loggingFeature);
    
    return cxfBus;
  }

  @Bean
  public LoggingFeature loggingFeature() {
    
    LoggingFeature loggingFeature = new LoggingFeature();
    loggingFeature.setPrettyLogging(true);
    
    return loggingFeature;
  }

  @Bean
  public Endpoint endpoint(Bus bus, AccountServiceEndpoint accountServiceEndpoint) {

    EndpointImpl endpoint = new EndpointImpl(bus, accountServiceEndpoint);
    endpoint.publish("/service/accounts");
    
    return endpoint;
  }

}
  • ServletRegistrationBean<CXFServlet> – registers the CXF dispatcher servlet to handle incoming HTTP requests to /soap-api/*.  The dispatcher servlet essentially routes requests to an endpoint for processing.
  • SpringBus – is a Spring flavoured CXF Bus. A Bus is a core CXF extension point that allows you to add interceptors to any CXF client or endpoint that uses the bus.  In the example above we add the injected LoggingFetaure to enable logging.
  • LoggingFeature – A Feature is something that adds some functionality to a CXF client or server. In this instance the LoggingFeature performs logging of the inbound and outbound SOAP payload. I’ve enabled pretty logging to make the SOAP messages a little more readable.
  • Endpoint – exposes a HTTP endpoint to process incoming SOAP requests. The publish method tells CXF to publish the endpoint at /service/accounts. This path will be appended to the /soap-api/* pattern used to configure the CXFServlet earlier. This means the endpoint is exposed at CONTEXT_ROOT/soap-api/service/accounts

Writing an Integration Test

Next, we’re going to write an integration test to make sure everything is working as expected. The test will do the following

  • Use Jetty to stand up and an instance of the endpoint at http://localhost:8080/services/accounts
  • Create a web service client – SOAP proxy to handle serialization of request and deserialization of response
  • Create an AccountDetailsRequest, send it to the SOAP endpoint and check the contents of the AccountDetailsResponse
  • Tear down the test endpoint

You’ll notice that the test doesn’t use ApplicationConfig we defined earlier but instead uses the following test specific config.

@Configuration
@ComponentScan("com.blog.demo.service")
public class TestConfig {

  private static final String SERVICE_URL = "http://localhost:8080/services/accounts";
  
  @Bean("accountServiceClient")
  public AccountService accountServiceClient() {

    JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
    jaxWsProxyFactoryBean.setServiceClass(AccountService.class);
    jaxWsProxyFactoryBean.setAddress(SERVICE_URL);		    
    return (AccountService) jaxWsProxyFactoryBean.create();
  }
  
  @Bean(name=Bus.DEFAULT_BUS_ID)
  public SpringBus springBus(LoggingFeature loggingFeature) {
    
    SpringBus bus = new  SpringBus();
    bus.getFeatures().add(loggingFeature);
    
    return bus;
  }

  @Bean
  public LoggingFeature loggingFeature() {
    
    LoggingFeature loggingFeature = new LoggingFeature();
    loggingFeature.setPrettyLogging(true);
    
    return loggingFeature;
  }
  
  @Bean
  public Endpoint endpoint(Bus bus, LoggingFeature loggingFeature, AccountServiceEndpoint accountServiceEndpoint) {
    
    EndpointImpl endpoint = new EndpointImpl(bus, accountServiceEndpoint);
    endpoint.publish(SERVICE_URL);
    
    return endpoint;
  }

The accountServiceClient method uses the JaxWsProxyFactoryBean to create a web service client for the AccountService Service Endpoint Interface. The client is configured to call the endpoint at http://localhost:8080/services/accounts. In order to stand up a test instance of the endpoint, we also configure a SpringBus , LoggingFeature and Endpoint similar to the way we did in ApplicationConfig.

The AccountServiceEndpointTest below uses the injected accountServiceClient from TestConfig.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class AccountServiceEndpointTest {
  
  @Autowired
  @Qualifier("accountServiceClient")
  private AccountService accountsServiceClient;
  private AccountDetailsRequest accountDetailsRequest;
  
  @Before
  public void setUp() throws Exception {
    
    ObjectFactory objectFactory = new ObjectFactory();
    accountDetailsRequest = objectFactory.createAccountDetailsRequest();
    accountDetailsRequest.setAccountNumber("12345");
  }

  @Test
  public void testGetAccountDetails() throws Exception {
    
    AccountDetailsResponse response = accountsServiceClient.getAccountDetails(accountDetailsRequest);
    assertTrue(response.getAccountDetails()!= null);
    assertTrue(response.getAccountDetails().getAccountNumber().equals("12345"));
    assertTrue(response.getAccountDetails().getAccountName().equals("Joe Bloggs"));
    assertTrue(response.getAccountDetails().getAccountBalance() == 3400);
    assertTrue(response.getAccountDetails().getAccountStatus().equals(EnumAccountStatus.ACTIVE));
  }

}

Running the Test

You can run the integration test in your IDE or fire up the command line and run mvn test. You’ll see Jetty start on port 8080 and the SOAP request/response payloads logged when the endpoint is called.

Integration Test Output

Running as a Stand Alone Service

At this point, we’ve run the integration test and everything is behaving as expected. The only thing left to do is to fire up the service with Spring Boot.  On the command line run mvn spring-boot:run. The service should start on port 8090 as shown below.

Service Start

If you browse to http://localhost:8090/soap-api you’ll see the standard CXF service page with the Account Service listed and a link to the WSDL.

You can now test the service using any standard HTTP client. Below I use Postman to send a POST request to http://localhost:8090/soap-api/service/accounts. The SOAP response is displayed as expected.

Wrapping Up

In this post, we built a SOAP Web Service from scratch using Apache CXF and Spring Boot. We began by defining the data model and WSDL (contract first) and then moved on to implement the service endpoint. We looked at how a CXF endpoint is configured in Spring Boot and also put together a simple integration test. The full source code for this post is available on Github. As always, feel free to post comments or questions below.

By |2019-10-02T08:14:28+01:00June 19th, 2019|Spring, Spring Boot, Web Services|0 Comments

Leave A Comment