1. Overview

This tutorial focuses on configuring and using the Apache CXF framework together with Spring – either with Java or XML configuration.

It’s the second in a series on Apache CXF; the first one focused on the fundamentals of CXF as an implementation of the JAX-WS standard APIs.

2. Maven Dependencies

Similar to the previous tutorial, the following two dependencies need to be included:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.6</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>3.1.6</version>
</dependency>

For the latest versions of Apache CXF artifacts, please check out apache-cxf.

In addition, the following dependencies are necessary to support Spring:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.25</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.25</version>
</dependency>

The latest versions of Spring artifacts can be found here.

Finally, because we’ll programmatically configure the application using the Java Servlet 3.0+ API instead of a traditional web.xml deployment descriptor, we’ll need the artifact below:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.1.0</version>
</dependency>

This is where we can find the latest version of the Servlet API.

3. Server-Side Components

Let’s now have a look at the components that need to be present on the server side in order to publish the web service endpoint.

3.1. WebApplicationInitilizer Interface

The WebApplicationInitializer interface is implemented to programmatically configure the ServletContext interface for the application. When present on the classpath, its onStartup method is automatically invoked by the servlet container and thereafter the ServletContext is instantiated and initialized.

Here is how a class is defined to implement the WebApplicationInitializer interface:

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        // Method implementation
    }
}

The onStartup() method is implemented using code snippets shown below.

First, a Spring application context is created and configured to register a class containing configuration metadata:

AnnotationConfigWebApplicationContext context 
  = new AnnotationConfigWebApplicationContext();
context.register(ServiceConfiguration.class);

The ServiceConfiguration class is annotated with the @Configuration annotation to provide bean definitions. This class is discussed in the next subsection.

The following snippet shows how the Spring application context is added to the servlet context:

container.addListener(new ContextLoaderListener(context));

The CXFServlet class, which is defined by Apache CXF, is generated and registered to handle incoming requests:

ServletRegistration.Dynamic dispatcher 
  = container.addServlet("dispatcher", new CXFServlet());

The application context loads Spring elements defined in a configuration file. In this case, the name of the servlet is cxf, therefore the context looks for those elements in a file named cxf-servlet.xml by default.

Lastly, the CXF servlet is mapped to a relative URL:

dispatcher.addMapping("/services");

3.2. The Good Old web.xml

Alternatively, if we want to make use of a (somewhat old-fashioned) deployment descriptor rather than the WebApplicationInitilizer interface, the corresponding web.xml file should contain the following servlet definitions:

<servlet>
    <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>

3.3. ServiceConfiguration Class

Let’s now have a look at the service configuration – first a basic skeleton which encloses bean definitions for the web service endpoint:

@Configuration
public class ServiceConfiguration {
    // Bean definitions
}

The first required bean is the SpringBus – which supplies extensions for Apache CXF to work with the Spring Framework:

@Bean
public SpringBus springBus() {
    return new SpringBus();
}

An EnpointImpl bean also needs to be created using the SpringBus bean and a web service implementor. This bean is used to publish the endpoint at the given HTTP address:

@Bean
public Endpoint endpoint() {
    EndpointImpl endpoint = new EndpointImpl(springBus(), new BaeldungImpl());
    endpoint.publish("http://localhost:8080/services/baeldung");
    return endpoint;
}

The BaeldungImpl class is used to implement the web service interface. Its definition is given in the next subsection.

Alternatively, we may also declare the server endpoint in an XML configuration file. Specifically, the cxf-servlet.xml file below works with the web.xml deployment descriptor as was defined in subsection 3.1 and describes the exact same endpoint:

<jaxws:endpoint
  id="baeldung"
  implementor="com.baeldung.cxf.spring.BaeldungImpl"
  address="http://localhost:8080/services/baeldung" />

Note that the XML configuration file is named after the servlet name defined in the deployment descriptor, which is cxf.

3.4. Type Definitions

Next – here is the definition of the implementor that has already been mentioned in the preceding subsection:

@WebService(endpointInterface = "com.baeldung.cxf.spring.Baeldung")
public class BaeldungImpl implements Baeldung {
    private int counter;

    public String hello(String name) {
        return "Hello " + name + "!";
    }

    public String register(Student student) {
        counter++;
        return student.getName() + " is registered student number " + counter;
    }
}

This class provides an implementation for the Baeldung endpoint interface that Apache CXF will include in the published WSDL metadata:

@WebService
public interface Baeldung {
    String hello(String name);
    String register(Student student);
}

Both the endpoint interface as well as the implementor make use of the Student class, which is defined as follows:

public class Student {
    private String name;

    // constructors, getters and setters
}

4. Client-Side Beans

To take advantage of the Spring Framework, we declare a bean in a @Configuration annotated class:

@Configuration
public class ClientConfiguration {
    // Bean definitions
}

A bean with the name of client is defined:

@Bean(name = "client")
public Object generateProxy() {
    return proxyFactoryBean().create();
}

The client bean represents a proxy for the Baeldung web service. It is created by an invocation to the create method on a JaxWsProxyFactoryBean bean, a factory for the creation of JAX-WS proxies.

The JaxWsProxyFactoryBean object is created and configured by the following method:

@Bean
public JaxWsProxyFactoryBean proxyFactoryBean() {
    JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
    proxyFactory.setServiceClass(Baeldung.class);
    proxyFactory.setAddress("http://localhost:8080/services/baeldung");
    return proxyFactory;
}

The factory’s serviceClass property denotes the web service interface, while the address property indicates the URL address for the proxy to make remote invocations.

Also for the Spring beans on the client side one may revert to an XML configuration file. The following elements declare the same beans as the ones we just have programmatically configured above:

<bean id="client" factory-bean="clientFactory" factory-method="create" />
<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
    <property name="serviceClass" value="com.baeldung.cxf.spring.Baeldung" />
    <property name="address" value="http://localhost:8080/services/baeldung" />
</bean>

5. Test Cases

This section describes test cases used to illustrate Apache CXF support for Spring. The test cases are defined in a class named StudentTest.

First, we need to load a Spring application context from the aforementioned ServiceConfiguration configuration class and cache it in the context field:

private ApplicationContext context 
  = new AnnotationConfigApplicationContext(ClientConfiguration.class);

Next, a proxy for the service endpoint interface is declared and loaded from the application context:

private Baeldung baeldungProxy = (Baeldung) context.getBean("client");

This Baeldung proxy will be used in test cases described below.

In the first test case, we prove that when the hello method is locally invoked on the proxy, the response is exactly the same as what the endpoint implementor returns from the remote web service:

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String response = baeldungProxy.hello("John Doe");
    assertEquals("Hello John Doe!", response);
}

In the second test case, students register for Baeldung courses by locally invoking the register method on the proxy, which in turn calls the web service. That remote service will then calculate the student numbers and return them to the caller. The following code snippet confirms what we expect:

@Test
public void whenUsingRegisterMethod_thenCorrect() {
    Student student1 = new Student("Adam");
    Student student2 = new Student("Eve");
    String student1Response = baeldungProxy.register(student1);
    String student2Response = baeldungProxy.register(student2);

    assertEquals("Adam is registered student number 1", student1Response);
    assertEquals("Eve is registered student number 2", student2Response);
}

6. Integration Testing

In order to be deployed as a web application on a server, code snippets in this tutorial need to be packaged into a WAR file first. This can be achieved by declaring the packaging property in the POM file:

<packaging>war</packaging>

The packaging job is implemented by the Maven WAR plugin:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.4.0</version>
    <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
</plugin>

This plugin packages the compiled source code into a WAR file. Since we configure the servlet context using Java code, the traditional web.xml deployment descriptor does not need to be existent. As the result, the failOnMissingWebXml property must be set to false to avoid failure when the plugin is executed.

We can follow this link for the most recent version of the Maven WAR plugin.

In order to illustrate operations of the web service, we create an integration test. This test first generates a WAR file and starts an embedded server, then makes clients invoke the web service, verifies subsequent responses and finally stops the server.

The following plugins need to be included in the Maven POM file. For more details, please check out this Integration Testing tutorial.

Here is the Maven Surefire plugin:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <excludes>
            <exclude>StudentTest.java</exclude>
        </excludes>
    </configuration>
</plugin>

The latest version of this plugin can be found here.

A profile section with the id of integration is declared to facilitate the integration test:

<profiles>
   <profile>
      <id>integration</id>
      <build>
         <plugins>
            ...
         </plugins>
      </build>
   </profile>
</profiles>

The Maven Cargo plugin is included in the integration profile:

<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.5.0</version>
    <configuration>
        <container>
            <containerId>jetty9x</containerId>
            <type>embedded</type>
        </container>
        <configuration>
            <properties>
                <cargo.hostname>localhost</cargo.hostname>
                <cargo.servlet.port>8080</cargo.servlet.port>
            </properties>
        </configuration>
    </configuration>
    <executions>
        <execution>
            <id>start-server</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>stop-server</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Note that the cargo.hostname and cargo.servlet.port configuration properties are merely included for the sake of clarity. These configuration properties may be left out without any impact on the application since their values are the same as the default values. This plugin starts the server, waits for connections and finally stops the server to release system resources.

This link allows us to check out the latest version of the Maven Cargo plugin.

The Maven Surefire plugin is declared again, within the integration profile, to override its configuration in the main build section and to execute test cases described in the previous section:

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>test</goal>
            </goals>
            <configuration>
                <excludes>
                    <exclude>none</exclude>
                </excludes>
            </configuration>
        </execution>
    </executions>
</plugin>

Now the entire process can be run by the command: mvn -Pintegration clean install.

7. Conclusion

This tutorial illustrated Apache CXF support for Spring. In particular, it has been shown how a web service may be published using a Spring configuration file, and how a client may interact with that service through a proxy created by an Apache CXF proxy factory, which was declared in another configuration file.

The implementation of all these examples and code snippets can be found in the linked GitHub project.


« 上一篇: PITest 测试
» 下一篇: Spring 4.3 新特性