Conteúdo


Desenvolver um serviço da web RESTful usando o Jersey e o Apache Tomcat

Comments

Introdução ao serviço da web RESTful

O Representational State Transfer, ou REST, foi introduzido e definido em 2000 pela dissertação de doutorado de Roy Fielding, um dos principais autores das versões 1.0 e 1.1 da especificação HTTP.

O conceito mais importante em REST são os recursos, que são identificados pelos IDs globais— que geralmente usam URIs. As aplicações do cliente usam métodos HTTP (GET/ POST/ PUT/ DELETE) para manipular o recurso ou coleção de recursos. Um serviço da web RESTful é um serviço da web implementado que usa HTTP e os princípios de REST. Geralmente, um serviço da web RESTful deve definir os aspectos a seguir:

  • O URI base/raiz para o serviço da web, tal como http://host/<appcontext>/resources.
  • O tipo MIME dos dados de resposta suportados, que são JSON/XML/ATOM e assim por diante.
  • O conjunto de operações suportadas pelo serviço. (por exemplo, POST, GET, PUT ou DELETE).

A Tabela 1 ilustra os métodos URI e HTTP do recurso usados nos típicos serviços da web RESTful.

Tabela 1. Exemplo de um serviço da web RESTful
Método/recurso Coleção de recursos, URI como:
http://host/<appctx>/resources
Recursos de membro, URI como:
http://host/<appctx>/resources/1234
GET Liste todos os membros dos recursos de coleção. Recupere uma representação de um recurso identificado como 1234.
PUT Atualize (substitua) a coleção por outra. Atualize o recurso membro identificado como 1234.
POST Crie um recurso membro na coleção em que o ID dele seja automaticamente designado. Crie um sub-recurso sob ele.
DELETE Exclua a coleção de recursos inteira. Exclua o recurso membro identificado como 1234.

JSR 311 (JAX-RS) e Jersey

A proposta para o JSR 311 ou JAX-RS (a API Java para serviços da web RESTful) foi iniciada em 2007 e a liberação da versão 1.0 foi finalizada em outubro de 2008. Atualmente, o JSR 311 versão 1.1 está no estado de rascunho. O propósito desse JSR é fornecer um conjunto de APIs que podem simplificar o desenvolvimento de serviço da web estilo REST.

Antes da especificação JAX-RS, havia estruturas como Restlet e RestEasy que poderiam ajudar a implementar os serviços da web RESTful, mas elas não eram intuitivas. Jersey é a implementação de referência para o JAX-RS e ele contém três partes principais.

  • Servidor central: ao fornecer anotações e APIs padronizadas no JSR 311, é possível desenvolver um serviço da web RESTful de maneira muito intuitiva.
  • Cliente central: a API do cliente Jersey ajuda a comunicar facilmente com os serviços REST.
  • Integração: o Jersey também fornece bibliotecas que podem se integrar facilmente com o Spring, o Guice, o Apache Abdera e assim por diante.

Nas seções do artigo a seguir, apresento todos esses componentes, mas o foco maior será no Servidor central.

Desenvolver um serviço da web RESTful

Começarei com um aplicativo "hello world" que pode ser integrado ao Tomcat. Esse aplicativo irá ajudar na configuração do ambiente e cobrirá as informações básicas do Jersey e do JAX-RS.

Em seguida, apresentarei uma aplicação mais complicada para aprofundar os fundamentos e recursos do JAX-RS, como o suporte às várias representações do tipo MIME, o suporte ao JAXB e assim por diante. Extrairei fragmentos de código da amostra para apresentar os conceitos importantes.

Hello World: o primeiro projeto da web Jersey

Para configurar o ambiente de desenvolvimento são necessários os seguintes artefatos:

  • IDE: Eclipse IDE for JEE (v3.4+) ou IBM Rational Application Developer 7.5
  • Java SE5 ou superior
  • Contêiner da web: Apache Tomcat 6.0 (Jetty e outros também funcionarão)
  • Bibliotecas Jersey: archive Jersey 1.0.3, que inclui todas as bibliotecas necessárias

Configurando o ambiente para o Jersey

Primeiro, crie um tempo de execução do servidor para o Tomcat 6.0 no Eclipse. Esse é o contêiner da web para seu aplicativo da web RESTful. Em seguida, crie um aplicativo dinâmico da web nomeado "Jersey" e especifique o tempo de execução de destino a ser realizado pelo Tomcat 6.0.

Finalmente, copie as seguintes bibliotecas do archive Jersey para o diretório lib em WEB-INF:

  • Servidor central: jersey-core.jar, jersey-server.jar, jsr311-api.jar, asm.jar
  • Cliente central: (usado para teste) jersey-client.jar
  • Suporte a JAXB: (usado no exemplo avançado) jaxb-impl.jar, jaxb-api.jar, activation.jar, stax-api.jar, wstx-asl.jar
  • Suporte a JSON: (usado no exemplo avançado) jersey-json.jar

Desenvolvendo o serviço REST

Agora que o ambiente foi configurado, você está pronto para desenvolver seu primeiro serviço REST, que simplesmente diz "Olá" para o cliente.

Para fazer isso, é necessário direcionar todas as solicitações REST ao contêiner do Jersey, definindo um despachante de servlet no arquivo web.xml do aplicativo. (Veja a Listagem 1.) Além de declarar o servlet Jersey, também define um parâmetro de inicialização, indicando o pacote Java que contém os recursos.

Listagem 1. Definir o despachante do servlet do Jersey no arquivo web.xml
<servlet>
  <servlet-name>Jersey REST Service</servlet-name>
<servlet-class>
  com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>sample.hello.resources</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>Jersey REST Service</servlet-name>
  <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

Agora você escreverá um recurso nomeado HelloResource, que aceita o HTTP GET e as respostas com o clichê "Hello Jersey."

Listagem 2. HelloResource no pacote sample.hello.resources
@Path("/hello")
public class HelloResource {
	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String sayHello() {
		return "Hello Jersey";
	}
}

Há vários pontos no código que precisam ser destacados:

  • Classe do recurso: observe que a classe do recurso é um plain old java object (POJO) e não é restrito à implementação de nenhuma interface. Isso inclui muitas vantagens, como reutilização e simplicidade.
  • Anotações: eles são definidos em javax.ws.rs.*, que são parte da especificação JAX-RS (JSR 311).
  • @Path: isso define o URI base do recurso. Formado com a raiz de contexto e com o nome do host, o identificador de recurso será algo como http://localhost:8080/Jersey/rest/hello.
  • @GET: Isso significa que o método a seguir responde ao método HTTP GET .
  • @Produces: Define o tipo MIME do conteúdo de resposta como plain/text.

Testando o app Hello

Para testar o app, abra o seu navegador e insira a URL http://<host>:<port>/<appctx>/rest/hello. Você verá a resposta "Hello Jersey". Isso é bastante simples, com anotações, tomando cuidado com a solicitação, resposta e métodos.

As seções a seguir cobrirão as partes fundamentais da especificação JAX-RS e serão introduzidas usando alguns fragmentos de código do aplicativo de exemplo Contatos. É possível localizar todo o código para essa amostra mais avançada no pacote de código-fonte (veja Download).

Recursos

Os recursos são as partes principais que compõem um serviço da web RESTful. Você manipula os recursos usando métodos HTTP, como GET, POST, PUTe o DELETE. Qualquer coisa no aplicativo pode ser um recurso: funcionários, contatos, organizações, tudo. No JAX-RX, os recursos são implementados por um POJO, com uma anotação @Path para compor seu identificador. Um recurso também pode ter sub-recursos. Nesse caso, o recurso pai é uma coleção de recursos, enquanto os sub-recursos são recursos membros.

No aplicativo de amostra Contatos, você manipulará contatos individuais e coleções de contatos. ContactsResource é o recurso coleção com o URI de /contacts, e ContactResource é o recurso membro com o URI de /contacts/{contactId}. O JavaBean sublinhado é uma classe simples do app Contato com ID, nome e endereço como seus campos membros. Veja as Listagens 3 e 4 para obter detalhes. Também é possível fazer download do pacote completo de código-fonte no final deste artigo (veja Download).

Listagem 3. ContactsResource
@Path("/contacts")
public class ContactsResource {
	@Context
	UriInfo uriInfo;
	@Context
	Request request;

	@GET
	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
	public List<Contact> getContacts() {
		List<Contact> contacts = >new ArrayList<Contact>();
		contacts.addAll( ContactStore.getStore().values() );
		return contacts;
	}

@Path("{contact}")
	public ContactResource getContact(
			@PathParam("contact") String contact) {
		return new ContactResource(uriInfo, request, contact);
	}
}

Há várias coisas interessantes aqui que você deveria anotar.

  • @Context: Use essa anotação para introduzir os objetos contextuais, como Solicitação, Resposta, UriInfo, ServletContext e assim por diante.
  • @Path("{contact}"): Essa é a anotação @Path combinada com o caminho raiz "/contacts", que forma o URI dos sub-recursos.
  • @PathParam("contact"): Essa anotação introduz os parâmetros no caminho, o ID do contato, nesse caso, para o parâmetro de método. Outras anotações disponíveis são @FormParam, @QueryParam e assim por diante.
  • @Produces: Vários tipos MIME são suportados para as respostas. Nesse caso e no anterior, application/xml será o tipo MIME padrão.

Também é possível notar que os métodos GET retornam objetos Java customizados, em vez de uma Sequência (texto simples), conforme é mostrado no exemplo anterior do Hello World. A especificação JAX-RS requer que a implementação suporte vários tipos de representação, como InputStream, byte[], elementos JAXB, coleções de elementos JAXB e assim por diante, bem como a capacidade de serializá-los para XML, JSON ou texto simples como respostas. Informações adicionais sobre técnicas de representação serão fornecidas mais adiante neste artigo, especialmente sobre a representação de elementos JAXB.

Listagem 4. ContactResource
public class ContactResource {
	@Context
	UriInfo uriInfo;
	@Context
	Request request;
	String contact;
	
	public ContactResource(UriInfo uriInfo, Request request, 
			String contact) {
		this.uriInfo = uriInfo;
		this.request = request;
		this.contact = contact;
	}
	
	@GET
	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
	public Contact getContact() {
		Contact cont = ContactStore.getStore().get(contact);
		if(cont==null)
			throw new NotFoundException("No such Contact.");
		return cont;
	}
}

O código para ContactResource é simples. Observe os itens a seguir:

  • Tipo de representação Contato: Contato é um JavaBean simples anotado por @XmlRootElement, que possibilita que ele seja representado como XML ou JSON.
  • ContactStore: é um datacenter em memória, baseado em HashMap, cuja implementação não é importante para este artigo.

Método

Os métodos HTTP são mapeados para as ações CRUD (criar, ler, atualizar e excluir) para um recurso. Embora seja possível fazer ligeiras modificações, como fazer o método PUT ser criado ou atualizado, os padrões básicos são listados como a seguir.

  • HTTP GET: obtenha/liste/recupere um recurso individual ou uma coleção de recursos.
  • HTTP POST: crie um novo recurso ou novos recursos.
  • HTTP PUT: atualize um recurso ou uma coleção de recursos existente.
  • HTTP DELETE: exclua um recurso ou uma coleção de recursos.

Como eu já introduzi o método GET , iniciarei minhas descrições com o POST. Continuarei usando o exemplo do app Contatos para explicar estes outros métodos.

POST

Normalmente, um novo contato é criado ao se preencher um formulário. Ou seja, um formulário HTML é POSTado no servidor e o servidor, então, cria e persiste o contato recentemente criado. A Listagem 5 demonstra a lógica do lado do servidor para isso.

Listagem 5. Aceitar o envio de formulário (POST) e criar um novo contato
@POST
@Produces(MediaType.TEXT_HTML)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newContact(
		@FormParam("id") String id,
		@FormParam("name") String name,
		@Context HttpServletResponse servletResponse
) throws IOException {
	Contact c = new Contact(id,name,new ArrayList<Address>());
	ContactStore.getStore().put(id, c);
		
	URI uri = uriInfo.getAbsolutePathBuilder().path(id).build();
	Response.created(uri).build();
		
	servletResponse.sendRedirect("../pages/new_contact.html");
}

Observe que as seguintes partes fazem esse exemplo funcionar.

  • @Consumes: declara que o método consume um FORMULÁRIO HTML.
  • @FormParam: introduz a entrada do formulário, identificada pelo atributo HTML name, a esse método.
  • @Response.created(uri).build(): constrói um novo URI para o contato recentemente criado como /contacts/{id} e configura o código de resposta como 201/created. É possível acessar o novo contato usando http://localhost:8080/Jersey/rest/contacts/<id>.

PUT

Eu uso o método PUT para atualizar um recurso existente. No entanto, isso também pode ser implementado como uma atualização ou criando um recurso, conforme mostrado no fragmento de código na Listagem 6.

Listagem 6. Aceitar a solicitação PUT e criar ou atualizar um contato
@PUT
@Consumes(MediaType.APPLICATION_XML)
public Response putContact(JAXBElement<Contact> jaxbContact) {
	Contact c = jaxbContact.getValue();
	return putAndGetResponse(c);
}

private Response putAndGetResponse(Contact c) {
	Response res;
	if(ContactStore.getStore().containsKey(c.getId())) {
		res = Response.noContent().build();
	} else {
		res = Response.created(uriInfo.getAbsolutePath()).build();
	}
	ContactStore.getStore().put(c.getId(), c);
	return res;
}

Eu cubro vários conceitos diferentes neste exemplo, destacando os seguintes conceitos.

  • Consumir XML: o método putContact() aceita o tipo de conteúdo de solicitação de APPLICATION/XML, enquanto essa entrada XML será ligada ao objeto Contato usando JAXB. Você localizará o código do cliente na próxima seção.
  • Resposta vazia com código de status diferente: a resposta da solicitação PUT não terá nenhum conteúdo, mas terá um código de status diferente. Se o contato já existir no datacenter, atualizarei esse contato e retornarei 204/no content. Se houver um novo contato, o criarei e retornarei 201/created.

DELETE

É bastante simples implementar um método DELETE . Dê uma olhada na Listagem 7 para obter um exemplo.

Listagem 7. Excluir um contato identificado por seu ID
@DELETE
public void deleteContact() {
	Contact c = ContactStore.getStore().remove(contact);
	if(c==null)
		throw new NotFoundException("No such Contact.");
}

Representação

Nas seções anteriores, ilustrei vários tipos de representação. Agora, passarei brevemente por elas e daremos uma olhada na representação JAXB. Outros tipos de representação suportados são byte[], InputStream, Arquivo e assim por diante.

  • Sequência: texto simples.
  • Resposta: uma resposta HTTP genérica que pode conter seu conteúdo customizado com um código de resposta diferente.
  • Vazio: uma resposta vazia com um código de status de 204/no content.
  • Classe do recurso: delegue o processo a essa classe do recurso.
  • POJO: JavaBeans que são anotados com @XmlRootElement, que o torna um bean JAXB e que é possível ligar ao XML.
  • Coleção de POJOs: uma coleção de beans JAXB.

O JAX-RS suporta o uso de JAXB (API Java para XML Binding) para ligar um JavaBean ao XML ou JSON e vice-versa. O JavaBean deve ser anotado com @XmlRootElement. A Listagem 8 toma o bean Contato como um exemplo. Os campos sem uma anotação @XmlElement explícita serão nomeados da mesma forma que o elemento XML. A Listagem 9 exibe a representação XML e JSON serializada para um bean Contato. A representação para uma coleção de contatos é muito parecida e tem <Contatos> como o elemento agrupador, por padrão.

Listagem 8. Bean Contato
@XmlRootElement
public class Contact {
	private String id;
	private String name;
	private List<Address> addresses;
	
	public Contact() {}
	
	public Contact(String id, String name, List<Address> addresses) {
		this.id = id;
		this.name = name;
		this.addresses = addresses;
	}

	@XmlElement(name="address")
	public List<Address> getAddresses() {
		return addresses;
	}

	public void setAddresses(List<Address> addresses) {
		this.addresses = addresses;
	}
	// Omit other getters and setters
}
Listagem 9. A representação para um Contato
representação XML:
<contact>
  <address>
    <city>Shanghai</city>
    <street>Long Hua Street</street>
  </address>
  <address>
    <city>Shanghai</city>
    <street>Dong Quan Street</street>
  </address>
  <id>huangyim</id>
    <name>Huang Yi Ming</name>
</contact>


JSON representation:
{"contact":[{"address":[{"city":"Shanghai","street":"Long
            Hua Street"},{"city":"Shanghai","street":"Dong Quan
            Street"}],"id":"huangyim","name":"Huang Yi Ming"}]}

Clientes que se comunicam com serviços REST

No exemplo até agora, desenvolvi um serviço da web RESTful que suporta contatos CRUD. Agora, explicarei como comunicar-se com esse serviço REST usando as APIs de cliente curl e Jersey. Ao fazê-lo, testarei o código do lado do servidor e darei mais informações sobre as tecnologias do lado do cliente.

Usar curl para comunicar-se com o servidor REST

Curl é uma ferramenta de linha de comando popular que pode enviar solicitações a um servidor usando protocolos como HTTP e HTTPS. É uma boa ferramenta para comunicar-se com os serviços da web RESTful porque ela pode enviar conteúdo por qualquer método HTTP. Curl já é distribuído com o Linux® e com o Mac, e há um utilitário que pode ser instalado para o Windows.

Agora, vamos inicializar o primeiro comando curl que recebe todos os contatos. É possível consultar a Listagem 3 para obter o código do lado do servidor.

curl http://localhost:8080/Jersey/rest/contacts

A resposta estará em XML e conterá todos os contatos.

Note que o método getContacts() também produz uma resposta tipo MIME do aplicativo/json. É possível solicitar conteúdo nesse tipo também.

curl –HAccept:application/json http://localhost:8080/Jersey/rest/contacts

A resposta será uma sequência JSON, que contém todos os contatos.

Agora irei inserir um novo contato utilizando o comando PUT . Observe que o método putContact() na Listagem 6 aceita XML e usa JAXB para ligar o XML ao objeto Contato.

curl -X PUT -HContent-type:application/xml --data "<contact><id>foo</id>
                <name>bar</name></contact>" http://localhost:8080/Jersey/rest/contacts/foo

Um novo contato identificado por "foo" é incluído ao armazenamento de contatos. É possível usar o URI /contacts ou /contacts/foo para verificar a coleção de contatos ou um contato individual.

Usando o Jersey Client para comunicar-se com o serviço REST

O Jersey também fornece uma biblioteca do cliente que o ajuda a se comunicar com o servidor, bem como com a unidade, para testar os serviços RESTful. A biblioteca é uma implementação genérica que pode cooperar com qualquer serviço da web baseado em HTTP/HTTPS.

A classe central para o cliente é a classe WebResource . Você a usa para construir uma URL de solicitação baseada no URI raiz e, em seguida, envia as solicitações e recebe as respostas. A Listagem 10 mostra como criar uma instância WebResource . Observe que WebResource é um objeto pesado, por isso, crie-o apenas uma vez.

Listagem 10. Criar a instância WebResource
Client c = Client.create();
WebResource r=c.resource("http://localhost:8080/Jersey/rest/contacts");

O primeiro exemplo de cliente Jersey é enviar uma solicitação GET para buscar todos os contatos e imprimir o código de status da resposta e o conteúdo da resposta. Veja a Listagem 11.

Listagem 11. OBTER todos os contatos e imprimir a resposta
ClientResponse response = r.get(ClientResponse.class);
System.out.println( response.getStatus() );
System.out.println( response.getHeaders().get("Content-Type") );
String entity = response.getEntity(String.class);
System.out.println(entity);

A Listagem 12 mostra um segundo exemplo, que cria um novo contato identificado por "foo".

Listagem 12. Criar um contato
Address[] addrs = {
	new Address("Shanghai", "Ke Yuan Street")
};
Contact c = new Contact("foo", "Foo Bar", Arrays.asList(addrs));

ClientResponse response = r
	.path(c.getId())
	.accept(MediaType.APPLICATION_XML)
	.put(ClientResponse.class, c);
System.out.println(response.getStatus());

Observe as APIs para a instância WebResource . Ela constrói o URI, configura os cabeçalhos de solicitação e chama a solicitação em uma linha de código. O conteúdo (objeto Contato) é ligado a XML automaticamente.

A Listagem 13 mostra o último exemplo, que recupera o contato identificado por "foo" (que eu criei no último exemplo) e, então, o exclui.

Listagem 13. Recuperar contato "foo" e excluí-lo
GenericType<JAXBElement<Contact>> generic = new GenericType<JAXBElement<Contact>>() {};
JAXBElement<Contact> jaxbContact = r
	.path("foo")
	.type(MediaType.APPLICATION_XML)
	.get(generic);
Contact contact = jaxbContact.getValue();
System.out.println(contact.getId() + ": " + contact.getName());

ClientResponse response = r.path("foo").delete(ClientResponse.class);
System.out.println(response.getStatus());

Observe aqui que quando você deseja obter uma resposta que é um bean JAXB, é necessário usar o recurso de tipo genérico introduzido no Java 2 Platform, Standard Edition (J2SE).

Reproduza esses exemplos usando o cliente Jersey. Também é possível localizar mais códigos de amostra no pacote de origem (veja Download). Consulte também o website do Jersey para obter mais informações.

Conclusão

O Jersey pode ser integrado com outras estruturas ou bibliotecas de utilitário usando as bibliotecas de integração do Jersey. Atualmente, o Jersey pode integrar-se com o Spring e com o Guice, além de suportar representação ATOM com integração apache-adbera. Você localizará as APIs e os guias nas Noções Básicas da página inicial do projeto Jersey.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=1057970
ArticleTitle=Desenvolver um serviço da web RESTful usando o Jersey e o Apache Tomcat
publish-date=12192016