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]cxf–rt–frontend–jaxws. 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.