Introducción a Apache CXF

1. Información general

Apache CXF es un marco totalmente compatible con JAX-WS.

Además de las características definidas por los estándares JAX-WS, Apache CXF proporciona la capacidad de conversión entre las clases WSDL y Java, las API que se utilizan para manipular mensajes XML sin procesar, el soporte para JAX-RS, la integración con Spring Framework, etc.

Este tutorial es el primero de una serie sobre Apache CXF, que presenta características básicas del marco. Solo usa las API estándar JAX-WS en el código fuente mientras aún aprovecha Apache CXF detrás de escena, como los metadatos WSDL generados automáticamente y la configuración predeterminada de CXF.

2. Dependencias de Maven

La dependencia clave necesaria para usar Apache CXF es org.apache.cxf: cxf - rt - frontend - jaxws . Esto proporciona una implementación JAX-WS para reemplazar la JDK incorporada:

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

Observe que este artefacto contiene un archivo denominado javax.xml.ws.spi.Provider dentro del directorio META-INF / services . Java VM examina la primera línea de este archivo para determinar la implementación de JAX-WS que se debe utilizar. En este caso, el contenido de la línea es o rg.apache.cxf.jaxws.spi.ProviderImpl , refiriéndose a la implementación proporcionada por Apache CXF.

En este tutorial, no usamos un contenedor de servlets para publicar el servicio, por lo tanto, se requiere otra dependencia para proporcionar las definiciones de tipo Java necesarias:

 org.apache.cxf cxf-rt-transports-http-jetty 3.1.6 

Para obtener las últimas versiones de estas dependencias, consulte cxf-rt-frontend-jaxws y cxf-rt-transports-http-jetty en el repositorio central de Maven.

3. Punto final del servicio web

Comencemos con la clase de implementación utilizada para configurar el punto final del servicio:

@WebService(endpointInterface = "com.baeldung.cxf.introduction.Baeldung") public class BaeldungImpl implements Baeldung { private Map students = new LinkedHashMap(); public String hello(String name) { return "Hello " + name; } public String helloStudent(Student student) { students.put(students.size() + 1, student); return "Hello " + student.getName(); } public Map getStudents() { return students; } }

Lo más importante que debe notarse aquí es la presencia del atributo endpointInterface en la anotación @WebService . Este atributo apunta a una interfaz que define un contrato abstracto para el servicio web.

Todas las firmas de métodos declaradas en la interfaz del punto final deben implementarse, pero no es necesario para implementar la interfaz.

Aquí, la clase de implementación BaeldungImpl aún implementa la siguiente interfaz de punto final para dejar en claro que todos los métodos declarados de la interfaz se han implementado, pero hacer esto es opcional:

@WebService public interface Baeldung { public String hello(String name); public String helloStudent(Student student); @XmlJavaTypeAdapter(StudentMapAdapter.class) public Map getStudents(); }

De forma predeterminada, Apache CXF usa JAXB como su arquitectura de enlace de datos. Sin embargo, dado que JAXB no admite directamente el enlace de un mapa , que se devuelve desde el método getStudents , necesitamos un adaptador para convertir el mapa en una clase Java que JAXB pueda usar .

Además, para separar los elementos del contrato de su implementación, definimos Student como una interfaz y JAXB tampoco admite directamente interfaces, por lo que necesitamos un adaptador más para lidiar con esto. De hecho, por conveniencia, podemos declarar Student como clase. El uso de este tipo como interfaz es solo una demostración más del uso de clases de adaptación.

Los adaptadores se muestran en la sección a continuación.

4. Adaptadores personalizados

Esta sección ilustra la forma de utilizar las clases de adaptación para admitir el enlace de una interfaz Java y un mapa utilizando JAXB.

4.1. Adaptador de interfaz

Así es como se define la interfaz del estudiante :

@XmlJavaTypeAdapter(StudentAdapter.class) public interface Student { public String getName(); }

Esta interfaz declara solo un método que devuelve una cadena y especifica StudentAdapter como la clase de adaptación para mapearse desde y hacia un tipo que puede aplicar el enlace JAXB.

La clase StudentAdapter se define de la siguiente manera:

public class StudentAdapter extends XmlAdapter { public StudentImpl marshal(Student student) throws Exception { if (student instanceof StudentImpl) { return (StudentImpl) student; } return new StudentImpl(student.getName()); } public Student unmarshal(StudentImpl student) throws Exception { return student; } }

Una clase de adaptación debe implementar la interfaz XmlAdapter y proporcionar implementación para los métodos marshal y unmarshal . El método marshal transforma un tipo vinculado ( Student , una interfaz que JAXB no puede manejar directamente) en un tipo de valor ( StudentImpl , una clase concreta que puede ser procesada por JAXB). El método unmarshal hace las cosas al revés.

Aquí está la definición de la clase StudentImpl :

@XmlType(name = "Student") public class StudentImpl implements Student { private String name; // constructors, getter and setter }

4.2. Adaptador de mapa

El getStudents método de Baeldung interfaz de punto final devuelve un mapa e indica una clase de adaptación para convertir el mapa a un tipo que puede ser manejado por JAXB. De manera similar a la clase StudentAdapter , esta clase de adaptación debe implementar métodos marshal y unmarshal de la interfaz XmlAdapter :

public class StudentMapAdapter extends XmlAdapter
    
      { public StudentMap marshal(Map boundMap) throws Exception { StudentMap valueMap = new StudentMap(); for (Map.Entry boundEntry : boundMap.entrySet()) { StudentMap.StudentEntry valueEntry = new StudentMap.StudentEntry(); valueEntry.setStudent(boundEntry.getValue()); valueEntry.setId(boundEntry.getKey()); valueMap.getEntries().add(valueEntry); } return valueMap; } public Map unmarshal(StudentMap valueMap) throws Exception { Map boundMap = new LinkedHashMap(); for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) { boundMap.put(studentEntry.getId(), studentEntry.getStudent()); } return boundMap; } }
    

La clase StudentMapAdapter asigna Map hacia y desde el tipo de valor StudentMap con la definición de la siguiente manera:

@XmlType(name = "StudentMap") public class StudentMap { private List entries = new ArrayList(); @XmlElement(nillable = false, name = "entry") public List getEntries() { return entries; } @XmlType(name = "StudentEntry") public static class StudentEntry { private Integer id; private Student student; // getters and setters } }

5. Despliegue

5.1. Definición de servidor

Para implementar el servicio web mencionado anteriormente, usaremos las API estándar de JAX-WS. Dado que estamos usando Apache CXF, el marco hace un trabajo adicional, por ejemplo, generar y publicar el esquema WSDL. Así es como se define el servidor de servicios:

public class Server { public static void main(String args[]) throws InterruptedException { BaeldungImpl implementor = new BaeldungImpl(); String address = "//localhost:8080/baeldung"; Endpoint.publish(address, implementor); Thread.sleep(60 * 1000); System.exit(0); } }

Después de que el servidor esté activo durante un tiempo para facilitar las pruebas, debe cerrarse para liberar recursos del sistema. Puede especificar cualquier duración de trabajo para el servidor en función de sus necesidades pasando un argumento largo al método Thread.sleep .

5.2. Despliegue del servidor

En este tutorial, usamos el complemento org.codehaus.mojo: exec -maven- plugin para crear una instancia del servidor ilustrado arriba y controlar su ciclo de vida. Esto se declara en el archivo POM de Maven de la siguiente manera:

 org.codehaus.mojo exec-maven-plugin  com.baeldung.cxf.introduction.Server  

La configuración mainClass se refiere a la clase de servidor donde se publica el punto final del servicio web. Después de ejecutar el objetivo de Java de este complemento, podemos verificar el esquema WSDL generado automáticamente por Apache CXF accediendo a la URL // localhost: 8080 / baeldung? Wsdl .

6. Casos de prueba

Esta sección lo guía a través de los pasos para escribir casos de prueba utilizados para verificar el servicio web que creamos antes.

Tenga en cuenta que debemos ejecutar el objetivo exec: java para iniciar el servidor del servicio web antes de ejecutar cualquier prueba.

6.1. Preparación

El primer paso es declarar varios campos para la clase de prueba:

public class StudentTest { private static QName SERVICE_NAME = new QName("//introduction.cxf.baeldung.com/", "Baeldung"); private static QName PORT_NAME = new QName("//introduction.cxf.baeldung.com/", "BaeldungPort"); private Service service; private Baeldung baeldungProxy; private BaeldungImpl baeldungImpl; // other declarations }

El siguiente bloque inicializador se utiliza para iniciar el campo de servicio del tipo javax.xml.ws.Service antes de ejecutar cualquier prueba:

{ service = Service.create(SERVICE_NAME); String endpointAddress = "//localhost:8080/baeldung"; service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress); }

Después de agregar la dependencia JUnit al archivo POM, podemos usar la anotación @Before como en el fragmento de código a continuación. Este método se ejecuta antes de cada prueba para volver a crear una instancia de los campos de Baeldung :

@Before public void reinstantiateBaeldungInstances() { baeldungImpl = new BaeldungImpl(); baeldungProxy = service.getPort(PORT_NAME, Baeldung.class); }

La variable baeldungProxy es un proxy para el punto final del servicio web, mientras que baeldungImpl es solo un objeto Java simple. Este objeto se utiliza para comparar los resultados de las invocaciones de métodos de punto final remoto a través del proxy con invocaciones de métodos locales.

Tenga en cuenta que una instancia de QName se identifica mediante dos partes: un URI de espacio de nombres y una parte local. Si el PORT_NAME argumento, de la QName tipo, de la Service.getPort se omite método, Apache CXF asumirá de ese argumento URI de espacio es el nombre del paquete de la interfaz de punto final en el orden inverso y su parte local es el nombre de la interfaz anexado por el puerto , que es exactamente el mismo valor de PORT_NAME. Por lo tanto, en este tutorial podemos omitir este argumento.

6.2. Implementación de prueba

El primer caso de prueba que ilustramos en esta subsección es validar la respuesta devuelta por una invocación remota del método hello en el punto final del servicio:

@Test public void whenUsingHelloMethod_thenCorrect() { String endpointResponse = baeldungProxy.hello("Baeldung"); String localResponse = baeldungImpl.hello("Baeldung"); assertEquals(localResponse, endpointResponse); }

It is clear that the remote endpoint method returns the same response as the local method, meaning the web service works as expected.

The next test case demonstrates the use of helloStudent method:

@Test public void whenUsingHelloStudentMethod_thenCorrect() { Student student = new StudentImpl("John Doe"); String endpointResponse = baeldungProxy.helloStudent(student); String localResponse = baeldungImpl.helloStudent(student); assertEquals(localResponse, endpointResponse); }

In this case, the client submits a Student object to the endpoint and receives a message containing the student's name in return. Like the previous test case, the responses from both remote and local invocations are the same.

The last test case that we show over here is more complicated. As defined by the service endpoint implementation class, each time the client invokes the helloStudent method on the endpoint, the submitted Student object will be stored in a cache. This cache can by retrieved by calling the getStudents method on the endpoint. The following test case confirms that content of the students cache represents what the client has sent to the web service:

@Test public void usingGetStudentsMethod_thenCorrect() { Student student1 = new StudentImpl("Adam"); baeldungProxy.helloStudent(student1); Student student2 = new StudentImpl("Eve"); baeldungProxy.helloStudent(student2); Map students = baeldungProxy.getStudents(); assertEquals("Adam", students.get(1).getName()); assertEquals("Eve", students.get(2).getName()); }

7. Conclusion

This tutorial introduced Apache CXF, a powerful framework to work with web services in Java. It focused on the application of the framework as a standard JAX-WS implementation, while still making use of the framework's specific capabilities at run-time.

La implementación de todos estos ejemplos y fragmentos de código se puede encontrar en un proyecto de GitHub.