Eine Anleitung zu Apache CXF mit Spring

1. Übersicht

Dieses Tutorial konzentriert sich auf die Konfiguration und Verwendung des Apache CXF-Frameworks zusammen mit Spring - entweder mit Java- oder XML-Konfiguration.

Es ist der zweite Teil einer Reihe über Apache CXF. Die erste konzentrierte sich auf die Grundlagen von CXF als Implementierung der JAX-WS-Standard-APIs.

2. Maven-Abhängigkeiten

Ähnlich wie im vorherigen Lernprogramm müssen die folgenden zwei Abhängigkeiten enthalten sein:

 org.apache.cxf cxf-rt-frontend-jaxws 3.1.6   org.apache.cxf cxf-rt-transports-http 3.1.6 

Die neuesten Versionen von Apache CXF-Artefakten finden Sie unter apache-cxf.

Darüber hinaus sind die folgenden Abhängigkeiten erforderlich, um Spring zu unterstützen:

 org.springframework spring-context 4.3.1.RELEASE   org.springframework spring-webmvc 4.3.1.RELEASE 

Die neuesten Versionen von Spring-Artefakten finden Sie hier.

Da wir die Anwendung programmgesteuert mithilfe der Java Servlet 3.0+ API anstelle eines herkömmlichen Deployment-Deskriptors für web.xml konfigurieren , benötigen wir das folgende Artefakt:

 javax.servlet javax.servlet-api 3.1.0 

Hier finden wir die neueste Version der Servlet-API.

3. Serverseitige Komponenten

Schauen wir uns nun die Komponenten an, die auf der Serverseite vorhanden sein müssen, um den Webdienst-Endpunkt zu veröffentlichen.

3.1. WebApplicationInitilizer- Schnittstelle

Die WebApplicationInitializer- Schnittstelle wird implementiert, um die ServletContext- Schnittstelle für die Anwendung programmgesteuert zu konfigurieren . Wenn im Klassenpfad vorhanden, wird seine onStartup- Methode automatisch vom Servlet-Container aufgerufen, und danach wird der ServletContext instanziiert und initialisiert.

So wird eine Klasse definiert, um die WebApplicationInitializer- Schnittstelle zu implementieren :

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

Die onStartup () -Methode wird mithilfe der unten gezeigten Codefragmente implementiert.

Zunächst wird ein Spring-Anwendungskontext erstellt und konfiguriert, um eine Klasse mit Konfigurationsmetadaten zu registrieren:

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

Die ServiceConfiguration- Klasse wird mit der Annotation @Configuration versehen , um Bean-Definitionen bereitzustellen. Diese Klasse wird im nächsten Unterabschnitt behandelt.

Das folgende Snippet zeigt, wie der Spring-Anwendungskontext zum Servlet-Kontext hinzugefügt wird:

container.addListener(new ContextLoaderListener(context));

Die von Apache CXF definierte CXFServlet- Klasse wird generiert und registriert, um eingehende Anforderungen zu verarbeiten:

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

Der Anwendungskontext lädt in einer Konfigurationsdatei definierte Spring-Elemente. In diesem Fall lautet der Name des Servlets cxf . Daher sucht der Kontext standardmäßig nach diesen Elementen in einer Datei mit dem Namen cxf-servlet.xml .

Zuletzt wird das CXF-Servlet einer relativen URL zugeordnet:

dispatcher.addMapping("/services");

3.2. Die gute alte web.xml

Wenn Sie alternativ einen (etwas altmodischen) Bereitstellungsdeskriptor anstelle der WebApplicationInitilizer- Schnittstelle verwenden möchten , sollte die entsprechende web.xml- Datei die folgenden Servlet-Definitionen enthalten:

 cxf org.apache.cxf.transport.servlet.CXFServlet 1   cxf /services/* 

3.3. ServiceConfiguration- Klasse

Schauen wir uns nun die Dienstkonfiguration an - zunächst ein Grundgerüst, das Bean-Definitionen für den Webdienst-Endpunkt enthält:

@Configuration public class ServiceConfiguration { // Bean definitions }

Die erste erforderliche Bean ist der SpringBus, der Erweiterungen für Apache CXF für die Arbeit mit dem Spring Framework bereitstellt :

@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("//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:

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("//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:

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:

war

The packaging job is implemented by the Maven WAR plugin:

 maven-war-plugin 2.6  false  

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:

 maven-surefire-plugin 2.19.1   StudentTest.java   

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:

  integration   ...    

The Maven Cargo plugin is included in the integration profile:

 org.codehaus.cargo cargo-maven2-plugin 1.5.0   jetty9x embedded    localhost 8080      start-server pre-integration-test  start    stop-server post-integration-test  stop    

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.

Das Maven Surefire-Plugin wird innerhalb des Integrationsprofils erneut deklariert, um seine Konfiguration im Haupt- Build- Abschnitt zu überschreiben und die im vorherigen Abschnitt beschriebenen Testfälle auszuführen:

 maven-surefire-plugin 2.19.1   integration-test  test    none     

Jetzt kann der gesamte Prozess mit dem folgenden Befehl ausgeführt werden: mvn -Pintegration clean install .

7. Fazit

Dieses Tutorial veranschaulicht die Apache CXF-Unterstützung für Spring. Insbesondere wurde gezeigt, wie ein Webdienst mithilfe einer Spring-Konfigurationsdatei veröffentlicht werden kann und wie ein Client mit diesem Dienst über einen Proxy interagieren kann, der von einer Apache CXF-Proxy-Factory erstellt wurde, die in einer anderen Konfigurationsdatei deklariert wurde.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie im verknüpften GitHub-Projekt.