Nesse artigo vamos explorar o framework Apache CXF e como criar e publicar um WebService simples de forma prática e rápida utilizando o Spring framework para aumentar nossa produtividade.
O Apache CXF é um framework de integração que dá suporte à criação de serviços no padrão WebService. Através dele podemos criar e consumir serviços, utilizando código Java e configuração do framework Spring. Nesse artigo vamos utilizar um WSDL pronto para demonstrar como trabalhar com o Apache CXF junto com o Spring Framework.
O WSDL que utilizaremos é bem simples e oferece apenas uma operação que retorna obrigatoriamente uma resposta de sucesso ou um erro. Operações que trabalham requisição / resposta de forma síncrona são ditas que seguem o MEP (message exchange pattern) Request / Reply. Erros são comumente chamados de Fault.
Listagem 1: WSDL do serviço de exemplo
<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions name="HelloWorldService"
targetNamespace="http://helloworldservice.devmedia.com.br/wsdl/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://helloworldservice.devmedia.com.br/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:do="http://helloworldservice.devmedia.com.br/schema/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
<xs:schema targetNamespace="http://helloworldservice.devmedia.com.br/schema/"
attributeFormDefault="unqualified" elementFormDefault="unqualified"
xmlns:tns="http://helloworldservice.devmedia.com.br/schema/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Nome" type="tns:Nome"/>
<xs:element name="Saudacao" type="tns:Saudacao" />
<xs:element name="Erro" type="tns:Erro"/>
<xs:complexType name="Nome">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Saudacao">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Erro">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="DigaOlaRequisicao">
<wsdl:part name="parametro" type="do:Nome" />
</wsdl:message>
<wsdl:message name="DigaOlaResposta">
<wsdl:part name="saudacao" type="do:Saudacao" />
</wsdl:message>
<wsdl:message name="DigaOlaException">
<wsdl:part name="DigaOlaException" type="do:Erro" />
</wsdl:message>
<wsdl:portType name="DigaOla_PortType">
<wsdl:documentation>
A operacao digaOla recebe um nome por parametro e devolve
um cumprimento ou erro em situacoes especificas
</wsdl:documentation>
<wsdl:operation name="digaOla">
<wsdl:input name="DigaOlaRequisicao" message="tns:DigaOlaRequisicao" />
<wsdl:output name="DigaOlaResposta" message="tns:DigaOlaResposta" />
<wsdl:fault name="DigaOlaException" message="tns:DigaOlaException" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="DigaOla_SoapBinding" type="tns:DigaOla_PortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="digaOla">
<soap:operation soapAction="" />
<wsdl:input name="DigaOlaRequisicao">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="DigaOlaResposta">
<soap:body use="literal" />
</wsdl:output>
<wsdl:fault name="DigaOlaException">
<soap:fault name="DigaOlaException" use="literal" />
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HelloWorldService">
<wsdl:port name="HelloWorldService_Port" binding="tns:DigaOla_SoapBinding">
<soap:address location="https://www.devmedia.com.br/HelloWorldService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Figura 1: Estrutura do WSDL
Agora, vamos ao trecho do nosso projeto maven. O projeto completo e outros arquivos você poderá consultar no repositório github para esse artigo.
Listagem 2: Dependências especiais do nosso projeto
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
< version>${spring.version}< /version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
< version>${spring.version}< /version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
< version>${spring.version}< /version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
< version>${spring.version}< /version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-api</artifactId>
< version>${project.version}< /version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-core</artifactId>
< version>${project.version}< /version>
< /dependency>
< dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
< version>${project.version}< /version>
< /dependency>
< dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
< version>${project.version}< /version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
< version>${project.version}< /version>
< /dependency>
Vemos várias dependências de Spring e CXF, sendo que as do CXF chamam bastante atenção porque têm palavras tais como transports e frontend. Isso não é por acaso. O diagrama de arquitetura do CXF (Figura 1) demonstra a separação das responsabilidades em módulos distintos, permitindo um ajuste fino do framework à real necessidade do projeto.
Um front-end nada mais é do que um modelo de programação. Usamos JAX-WS ou JAX-RS, mas poderíamos trabalhar com “Simple” ou JavaScript ou ainda algo totalmente novo que nós podemos inventar. Assim como o front-end, o Apache CXF suporta transportes diferentes. Nesse caso estamos expondo nosso WebService através de um servidor HTTP, por isso utilizamos os transportes selecionados, mas poderíamos utilizar JMS, SMTP, Local, Servlet e outros tantos mais. Uma observação importante: no WSDL definimos um transporte através do elemento “binding” em seu atributo “transport”. Em nosso exemplo utilizamos “http://schemas.xmlsoap.org/soap/http” que só funcionará se o CXF reconhecer esse namespace através de seus componentes.
Figura 2: Visão de módulos arquiteturais do Apache CXF
Vamos agora dar uma olhada no código fonte do projeto.
Listagem 3: Comando maven para gerar projeto para IDE Eclipse
mvn clean package eclipse:eclipse
O arquivo principal é o que define o contexto do Spring. Nele podemos ver dois exemplos interessantes: jaxws:endpoint e jaxws:client. Eles configuram um servidor para hospedar nosso WebServive e um cliente para invocar operações nesse serviço.
Listagem 4: Implementação de exemplo
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd" >
<jaxws:endpoint
id="server"
address="http://localhost:9292/HelloWorldService"
implementor="br.com.devmedia.helloworldservice.SimpleHelloWorldService"
wsdlLocation="HelloWorldService.wsdl">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
<jaxws:properties>
<entry key="schema-validation-enabled" value="true" />
</jaxws:properties>
</jaxws:endpoint>
<jaxws:client address="http://localhost:9292/HelloWorldService"
id="client"
xmlns:s="http://helloworldservice.devmedia.com.br/wsdl/"
serviceName="s:HelloWorldService"
endpointName="s:HelloWorldService_Port"
wsdlLocation="HelloWorldService.wsdl"
serviceClass="br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType" >
</jaxws:client>
</beans>
Na definição de endpoint, passamos parâmetros “address” indicando em que endereço físico nosso serviço estará disponível, implementor contendo a classe que implementa nosso o WebService e onde está localizado o WSDL. Como o arquivo WSDL está na pasta src/main/resources, apenas colocamos o nome do arquivo. Se ele estivesse em src/main/resources/wsdl, a configuração seria “wsdl/HelloWorldService.wsdl” e assim por diante. A “feature” liga permite que sejam mostradas no log as mensagens SOAP de entrada e saída durante a execução do serviço.
Logo abaixo da definição do servidor vemos a definição do cliente. Ela é um pouco mais “complicada”, pois demanda que conheçamos o “serviceName” e o endpointName além do namespace do WebService. Esses valores estão no próprio WSDL como mostrado na Figura 3.
Figura 3: Configuração do cliente
Claramente o namespace é aquele definido no início no WSDL (http://helloworldservice.devmedia.com.br/wsdl/). O serviceName tem seu valor retirado do atributo “name” do elemento wsdl:service enquanto que o endpointName é o atributo “name” do elemento wsdl:port. Fácil não é? Um programador pode gastar horas quebrando a cabeça até entender como configurar o cliente. Finalmente vamos olhar o código-fonte do teste unitário.
Listagem 5: Teste unitário para chamada do serviço
package br.com.devmedia.helloworldservice;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import br.com.devmedia.helloworldservice.schema.Nome;
import br.com.devmedia.helloworldservice.schema.Saudacao;
import br.com.devmedia.helloworldservice.wsdl.DigaOlaException;
import br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class HelloWorldServiceTest {
@Autowired
DigaOlaPortType port;
@Test
public void requestResponseTest() throws Exception {
Nome _digaOla_parametro = new Nome();
try {
_digaOla_parametro.setConteudo("ze");
Saudacao ret = port.digaOla(_digaOla_parametro);
Assert.assertEquals("Bom dia", ret.getConteudo());
} catch (DigaOlaException e) {
System.out.println("Expected exception: DigaOlaException has occurred.");
System.out.println(e.toString());
}
try {
_digaOla_parametro.setConteudo("maria");
Saudacao ret = port.digaOla(_digaOla_parametro);
Assert.assertEquals("Ola", ret.getConteudo());
} catch (DigaOlaException e) {
System.out.println("Expected exception: DigaOlaException has occurred.");
System.out.println(e.toString());
}
try {
_digaOla_parametro.setConteudo("anonimo");
Saudacao ret = port.digaOla(_digaOla_parametro);
Assert.assertTrue(false);
} catch (DigaOlaException e) {
Assert.assertEquals("ERR_01",e.getFaultInfo().getConteudo());
System.out.println("Expected exception: DigaOlaException has occurred.");
System.out.println(e.toString());
}
}
}
Como estamos trabalhando com Spring, nada mais natural que usar o suporte do framework Spring a testes. Para isso, utilizamos duas anotações: ContextConfiguration e RunWith. A primeira indica que um contexto do Spring será criado quando o teste for executado. O arquivo XML deverá por padrão ter o nome < Nome da classe de Teste>-context.xml ou seja HelloWorldServiceTest-context.xml.
RunWith é uma anotação que permite ao framework Junit delegar a execução de Teste a uma classe executora específica que nesse caso é org.springframework.test.context.junit4.SpringJUnit4ClassRunner. O interessante do SpringJunit4ClassRunner é que através dela podemos trabalhar com outras funcionalidades de testes do Spring tais como injeção de dependências (@Autowired).
No teste, a nossa única preocupação foi implementar os cenários de teste para validar o serviço. Toda inicialização e execução do WebService está sob responsabilidade do Spring. Se reparamos no console, veremos também que todas as mensagens SOAP trocadas entre cliente e servidor estão sendo mostradas por que a feature de Logging está configurada.
É isso pessoal. Finalizamos a criação e chamada do nosso serviço utilizando o Apache CXF e Spring. Para ver o projeto completo, basta acessar o repositório do GITHUB https://github.com/leogsilva/ApacheCxfSpringHelloWorld.git. Até a próxima.