Introdução ao Apache CXF

Introdução ao Apache CXF

1. Visão geral

Apache CXF é uma estrutura totalmente compatível com JAX-WS.

Além dos recursos definidos pelos padrões JAX-WS, o Apache CXF oferece a capacidade de conversão entre classes WSDL e Java, APIs usadas para manipular mensagens XML brutas, suporte para JAX-RS, integração com o Spring Framework, etc.

Este tutorial é a primeira de uma série no Apache CXF, apresentando as características básicas da estrutura. Ele usa apenas as APIs padrão JAX-WS no código-fonte e ainda aproveita o Apache CXF nos bastidores, como metadados WSDL gerados automaticamente e configuração padrão do CXF.

2. Dependências do Maven

A dependência chave necessária para usar o Apache CXF éorg.apache.cxf:[.s1]cxfrtfrontendjaxws. This provides a JAX-WS implementation to replace the built-in JDK one:


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

Notice that this artifact contains a file named javax.xml.ws.spi.Provider inside the META-INF/services directory. Java VM looks at the first line of this file to determine the JAX-WS implementation to make use of. In this case, content of the line is org.apache.cxf.jaxws.spi.ProviderImpl, referindo-se à implementação fornecida pelo Apache CXF.

Neste tutorial, não usamos um contêiner de servlet para publicar o serviço; portanto, é necessária outra dependência para fornecer as definições de tipo Java necessárias:


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

Para as versões mais recentes dessas dependências, verifiquecxf-rt-frontend-jaxwsecxf-rt-transports-http-jetty no repositório central Maven.

3. Endpoint de serviço da web

Vamos começar com a classe de implementação usada para configurar o endpoint do serviço:

@WebService(endpointInterface = "com.example.cxf.introduction.example")
public class exampleImpl implements example {
    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;
    }
}

A coisa mais importante a ser notada aqui é a presença do atributoendpointInterface na anotação@WebService. Este atributo aponta para uma interface que define um contrato abstrato para o serviço da web.

Todas as assinaturas de método declaradas na interface do endpoint precisam ser implementadas, mas não é necessário implementar a interface.

Aqui, a classe de implementaçãoexampleImpl ainda implementa a seguinte interface de endpoint para deixar claro que todos os métodos declarados da interface foram implementados, mas fazer isso é opcional:

@WebService
public interface example {
    public String hello(String name);

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map getStudents();
}

Por padrão, o Apache CXF usa JAXB como sua arquitetura de ligação de dados. No entanto, como JAXB não suporta diretamente a ligação de umMap, que é retornado do métodogetStudents,we need an adapter to convert the Map to a Java class that JAXB can use.

Além disso, para separar os elementos do contrato de sua implementação, definimosStudent como uma interface e JAXB também não oferece suporte direto a interfaces, portanto, precisamos de mais um adaptador para lidar com isso. Na verdade, por conveniência, podemos declararStudent como uma classe. O uso desse tipo como interface é apenas mais uma demonstração do uso de classes de adaptação.

Os adaptadores são demonstrados na seção logo abaixo.

4. Adaptadores personalizados

Esta seção ilustra a maneira de usar classes de adaptação para dar suporte à ligação de uma interface Java e umMap usando JAXB.

4.1. Adaptador de Interface

É assim que a interfaceStudent é definida:

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

Essa interface declara apenas um método que retorna umStringe especificaStudentAdapter como a classe de adaptação para se mapear de e para um tipo que pode aplicar a vinculação JAXB.

A classeStudentAdapter é definida da seguinte forma:

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;
    }
}

Uma classe de adaptação deve implementar a interfaceXmlAdapter e fornecer implementação para os métodosmarshaleunmarshal. O métodomarshal transforma um tipo de limite (Student, uma interface que JAXB não pode manipular diretamente) em um tipo de valor (StudentImpl, uma classe concreta que pode ser processada por JAXB). O métodounmarshal faz as coisas ao contrário.

Aqui está a definição de classeStudentImpl:

@XmlType(name = "Student")
public class StudentImpl implements Student {
    private String name;

    // constructors, getter and setter
}

4.2. AdaptadorMap

O métodogetStudents da interface de terminalexample retorna umMape indica uma classe de adaptação para converterMap em um tipo que pode ser manipulado por JAXB. Semelhante à classeStudentAdapter, esta classe de adaptação deve implementar os métodosmarshaleunmarshal da interfaceXmlAdapter:

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;
    }
}

A classeStudentMapAdapter mapeiaMap<Integer, Student> de e para o tipo de valorStudentMap com a definição a seguir:

@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. Desdobramento, desenvolvimento

5.1. Definição deServer

Para implantar o serviço da web discutido acima, usaremos as APIs JAX-WS padrão. Como estamos usando o Apache CXF, a estrutura faz um trabalho extra, por exemplo gerando e publicando o esquema WSDL. Aqui está como o servidor de serviço é definido:

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

Depois que o servidor estiver ativo por um tempo para facilitar o teste, ele deverá ser desligado para liberar recursos do sistema. Você pode especificar qualquer duração de trabalho para o servidor com base em suas necessidades, passando um argumentolong para o métodoThread.sleep.

5.2. Implantação doServer

Neste tutorial, usamos o pluginorg.codehaus.mojo:[.s1]exec-maven-plugin para instanciar o servidor ilustrado acima e controlar seu ciclo de vida. Isso é declarado no arquivo Maven POM da seguinte maneira:


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

A configuraçãomainClass refere-se à classeServer em que o ponto de extremidade do serviço da web é publicado. Depois de executar a metajava deste plugin, nós can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/example?wsdl.

6. Casos de teste

Esta seção mostra as etapas para escrever casos de teste usados ​​para verificar o serviço da Web que criamos anteriormente.

Observe que precisamos executar a metaexec:java para iniciar o servidor de serviço da web antes de executar qualquer teste.

6.1. Preparação

A primeira etapa é declarar vários campos para a classe de teste:

public class StudentTest {
    private static QName SERVICE_NAME
      = new QName("http://introduction.cxf.example.com/", "example");
    private static QName PORT_NAME
      = new QName("http://introduction.cxf.example.com/", "examplePort");

    private Service service;
    private example exampleProxy;
    private exampleImpl exampleImpl;

    // other declarations
}

O seguinte bloco inicializador é usado para iniciar o camposervice do tipojavax.xml.ws.Service antes de executar qualquer teste:

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

Depois de adicionar a dependência JUnit ao arquivo POM, podemos usar a anotação@Before como no trecho de código abaixo. Este método é executado antes de cada teste para instanciar novamente os camposexample:

@Before
public void reinstantiateexampleInstances() {
    exampleImpl = new exampleImpl();
    exampleProxy = service.getPort(PORT_NAME, example.class);
}

A variávelexampleProxy é um proxy para o terminal de serviço da web, enquantoexampleImpl é apenas um objeto Java simples. Este objeto é usado para comparar resultados de invocações de métodos de terminal remotos por meio do proxy com invocações de métodos locais.

Observe que uma instânciaQName é identificada por duas partes: um URI de namespace e uma parte local. Se o argumentoPORT_NAME, do tipoQName, do métodoService.getPort for omitido, o Apache CXF assumirá que o URI do namespace do argumento é o nome do pacote da interface do terminal na ordem inversa e sua parte local é o nome da interface acrescentado porPort, que é exatamente o mesmo valor dePORT_NAME.. Portanto, neste tutorial, podemos deixar este argumento de fora.

6.2. Implementação de Teste

O primeiro caso de teste que ilustramos nesta subseção é validar a resposta retornada de uma invocação remota do métodohello no terminal de serviço:

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = exampleProxy.hello("example");
    String localResponse = exampleImpl.hello("example");
    assertEquals(localResponse, endpointResponse);
}

É claro que o método do ponto de extremidade remoto retorna a mesma resposta que o método local, o que significa que o serviço da Web funciona conforme o esperado.

O próximo caso de teste demonstra o uso do métodohelloStudent:

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

Nesse caso, o cliente envia um objetoStudent para o endpoint e recebe uma mensagem contendo o nome do aluno em troca. Como no caso de teste anterior, as respostas de invocações remotas e locais são as mesmas.

O último caso de teste que mostramos aqui é mais complicado. Conforme definido pela classe de implementação do terminal em serviço, cada vez que o cliente invoca o métodohelloStudent no terminal, o objetoStudent enviado será armazenado em um cache. Esse cache pode ser recuperado chamando o métodogetStudents no nó de extremidade. O caso de teste a seguir confirma que o conteúdo do cachestudents representa o que o cliente enviou para o serviço da web:

@Test
public void usingGetStudentsMethod_thenCorrect() {
    Student student1 = new StudentImpl("Adam");
    exampleProxy.helloStudent(student1);

    Student student2 = new StudentImpl("Eve");
    exampleProxy.helloStudent(student2);

    Map students = exampleProxy.getStudents();
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. Conclusão

Este tutorial apresentou o Apache CXF, uma estrutura poderosa para trabalhar com serviços da Web em Java. Ele se concentrava na aplicação da estrutura como uma implementação JAX-WS padrão, enquanto ainda fazia uso dos recursos específicos da estrutura em tempo de execução.

A implementação de todos esses exemplos e trechos de código pode ser encontrada ema GitHub project.