8. Versão 4 – cliente/servidor numa arquitetura de serviço web
Nesta nova versão, a aplicação [Pam] será executada em modo cliente/servidor numa arquitetura de serviço web. Voltemos à arquitetura da aplicação anterior:
![]() |
Na figura acima, uma camada de comunicação [C, RMI, S] permitia uma comunicação transparente entre o cliente [ui] e a camada remota [metier]. Vamos utilizar uma arquitetura semelhante, em que a camada de comunicação [C, RMI, S] será substituída por uma camada [C, HTTP / SOAP, S]:
![]() |
O protocolo HTTP / SOAP tem a vantagem, em relação ao protocolo anterior RMI / EJB, de ser multiplataforma. Assim, o serviço web pode ser escrito em Java e implementado no servidor Glassfish, enquanto o cliente pode ser um cliente .NET ou PHP.
Vamos desenvolver esta arquitetura de acordo com três modos diferentes:
- o serviço web será assegurado pelo EJB [Metier]
- o serviço web será prestado por uma aplicação web que utiliza o EJB e o [Metier]
- o serviço web será prestado por uma aplicação web que utiliza o Spring
Um serviço web pode ser implementado de várias formas num servidor Java EE:
- através de uma classe anotada com @WebService que é executada num contentor web
![]() |
- através de um EJB anotado com @WebService que é executado num contentor EJB
![]() |
Começamos por esta última arquitetura.
8.1. Serviço web implementado por um EJB
8.1.1. A parte do servidor
8.1.1.1. O projeto NetBeans
Comecemos por criar um novo projeto Maven, cópia do projeto EJB [mv-pam-ejb-metier-dao-jpa-eclipselink]:
![]() |
Com a seguinte arquitetura:
![]() |
a camada [metier] será o serviço web contactado pela camada [ui]. Esta classe não precisa de implementar uma interface. São as anotações que transformam um POJO (Plain Ordinary Java Object) num serviço web. A classe [Metier], que implementa a camada [metier] acima referida, é transformada da seguinte forma:
package metier;
...
@WebService
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal,IMetierRemote {
// referências sobre as camadas [DAO]
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao=null;
@EJB
private IIndemniteDaoLocal indemniteDao=null;
// obter a folha de pagamento
@WebMethod
public FeuilleSalaire calculerFeuilleSalaire(String SS,
...
}
// lista de funcionários
@WebMethod
public List<Employe> findAllEmployes() {
...
}
// importante — não há getters nem setters para os EJB
}
- na linha 4, a anotação @WebService transforma a classe [Metier] num serviço web. Um serviço web expõe métodos aos seus clientes. Estes devem ser anotados com o atributo @WebMethod.
- linhas 19 e 25: os dois métodos da classe [Metier] tornam-se métodos do serviço web.
- linha 29: é importante que os getters e setters sejam removidos; caso contrário, serão expostos no serviço web, o que provoca erros de segurança.
A adição destas anotações é detetada pelo NetBeans, que, em seguida, altera a natureza do projeto:
![]() |
Em [1], surgiu uma árvore de diretórios [Web Services] no projeto. Nela encontram-se o serviço web Metier e os seus dois métodos. A aplicação de servidor pode ser implementada em [2]. O servidor MySQL deve estar em execução e a sua base de dados [dbpam_eclipselink] deve existir e estar preenchida. Pode ser necessário, previamente, eliminar os ficheiros [3] e EJB do projeto cliente/servidor EJB analisado anteriormente, para evitar conflitos de nomes. Com efeito, o nosso novo projeto traz consigo os mesmos EJB que os do projeto anterior.
![]() |
No [1], vemos a nossa aplicação serveur implementada no servidor Glassfish. Assim que o serviço web estiver implementado, pode ser testado:
![]() |
- em [1], no projeto atual, testamos o serviço web [Metier]
- o serviço web está acessível através de diferentes URL. O URL [2] permite testar o serviço web
- em [3], uma ligação para o ficheiro XML que define o serviço web. Os clientes do serviço web precisam de conhecer o URL deste ficheiro. É a partir deste que é gerada a camada cliente (stubs) do serviço web.
- no ficheiro [4,5], um formulário que permite testar os métodos expostos pelo serviço web. Estes são apresentados com os respetivos parâmetros que o utilizador pode definir.
Por exemplo, vamos testar o método [findAllEmployes], que não requer nenhum parâmetro:
![]() |
Acima, testamos o método. Recebemos então a resposta abaixo (vista parcial). Nela, encontramos efetivamente os dois funcionários com as respetivas indemnizações. O leitor é convidado a testar, da mesma forma, o método [4], passando-lhe os três parâmetros que este espera.

8.1.2. A parte do cliente
![]() |
8.1.2.1. O projeto NetBeans do cliente « » console
Criamos agora um projeto Java do tipo [Java Application] para a parte client da aplicação. Não foi possível (junho de 2012) criar um projeto Maven para este cliente. Ocorre um erro, que parece ser conhecido na Internet, mas continua por resolver.
![]() | ![]() |
Depois de criado o projeto, indicamos que este será cliente do serviço web que acabámos de implementar no servidor Glassfish:
![]() |
- em [2], selecionamos o novo projeto e clicamos no botão [New File]
- em [3], indicamos que pretendemos criar um cliente do serviço web
![]() |
- com [4], vamos indicar o projeto NetBeans do serviço web
- na janela [5], estão listados todos os projetos com um ramo [Web Services]; aqui, apenas o projeto [mv-pam-ws-metier-dao-eclipselink].
- Um projeto pode implementar vários serviços web. Em [6], selecionamos o serviço web ao qual pretendemos ligar-nos.
![]() |
- Em [7], é apresentada a definição do serviço web URL. Esta URL é utilizada pelas ferramentas de software que geram a camada cliente que irá interagir com o serviço web.
![]() |
- A camada cliente [C] [1] que será gerada é constituída por um conjunto de classes Java que serão colocadas num único pacote. O nome deste pacote é definido como [8].
- Assim que o assistente de criação do cliente do serviço web for concluído com o botão [Finish], a camada [C] acima referida é criada.
Isto reflete-se numa série de alterações no projeto:
- No [10] acima, surge uma árvore de estrutura [Generated Sources] que contém as classes da camada [C], as quais permitem que o cliente [3] comunique com o serviço web. Esta camada permite que o cliente [3] comunique com a camada [metier] [4] como se esta fosse local e não remota.
- Em [11], surge uma árvore [Web Service References] que lista os serviços web para os quais foi gerada uma camada cliente.
Note-se que, na camada [C] [10] gerada, encontramos classes que foram implementadas no lado do servidor: Indemnite, Cotisation, Employe, FeuilleSalaire, ElementsSalaire, Metier. Metier é o serviço web e as outras classes são classes necessárias a esse serviço. Podemos sentir curiosidade em consultar o seu código. Veremos que a definição das classes que, quando instanciadas, representam objetos manipulados pelo serviço, consiste na definição dos campos da classe e dos seus acessores, bem como na adição de anotações que permitem a serialização da classe num fluxo XML. A classe «Metier» tornou-se uma interface que contém os dois métodos que foram anotados com @WebMethod. Cada um destes métodos dá origem a duas classes, por exemplo, [CalculerFeuilleSalaire.java] e [CalculerFeuilleSalaireResponse.java], em que uma encapsula a chamada ao método e a outra o seu resultado. Por fim, a classe MetierService é a classe que permite ao cliente ter uma referência ao serviço web de negócio remoto:
O método getMetierPort na linha 2 permite obter uma referência ao serviço web Metier remoto.
8.1.2.2. O cliente de consola do serviço web Metier
Resta-nos apenas escrever o cliente do serviço web Metier. Copiamos a classe [MainRemote] do projeto [mv-pam-client-metier-dao-jpa-eclipselink], que era um cliente de um servidor EJB, para o novo projeto.
![]() |
- em [1], a classe do cliente do serviço web. A classe [MainRemote] apresenta erros. Para as corrigir, começaremos por eliminar todas as instruções [import] existentes na classe e iremos regenerá-las através da opção [Fix Imports]. Com efeito, algumas das classes utilizadas pela classe [MainRemote] fazem agora parte do pacote [client] gerado.
- No [3], o trecho de código onde a camada [metier] é instanciada é o [3]. A instância é criada com código JNDI para obter uma referência a um EJB remoto.
Alteramos o código da seguinte forma:
- o código JNDI é eliminado
- uma vez que a classe [PamException] não existe do lado do cliente, eliminamos o catch associado para manter apenas o catch na classe-mãe [Exception].
![]() |
- no [4], resta-nos obter uma referência ao serviço web remoto [Metier] para podermos chamar o seu método [calculerFeuilleSalaire].
- em [5], com o rato, arrastamos (drag) o método [calculerFeuilleSalaire] do serviço web [Metier] para o soltar (drop) em [4]. É gerado código [6]. Este código genérico pode ser posteriormente adaptado pelo programador.
![]() |
- na linha 112, verifica-se que [calculerFeuilleSalaire] é um método da classe [client.Metier] (linha 111). Agora que sabemos como obter a camada [metier], o código anterior pode ser reescrito da seguinte forma:
A linha 7 obtém uma referência ao serviço web Metier. Feito isto, o código da classe não se altera, exceto que, na linha 10, não é a exceção do tipo [Exception] que é tratada, mas sim o tipo mais geral Throwable, a classe pai da classe Exception. Se ocorrer uma exceção, apresentamos todas as causas aninhadas da mesma até à causa original.
Estamos prontos para os testes:
- certificar-nos de que o SGBD MySQL5 é iniciado, que a base de dados dbpam_eclipselink é criada e inicializada
- certificar-se de que o serviço web está implementado no servidor Glassfish
- compilar o cliente (Clean and Build)
- configurar a execução do cliente
![]() |
- Executar o cliente
Os resultados na consola são os seguintes:
Com a seguinte configuração:

obtêm-se os seguintes resultados:
Note-se que, embora o serviço web [Metier] envie uma exceção do tipo [PamException], a exceção recebida pelo cliente é do tipo [SOAPFaultException]. Mesmo na cadeia de exceções, não se verifica a presença do tipo [PamException].
8.1.3. O cliente Swing do serviço web Metier
Tarefa a realizar: portar o cliente Swing do projeto [mv-pam-client-ejb-metier-dao-jpa-eclipselink] para o novo projeto, para que também passe a ser um cliente do serviço web implementado no servidor Glassfish.
8.2. Serviço web implementado por uma aplicação web
Encontramo-nos agora no contexto da seguinte arquitetura:
![]() |
O serviço web é assegurado por uma aplicação web executada no contentor web do servidor Glassfish. Este serviço web irá basear-se no EJB [Metier], que por sua vez está implementado no contentor EJB3.
8.2.1. A parte do servidor
Criamos uma aplicação web:
![]() |
- em [1], criamos um novo projeto
- em [2]; este projeto é do tipo [Web Application]
- em [3], atribuímos-lhe o nome [mv-pam-ws-ejb-metier-dao-eclipselink]
![]() |
- em [4], escolhemos a versão Java EE 6
- em [6], o projeto criado
No esquema abaixo, a aplicação web criada será executada no contentor web. Esta irá utilizar o EJB [Metier], que, por sua vez, será implementado no contentor EJB do servidor.
![]() |
Para que a aplicação web criada tenha acesso às classes associadas ao EJB [Metier], adicionamos às bibliotecas da aplicação web [mv-pam-ws-ejb-metier-dao-eclipselink], a dependência do servidor EJB [mv-pam-ejb-metier-dao-eclipselink], já analisado.
![]() |
- no [1], adicionamos um projeto às dependências do projeto web,
- em [2], seleciona-se o projeto [mv-pam-ejb-metier-dao-eclipselink],
- em [3], o tipo da dependência é ejb,
- em [4], o âmbito da dependência é provided, ou seja, será fornecida pelo ambiente de execução,
- em [5], a dependência foi adicionada.
Para criar o mesmo serviço web que anteriormente, precisamos de:
- criar uma classe com a tag @Webservice
- com dois métodos calculerFeuilleSalaire e findAllEmployes marcados com @WebMethod
Criamos uma classe [PamWsEjbMetier] num pacote [pam.ws]:
![]() |
![]() |
A classe [PamWsEjbMetier] é a seguinte:
- linhas 7-10: a classe importa classes do módulo EJB e [pam-serveurws-metier-dao-jpa-eclipselink], cujo projeto Maven foi adicionado às dependências do projeto.
- linha 12: a classe é um serviço web
- linha 13: implementa a interface IMetier definida no módulo EJB
- linhas 18-19: o método calculerFeuilleSalaire é exposto como método do serviço web
- linhas 23-24: o método findAllEmployes é exposto como método do serviço web
- linhas 15-16: a interface local do EJB [Metier] é inserida no campo da linha 16. Utilizamos a interface local porque a aplicação web e o módulo EJB são executados no mesmo JVM.
- linhas 20 e 25: os métodos calculerFeuilleSalaire e findAllEmployes delegam o seu processamento aos métodos com o mesmo nome do EJB [Metier]. A classe serve, portanto, apenas para expor aos clientes remotos os métodos de EJB e [Metier] como métodos de um serviço web.
No NetBeans, a aplicação web é reconhecida como expondo um serviço web:
![]() |
Para implementar o serviço web no servidor GlassFish, é necessário implementar:
- o módulo web no contentor web do servidor
- o módulo EJB no contentor EJB do servidor
Para tal, precisamos de criar uma aplicação do tipo [Enterprise Application] que irá implementar os dois módulos em simultâneo. Para o fazer, é necessário que os dois projetos estejam carregados no NetBeans [2].
Feito isto, criamos um novo projeto [3].
![]() |
- no [4], escolhemos um projeto do tipo [Enterprise Application].
- em [5], atribuímos um nome ao projeto
![]() |
- em [6], configuramos o projeto. A versão do Java EE será Java EE 6. Um projeto empresarial pode ser criado com dois módulos: um módulo EJB e um módulo Web. Neste caso, o projeto empresarial irá encapsular o módulo Web e o módulo EJB já criados e carregados no NetBeans. Por isso, não solicitamos a criação de novos módulos.
- No [7], o projeto empresarial [mv-pam-webapp-ear] assim criado. Foi criado, ao mesmo tempo, outro projeto Maven: o [mv-pam-webapp]. Não nos ocuparemos desse.
- No [8], adicionamos dependências ao projeto empresarial
![]() |
- no [9], adicionamos o projeto web do tipo WAR,
- no [10], adicionamos o projeto EJB do tipo EJB,
![]() |
- em [11], o projeto empresarial com as suas duas dependências.
Compilamos o projeto empresarial através de um «Clean and Build». Estamos praticamente prontos para o implementar no servidor Glassfish. Antes disso, poderá ser necessário descarregar as aplicações já carregadas no servidor, a fim de evitar possíveis conflitos de nomes entre EJB e [11]:
![]() |
O servidor MySQL deve estar em execução e a base de dados [dbpam_eclipselink] disponível e preenchida. Feito isto, a aplicação empresarial pode ser implementada [12]. Em [13], é possível verificar que a aplicação foi efetivamente implementada no servidor Glassfish.
Podemos testar o serviço web que acabou de ser implementado:
![]() |
- em [1], solicitamos o teste do serviço web [PamWsEjbMetier]
- em [2], a página de teste. Deixamos ao leitor a tarefa de realizar os testes.
8.2.2. A parte do cliente
Tarefa a realizar: seguindo o procedimento descrito no parágrafo 8.1.2.1, criar um cliente de consola para o serviço web anterior.
8.3. Serviço web implementado com Spring e Tomcat
Situamo-nos agora no contexto da seguinte arquitetura:
![]() |
O serviço web é assegurado por uma aplicação web executada no contentor web do servidor Tomcat. A arquitetura da aplicação será a seguinte:
![]() |
Iremos basear-nos no projeto [mv-pam-spring-hibernate] desenvolvido no parágrafo 5.11:
![]() |
8.3.1. A parte do servidor
Criamos uma aplicação Maven do tipo web denominada [mv-pam-ws-spring-tomcat] [1]:
![]() |
Alteramos o ficheiro [pom.xml] para incluir as seguintes dependências [2]:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-pam-spring-hibernate</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Dependências do Apache CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.2.12</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.2.12</version>
</dependency>
</dependencies>
- linhas 3-7: a dependência do projeto [spring-pam-jpa-hibernate],
- linhas 8-17: as dependências do framework Apache CXF e [http://cxf.apache.org/]. Este framework facilita a criação de serviços web.
Este ficheiro [pom.xml] traz consigo numerosas dependências [2].
Voltemos à arquitetura da aplicação:
![]() |
As chamadas ao serviço web que vamos construir são geridas por um servlet do framework CXF. Isto traduz-se no ficheiro [WEB-INF / web.xml] da seguinte forma:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>mv-pam-ws-spring-tomcat</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Configuração do CXF -->
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- O framework CXF depende do Spring. Linhas 4-6: é declarado um listener. A classe correspondente será carregada em simultâneo com a aplicação web. Esta irá utilizar o ficheiro de configuração do Spring [WEB-INF / applicationContext.xml]:
![]() |
- linhas 8-12: o servlet CXF, que irá gerir as chamadas ao serviço web que vamos criar,
- linhas 13-16: os URL tratados pelo servlet CXF serão do tipo /ws/*. Os restantes não serão tratados pelo CXF.
Para definir o serviço web, definimos uma interface e a sua implementação:
![]() |
A interface [IWsMetier] será a seguinte:
package pam.ws;
import javax.jws.WebService;
import metier.IMetier;
@WebService
public interface IWsMetier extends IMetier{
}
- linha 7: a interface [IWsMetier] deriva da interface [IMetier] da camada [métier] do projeto [mv-pam-spring-hibernate],
- linha 6: a interface [IWsMetier] é a de um serviço web.
A classe de implementação desta interface é a seguinte:
package pam.ws;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebService;
import jpa.Employe;
import metier.FeuilleSalaire;
import metier.IMetier;
@WebService
public class PamWsMetier implements IWsMetier {
// camada de negócio
private IMetier metier;
// Construtor
public PamWsMetier(){
}
@WebMethod
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillees, int nbJoursTravailles) {
return metier.calculerFeuilleSalaire(SS, nbHeuresTravaillees, nbJoursTravailles);
}
@WebMethod
public List<Employe> findAllEmployes() {
return metier.findAllEmployes();
}
// getters e setters
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
- linha 11: a classe [PamWsMetier] implementa a interface definida anteriormente,
- linha 10: define a classe como um serviço web,
- linha 14: a camada [métier] será injetada pelo Spring,
- linhas 21 e 26: a anotação @WebMethod transforma um método num método exposto pelo serviço web,
- linhas 23 e 28: os métodos são implementados com a ajuda da camada [métier].
Resta-nos definir o conteúdo do ficheiro de configuração do Spring [applicationContext.xml]:
![]() |
O seu conteúdo é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- Apache CXF -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<!-- camadas inferiores -->
<import resource="classpath:spring-config-metier-dao.xml" />
<!-- serviço web -->
<bean id="wsMetier" class="pam.ws.PamWsMetier">
<property name="metier" ref="metier"/>
</bean>
<jaxws:endpoint id="wsmetier"
implementor="#wsMetier"
address="/metier">
</jaxws:endpoint>
</beans>
- linhas 13-15: importam-se ficheiros de configuração do Apache CXF. Estes são procurados no Classpath do projeto (atributo classpath:),
- linhas 4, 9, 10: são declarados espaços de nomes específicos do Apache CXF,
- linha 18: importa-se o ficheiro de configuração do Spring do projeto [mv-pam-spring-hibernate],
- linhas 21-23: definem o bean do serviço web com a sua dependência da camada [métier] (linha 22),
- linhas 24-27: definem o próprio serviço web,
- linha 25: o bean Spring que implementa o serviço web é aquele definido na linha 21;
- linha 26: define o URL no qual o serviço web estará disponível, neste caso /metier. Combinada com o formato que as URL processadas pelo Apache CXF devem ter (ver ficheiro web.xml), esta URL passa a ser /ws/metier.
O nosso projeto está pronto para ser executado. Executamo-lo (Run) e acedemos ao URL [http://localhost:8080/mv-pam-ws-spring-tomcat/ws] num navegador:

A página apresenta uma lista de todos os serviços web implementados. Neste caso, existe apenas um. Seguimos o link WSDL:
![]() |
O texto apresentado, [1], corresponde ao conteúdo de um ficheiro XML que define as funcionalidades do serviço web, como o aceder e quais as respostas que este envia. Deve-se notar que o URL e o [2] estão associados a este ficheiro WSDL. Todos os clientes do serviço web precisam de o conhecer.
8.3.2. A parte do cliente
Tarefa a realizar: seguindo o procedimento descrito no parágrafo 8.1.2.1, criar um cliente de consola para o serviço web anterior.
Nota: para indicar o URL do ficheiro WSDL do serviço web, deve proceder-se da seguinte forma:
![]() |
Colocaremos em [3] o URL anteriormente anotado em [2].












































