Páginas

domingo, 22 de maio de 2011

Web Service com JAX-WS 2.2 e Cliente Assíncrono

Neste pequeno artigo irei abordar a criação de web service, em Java, com implementação JAX-WS 2.2, e o respectivo cliente com métodos assíncronos.

O objetivo aqui não é aprofundar em conceitos e especificações, mas mostrar uma forma de criação de web services independente de ferramentas facilitadoras (wizards) como, por exemplo, o NetBeans com GlassFish e o Eclipse com Axis.

Este artigo foi realizado e testado em um ambiente com as seguintes configurações:
- Windows XP
- Java SE 6 (JDK)- Tomcat 6.0

As ferramentas utilizadas foram:
- Eclipse Ganymede (Java EE)
- Bibliotecas Metro 2.1









1. A aplicação servidor.

1.1. Crie um projeto web dinâmico, o nome para o projeto e contexto que utilizarei será "ws", você tem liberdade para utilizar nomes de sua preferência, apenas lembre de fazer as adaptações onde necessário.

1.2. Copie as bibliotecas Metro para a pasta WEB-INF\lib. Os arquivos necessários são: stax-api, webservices-api, webservices-extra, webservices-extra-api, webservices-rt e webservices-tools.
Estou copiando as bibliotecas direto para dentro do projeto apenas como forma exemplificativa do funcionamento do JAX-WS 2.2 para criação do web service, mas o cliente irá funcionar no ambiente normal do Java SE 6 (JAX-WS 2.1). Um ambiente de produção deverá ter as bibliotecas instaladas conforme as instruções que veem junto com o pacote Metro.

1.3. Não é necessária nenhuma página web neste exemplo, então vamos direto para a criação do web service.
Crie a classe ws.sample.Agenda:
package ws.sample;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public class Agenda {

 @WebMethod
 public String adicionar(@WebParam(name="nome") String nome) {
  return nome + " adicionado à agenda";
 }

 @WebMethod
 public String localizar(@WebParam(name="nome") String nome) {
  try {
   Thread.sleep(10000);
  } catch(InterruptedException e) {}
  return nome + " localizado na agenda";
 }
}
O que você deve observar nesta classe são as anotações. As anotações são responsáveis pela definição do nosso web service e serão utilizadas pelo mecanismo do JAX-WS para publicação do serviço.

1.4. Vamos publicar o serviço agora.
Para publicação do serviço iremos utilizar a geração automática dos documentos de definição do JAX-WS 2.2, para isto precisamos adicionar a definição do servlet de negociação no web.xml da nossa aplicação ws. Inclua as seguintes linhas no web.xml:
<listener>
 <listener-class>
  com.sun.xml.ws.transport.http.servlet.WSServletContextListener
 </listener-class>
</listener>
<servlet>
 <servlet-name>agenda</servlet-name>
 <servlet-class>
  com.sun.xml.ws.transport.http.servlet.WSServlet
 </servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>agenda</servlet-name>
 <url-pattern>/servicos/agenda</url-pattern>
</servlet-mapping>
Observe nestas linhas que estamos utilizando um servlet disponível pela implementação do JAX-WS, e criando um endereço de resposta para este servlet. Mas ainda precisamos que este endereço seja direcionado para a nossa classe criada para fornecer o serviço, isto é feito através de uma arquivo padronizado, o sun-jaxws.xml:
<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
 <endpoint
   name="agenda"
   url-pattern="/servicos/agenda"
   implementation="ws.sample.Agenda" />
</endpoints>
Crie este arquivo na pasta WEB-INF da aplicação ws, e observe que, neste arquivo, as definições do servlet estão sendo vinculadas à classe de serviço criada.

Vamos testar o web service publicando a aplicação ws e colocando o servidor no ar. Acesse as páginas do serviço e verifique se não ocorreu nenhum problema:
http://localhost/ws/servicos/agenda e http://localhost/ws/servicos/agenda?wsdl
* lembre-se de colocar a porta caso seu servidor esteja configurado com uma porta diferente da 80.








2. A aplicação cliente.

2.1. Crie um projeto java vazio, o nome para o projeto que utilizarei será "ws_cliente", você tem liberdade para utilizar nomes de sua preferência, apenas lembre de fazer as adaptações onde necessário.

2.2. Vamos gerar os artefatos de conexão com o nosso web service.

Crie o arquivo de binding async-client.xml na raiz do projeto:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns="http://java.sun.com/xml/ns/jaxws">    
 <bindings node="wsdl:definitions">
  <enableAsyncMapping>true</enableAsyncMapping>
 </bindings>    
</bindings>
Crie o arquivo wsimport.bat (ou .sh) na raiz do projeto:
%JDK_PATH%\bin\wsimport.exe -s src -d bin -keep
  http://localhost/ws/servicos/agenda?wsdl -b async-client.xml
O arquivo de lote utiliza o wsimport padrão do java para criação dos artefatos, a referência ao arquivo de binding é necessária para habilitar a criação de métodos assíncronos.
* Aponte corretamente a localização do wsimport.exe que está localizado na JDK.
* Caso a estrutura de diretórios de fontes e compilados não seja igual, faça as necessárias correções.

Execute o arquivo de lote. Pronto, os artefatos foram gerados e estão disponíveis para serem utilizados.

2.3. Crie a classe ClienteAgenda:
import java.awt.Toolkit;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

import ws.sample.Agenda;
import ws.sample.AgendaService;
import ws.sample.LocalizarResponse;

public class ClienteAgenda {

 private static long inicio;

 public static void main(String[] args) {
  AgendaService servico = new AgendaService();
  Agenda porta = servico.getAgendaPort();

  //------------------------------------------------------------------------
  inicio = System.currentTimeMillis();
  System.out.println(String.format("\n%06d > Conexao sincrona...", 0));
  String ret = porta.adicionar("Sincrono da Silva");
  System.out.println(String.format("%06d > %s",
    System.currentTimeMillis() - inicio,
    ret));

  //------------------------------------------------------------------------
  inicio = System.currentTimeMillis();
  System.out.println(String.format("\n%06d > Conexao assincrona 1...",
    System.currentTimeMillis() - inicio));
  Response<LocalizarResponse> resp =
    porta.localizarAsync("Assincrono da Silva");
  System.out.print("Requisicao enviada. Aguardando resposta");
  while(!resp.isDone()) {
   System.out.print(".");
   try {
    Thread.sleep(250);
   } catch(InterruptedException e) {}
  }
  System.out.print("\n");
  try {
   System.out.println(String.format("%06d > %s",
     System.currentTimeMillis() - inicio,
     resp.get().getReturn()));
  } catch(InterruptedException e) {
   e.printStackTrace();
  } catch(ExecutionException e) {
   e.printStackTrace();
  }

  //------------------------------------------------------------------------
  inicio = System.currentTimeMillis();
  System.out.println(String.format("\n%06d > Conexao assincrona 2...",
    System.currentTimeMillis() - inicio));
  Future<?> future = porta.localizarAsync("Assincrono da Silva Junior",
    new AsyncHandler<LocalizarResponse>(){
    @Override
    public void handleResponse(Response<LocalizarResponse> res) {
     try {
      System.out.println(String.format("%06d > %s",
        System.currentTimeMillis() - inicio,
        res.get().getReturn()));
     } catch(InterruptedException e) {
      e.printStackTrace();
     } catch(ExecutionException e) {
      e.printStackTrace();
     }
    }
  });
  System.out.println("Requisicao enviada. Aguardando resposta...");
  while(!future.isDone()) {
   try {
    Toolkit.getDefaultToolkit().beep();
    Thread.sleep(1000);
   } catch(InterruptedException e) {}
  }

  System.out.println("\nEncerrando cliente");
 }
}
*Os nomes dos artefatos para o web service podem variar, então faça as correções necessárias.
Compile e execute o aplicativo ws_cliente e observe o resultado.

3. Observações

No método adicionar do serviço foi realizada a conexão normal (síncrona), onde o método somente retorna após a conclusão da execução, e podemos observar o tempo gasto para esta execução. Como o serviço executa rapidamente esta requisição, não temos nenhum problema.

Já o método localizar do serviço possui um pequeno atraso para responder o cliente, e este atraso pode ser inconveniente, dependendo do tempo e da sua aplicação, já que o usuário vai pensar que a sua aplicação está "travada", então é interessante implementar alguma informação visual, como uma barra de progresso, e/ou liberar o uso da aplicação para o usuário. Através dos dois exemplos de métodos assíncronos você verifica que a execução do aplicativo é imediatamente continuada após a requisição do método demorado do serviço, não "travando" o aplicativo, e a resposta é recuperada posteriormente. Entre os dois exemplos de métodos assíncronos, o que você deve observar é a diferença na forma de recuperar o valor, onde no primeiro existe um controle de verificação de resposta disponível e no segundo existe um handler que será avisado imediatamente quando a resposta estiver disponível.

11 comentários:

  1. só não entendi no cliente quem é esse AgendaService e o LocalizarResponse, se poder adicionar esses 2 códigos, e também se o agenda que voce está utilizando é o mesmo da classe que cria o serviço?


    Mas está muito bom esse post, já ganhei muito base para construir um webservice.

    ResponderExcluir
    Respostas
    1. Olá Guilherme,
      Estas classes são código gerado pela ferramenta wsimport. Dependendo da ferramenta ou da versão que seja utilizada esta estrutura de códigos pode mudar.
      Mas respondendo a pergunta inicial, AgendaService é o código gerado responsável pela conexão com o webservice, e LocalizarResponse é o código gerado responsável por gerenciar a resposta do webservice, no nosso exemplo: controlar o uso assíncrono.

      Falouzs!!

      Excluir
  2. Claudio, fiz algumas modificações para poder rodar o projeto cliente, pois o mesmo estava dando erro no wsimport, acabei fazendo mudanças. estarei postando aqui como ficou meu wsimport:
    "/usr/bin/wsimport -keep -p src.br.gov.ba.prodeb.fiplan.webservice.jaxws http://localhost:8080/fiplan/WebServiceFiplanLid?wsdl -b async-client.xml"
    Pelo que notei ele gerou as classes aparentemente de forma correta, mas quando tento executar o cliente ele me da esse erro:
    "Exception in thread "main" java.lang.IllegalArgumentException: br.gov.ba.prodeb.fiplan.webservice.WebServiceFiplanLid is not an interface"
    no debug quando ele tenta obter a porta.
    Estou utilizando java 6.
    acredito que esteja bem perto de conseguir colocar pra rodar
    =)
    Agradeço desde já pela atenção.

    ResponderExcluir
    Respostas
    1. esqueci de dizer que estou utilizando linux, mas acredito que isso nao seja muito importante devido ao java nao ter essa diferença.

      Excluir
    2. Guilherme, o Linux, por padrão, utiliza o openJDK que pode ter diferenças significativas. Se você vincular as bibliotecas do jax 2.2 no seu projeto cliente, ou instalar no modo endorsed, também podem existir diferenças de classes.

      Excluir
    3. Claudio, então considera que o erro na parte de cima pode ser relacionado a estar utilizando o linux??? o que ele informa que minha classe (WebServiceFiplanLid.java) não é uma interface?
      pois nao estou conseguindo resolver esse problema no momento...
      :(

      Excluir
    4. Guilherme, é importante observar que o Linux pode gerar código diferente, mas deveria funcionar. O erro aparenta ser um problema de classpath, se você está utilizando uma IDE, tente verificar o classpath do projeto.

      Excluir
  3. Consegui Claudio, muito obrigado pela ajuda!!!
    O que aconteceu foi o seguinte: Quando mandei gerar as classes através do wsimport ele nao estava colocando no pacote certo, ai acabei colocando na mao, mas a minha classe client nao tinha ainda o import que eu tinha criado atraves do wsimport, ai tive que colocar o import na mao na minha classe client.
    Obrigado por tudo!
    :D

    ResponderExcluir
  4. Claudio, surgiu mais uma dúvida, gostaria de saber se tem como trabalhar com objetos complexos, tipo: um objeto carro que dentro tem outro objeto marca... esse tipo de coisas... e se é possivel trabalhar com listas???
    Quando vou chamar o metodo do webservice que espera um objeto completo ele me da a seguinte exceção: "argument type mismatch" e caso passe isso nulo para testar o retorno do meu webservice ele me da um class cast exception...
    Desse jeito tanto na passagem de paremtro quanto no retorno esta me dando erro com objetos complexos... Sabe me dizer o que estou fazendo de errado?
    Agradeço desde já a atenção.

    ResponderExcluir
    Respostas
    1. Guilherme, é possível trabalhar com objetos em webservices, mas lembre que sempre que você alterar o webservice você deve regerar as classes do cliente (wsimport), e os objetos devem implementar Serializable. Infelizmente não posso ajudar muito além, pois não utilizo objetos em definições de webservices para não complicar o uso do webservice em ferramentas "não java".

      Excluir

Olá! Antes de postar seu comentário, por favor, observe que comentários técnicos, elogios e sugestões são antecipadamente agradecidos, esclarecimentos sobre os conceitos envolvidos na postagem serão respondidos da melhor forma possível, mas pedidos de ajuda técnica ou suporte individual deverão ser feitos através do formulário de contato. Grato!