Para começar vamos entender o que é um serviço REST: Representational State Transfer é um estilo arquitetural para aplicações cliente-servidor distribuídas e descentralizadas sobre a estrutura da Web. Este estilo tem como diferencial a utilização da infraestrutura web existente, como servidores, bibliotecas de clientes, entre outros, permitindo uma maneira simples de organizar as interações entre sistemas independentes.
Ao ser criado, o REST reuniu um conjunto de restrições arquiteturais descritas abaixo:
- Cliente/Servidor: As responsabilidades devem ser separadas entre o cliente e o servidor.
- Stateless: A comunicação entre o cliente e o servidor não deve ter estado. O servidor não necessita saber o estado do cliente. Ao invés disso, o cliente deve incluir todas as informações necessárias na própria requisição.
- Sistema em Camadas: Múltiplas camadas podem existir entre o cliente e o servidor, como gateways, firewalls e proxies. Camadas podem ser adicionadas e removidas de forma transparente.
- Cache: Respostas do servidor devem ser declaradas como “cacheable” ou “noncacheable”, permitindo que o cliente ou seus intermediários utilizem novamente uma resposta para requisições posteriores para melhoria de performance.
- Código sob demanda: Clientes podem estender suas funcionalidades baixando códigos executáveis sob demanda.
- Interface Uniforme: Todas as interações entre o cliente e o servidor devem ser realizadas baseadas na uniformidade de suas interfaces.
O conceito mais importante para entendermos o REST é o conceito de recurso, podendo ser definido como qualquer informação que pode ser acessada e manipulada por clientes remotos, como postagens de blogs, informações de produtos, lista de filmes, um perfil de um usuário em uma rede social, etc.
Estes recursos para serem acessados remotamente devem ser endereçados através de um identificador único, denominado Uniform Resource Identifier (URI), sendo que um recurso pode ser identificado por diversas URIs, mas somente uma URI pode identificar um recurso.
Como exemplo de endereçamento vamos pensar em serviço que fornece informações sobre filmes, então vamos ver as URI para este serviço:
URI | Descrição |
---|---|
http://app.exemplo.com.br/filmes | Endereça uma coleção de recursos “filmes” |
http://app.exemplo.com.br/filmes/1 | Endereça um recurso “filme” com o identificador 1 |
http://app.exemplo.com.br/atores/1/filmes | Endereça uma coleção de filmes associadas a um ator identificado pelo identificador 1 |
Outro aspecto importante relacionado ao recurso é sua representação, já que um recurso não pode ser acessado diretamente, sendo necessário para isto a sua serialização, associando-o a uma representação. Uma representação de recurso consiste em uma amostra dos valores de suas propriedades em um determinado momento. Uma URI deve estar sempre associada a pelo menos uma representação de recurso, podendo o cliente solicitar qual representação deseja através da própria URI ou por headers de requisição como o accept.
A representação de recursos mais utilizada atualmente para serviços REST é o JavaScript Object Notation (JSON). JSON é um formato de texto para a serialização de dados estruturados, sendo derivado de objetos Javascript. Ele pode representar tipos primitivos como strings, numbers, booleans, null e dois tipos estruturados (objetos e array). Um objeto JSON consiste em uma coleção de um ou mais pares chave/valor, onde a chave é um valor string e valor qualquer tipo suportado.
[ { "id":354, "nome":"O Senhor dos Anéis: A Sociedade do Anel", "ano":2001, "duracao":null, "generos":[ "Aventura", "Drama", "Fantasia" ], "diretor":"Peter Jackson", "cast":{ "Boromir":{ "id":54, "nome":"Sean Bean", "nascimento":"1959-04-17" }, "Gandalf":{ "id":99, "nome":"Ian McKellen\t", "nascimento":"1939-05-25" }, "Legolas":{ "id":47, "nome":"Orlando Bloom", "nascimento":"1977-01-13" } } } ]
Outro formato muito utilizado para a representação de recurso é o Extensible Markup Language (XML), que consiste em uma linguagem de marcação para descrever e estruturar informações, assemelhando-se com a linguagem HyperText Markup Language (HTML), possuindo marcações para descrever os dados. Diferente do HTML as marcações não são definidas na linguagem, sendo possível a definição de marcações novas para atender a necessidades específicas.
<filmes> <filmes> <ano>2001</ano> <cast> <entry> <key>Boromir</key> <value> <id>54</id> <nascimento>1959-04-17</nascimento> <nome>Sean Bean</nome> </value> </entry> <entry> <key>Gandalf</key> <value> <id>99</id> <nascimento>1939-05-25</nascimento> <nome>Ian McKellen </nome> </value> </entry> <entry> <key>Legolas</key> <value> <id>47</id> <nascimento>1977-01-13</nascimento> <nome>Orlando Bloom</nome> </value> </entry> </cast> <diretor>Peter Jackson</diretor> <generos>Aventura</generos> <generos>Drama</generos> <generos>Fantasia</generos> <id>354</id> <nome>O Senhor dos Anéis: A Sociedade do Anel</nome> </filmes> </filmes>
Quando um cliente desejar interagir com algum recurso poderá executar uma ação de leitura ou enviar um novo recurso para ser armazenado/processado, ou mesmo solicitar a remoção de um recurso. Como uma das restrições do padrão REST é a Interface Uniforme, deve ser utilizado os métodos corretos definidos pelo Hypertext Transfer Protocol (HTTP) para realizar cada uma das operações sobre os recursos. Os verbos HTTP mais utilizados são:
- GET: Método utilizado para recuperar uma representação de um recurso;
- POST: Utilizado para criar um novo recurso, normalmente espera que o novo recurso seja enviado no corpo da requisição. O método POST é utilizado em outros casos, onde nenhum outro método parece apropriado;
- PUT: Utilizado para modificar o estado de um recurso já existente;
- DELETE: Utilizado para remover um recurso
Outra restrição importante para manter a Interface Uniforme é a utilização correta dos códigos de status determinados pelo protocolo HTTP. Os códigos de status representam o resultado de uma requisição e estão organizados da seguinte forma:
- 1XX – Informações;
- 2XX – Sucessos;
- 3XX – Redirecionamentos;
- 4XX – Erros causados pelo cliente;
- 5XX – Erros causados no servidor.
Vale a pena citar os códigos mais utilizados: 200 (OK), normalmente utilizado ao retornar uma representação de recurso; 201 (CREATED), quando um novo recurso foi criado; 404 (NOT FOUND), para um recurso não encontrado, bastante conhecido; 401 (Unauthorized), utilizado quando um cliente necessita de autenticação para acessar o recurso ou forneceu credenciais inválidas.
Os códigos mais específicos você pode ver aqui.
Agora vamos ao JAX-RS que é a especificação oficial que define um conjunto de APIs para o desenvolvimento de serviços de acordo com o estilo arquitetural REST em Java, provendo um conjunto de anotações e classes/interfaces que podem ser utilizados para expor recursos independente de formato. O Jersey RESTful Web Services framework é a implementação de referência da especificação JAX-RS, sendo um framework open-source.
Agora que temos os conceitos iniciais vamos começar a implementação, então criamos um projeto maven e adicionamos as seguintes dependências ao arquivo pom.xml:
... <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>2.22.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.9.5</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.22.2</version> </dependency> ...
A dependência jersey-container-servlet é o core do JAX-RS, que já vem com suporte ao XML, como queremos também ter suporte a JSON, incluímos a dependência jersey-media-json-jackson. Para manipular datas necessitaremos também da dependência jackson-datatype-jsr310, que configuraremos mais adiante.
Uma vez com as dependências em ordem vamos a configuração do web.xml para configurar o mapeamento das requisições para o servlet do Jersey, que irá receber requisições e encaminhara para as classes que mapeiam os recursos.
<servlet> <servlet-name>Jersey Web Application</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.packages</param-name> <param-value>filmes</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Jersey Web Application</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
Como visto no trecho acima, mapeamos o servlet org.glassfish.jersey.servlet.ServletContainer com o parâmetro de inicialização apontando para o pacote “filmes” onde deverão estar as classes anotadas para o framework Jersey encontrá-las. No mapeamento de url-pattern utilizamos “/*” para que todas as requisições sejam pegas e analisadas pelo framework.
O próximo passo é criar nossas classes de entidades, no exemplo de filme que iremos utilizar teremos somente Filme e Ator.
@XmlRootElement(name="filme") public class Filme { private Long id; private String nome; private Integer ano; private Long duracao; private List<String> generos; private String diretor; private Map<String,Ator> cast = new HashMap<String, Ator>(); /* Geters e seters omitidos*/ } @XmlRootElement(name="ator") public class Ator { private Long id; private String nome; private LocalDate nascimento; /* Geters e seters omitidos*/ }
Agora devemos criar nossas classes de mapeamento de recursos, e para referência veja o quadro com os mapeamentos que devemos fazer.
HTTP Method | URI do Recurso | Entrada | Resposta | Descrição |
---|---|---|---|---|
GET | /filmes | Corpo: Vazio |
Status: 200 Corpo: Lista de recursos Filme |
Recuperar todos os recursos filmes |
POST | /filmes | Corpo: representação do recurso a ser adicionado. |
Status: 201 Corpo: Vazio |
Adicionar um novo recurso filme |
GET | /filmes/{filmeId} | Corpo: Vazio |
Status: 200 ou 404 caso recurso não exista. Corpo: recurso Filme |
Recuperar um recurso filme existente pelo seu identificador. |
DELETE | /filmes/{filmeId} | Corpo: Vazio |
Status: 200 Corpo: Vazio |
Remover um recurso filme existente |
PUT | /filmes/{filmeId} | Corpo: representação do recurso a ser alterado. |
Status: 200 ou 404 caso recurso não exista. Corpo: recurso Filme atualizado |
Atualizar um recurso filme existente |
Primeiro devemos anotar a classe que irá mapear o recurso com a anotação @Path que deverá conter a base de sua URI, como visto na nossa tabela acima, todas nossas URIs iniciam com “filmes”, já que é o nome do nosso recurso. Caso não tenha um padrão de inicio basta utilizar “/” como início da URI.
@Path("/filmes") public class FilmeController { }
Dentro desta classe é onde colocaremos os método Java que irão mapear as operações que serão realizadas sobre os recursos, sendo um método para cada operação. Vamos começar com a operação de listar todos os recursos.
... @GET @Path("/") @Produces({ MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML}) public Response listar(){ List<Filme> filmes = filmeRepository.getFilmes(); GenericEntity<List<Filme>> entity = new GenericEntity<List<Filme>>(filmes) {}; return Response.status( 200 ).entity(entity).build(); } ...
A primeira anotação que vemos é @GET que define que este método irá mapear uma requisição realizada através do method GET. O JAX-RS fornece uma anotação para cada method, sendo elas @GET, @POST, @PUT, @DELETE.
Na segunda anotação temos @Path(“/”), como devemos mapear esta operação sobre o recurso usando a URI “/filmes” e já temos a parte “/filmes” na anotação @Path da classe necessitamos colocar no parâmetro desta anotação somente o caminho “/”.
A terceira anotação que utilizamos é a @Produces que indica para o framework qual representação de recurso pode ser gerada como resposta. Passamos por parâmetro os tipos de representação que esta operação poderá gerar através do valor de seu mime type, sendo informado através de strings como como “application/json”, “application/xml” ou “text/plain” ou dos dos valores constantes da classe MediaType como APPLICATION_JSON, APPLICATION_XML ou TEXT_PLAIN. O formato pode ser selecionado pela requisição através do header accept passado pela requisição, neste caso poderíamos escolher receber as informações no formato XML passando o header Accept: application/xml ou header Accept: application/json. Se nenhuma tipo de representação for solicitada será enviada a primeira da lista da anotação.
Lembrando que o JAX-RS tem suporte para as combinações de tipos Java e os formatos de representação mais comuns, mas ele não fornece suporte para a conversão de tipos Java para formatos de representação de recurso, mas fornece um conceito genérico de extensão que permite, em tempo de execução, a adição do suporte a estas conversões. Estes componentes da JAX-RS extension API que fornecem este tipo de extensibilidade são tipicamente referidos como entity providers, message body providers, message body works ou message body readers and writers, e podem ser utilizados de fornecedores diversos ou escritos pelo próprio programador, se este desejar um formato próprio. Neste exemplo utilizamos o entity provider jersey-media-json-jackson para a conversão do formato JSON, que ao ser adicionado como dependência do projeto é automaticamente carregado.
Embora o framework JAX-RS possa gerar a resposta simplesmente retornando pelo método o objeto que se deseja converte/serialzar para a representa em JSON ou XML, é uma melhor prática colocar este objeto dentro de um objeto Response e retorná-lo, sendo que através dele podemos adicionar na resposta um código de retorno ou headers.
A classe Response fornece diversos métodos para construir uma resposta, para configurar o código de resposta utilizamos um dos seus métodos estáticos como: ok(), ok(Entity), create(URI), noContent() ou status(int) que retornam um objeto ResponseBuilder, que permite adicionar uma entidade(conteúdo da resposta) através do método entity e adicionar um header através do método header(String chave, String valor). Após adicionadas todas as informações necessárias através do objeto ResponseBuilder basta chamar o método build() que irá criar um objeto Response que será retornado pelo método e o JAX-RS irá gerar a resposta HTTP para o cliente.
Um ponto que vale ressaltar é que o Jersey com seu “entity provider” padrão para XML não consegue converter as coleções List, sendo necessário colocar dentro de um objeto GenericEntity, como visto na linha 9.
Veja um exemplo de requisição feita pelo Postman.
Vamos ao exemplo de buscar um recurso pelo id.
... @GET @Path("/{id}") @Produces({MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML}) public Response buscarPorId( @PathParam("id") Long id ){ Filme filme = filmeRepository.buscarPorId(id); if(filme != null) { return Response.ok( filme ).build(); }else { return Response.status( Status.NOT_FOUND ).build(); } } ...
Como vimos as anotações são praticamente as mesmas, somente utilizamos uma modificação na anotação @Path que no parâmetro de URI é utilizado um “URI Template” que consiste em uma forma de representar a estrutura de uma URI, onde é possível definir um ou mais parâmetros que irão substituir partes dela antes de ser resolvida, inclusive permitindo validações via expressões regulares para garantir que somente parâmetros de certos formatos sejam válidos e aceitos. Para definir um parâmetro na URI devemos envolver uma palavra por chaves, por exemplo, o URI Template “/filmes/{id}” corresponderá as
URIs “/filmes/1”, “/filmes/2” e “/filmes/255”. Para termos acesso ao valor do(s) URI Template anotamos o parâmetro do método com a anotação @PathParam() e no parâmetro desta passamos o valor que está entre chaves.
No exemplo utilizamos o parâmetro para realizar uma busca no repositório, se retornar um objeto construímos uma resposta utilizando o método ok() passando a entidade, se não utilizamos o método status passando como parâmetro um dos valores do enum Status, no caso “NOT_FOUND” que corresponde ao erro 404.
Agora o exemplo de adicionar.
... @POST @Path("/") @Consumes({MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML}) public Response adicionar( Filme filme ){ filmeRepository.save(filme); URI uri = URI.create( "/filmes/"+filme.getId() ); return Response.created( uri ).build(); } ...
Para adicionar utilizamos uma requisição POST, então devemos adicionar a anotação @POST ao invés de @GET, além disto necessitamos adicionar uma anotação, @Consumes, muito semelhante a anotação @Produces define quais os formatos de recurso podem ser enviados no corpo da requisição POST a qual será convertida em um objeto Java que será passado como parâmetro do método.
Para gerar resposta utilizamos o método created que recebe um objeto URI com a url do recurso que será criado, isso irá fazer com que seja retornado um header com a chave location e valor da uri que identifica o novo recurso criado.
Agora temos os métodos de deletar e atualizar que utilizam os recursos já apresentados.
... @DELETE @Path("/{id}") public Response deletar( @PathParam("id") Long id ){ filmeRepository.delete(id); return Response.status( Status.OK ).build(); } ...
... @PUT @Path("/{id}") @Consumes({ MediaType.APPLICATION_JSON , MediaType.APPLICATION_XML}) public Response alterar(@PathParam("id") Long id , Filme filme){ Filme f = filmeRepository.buscarPorId( id ); if( f == null ){ return Response.status( Status.NOT_FOUND ).build(); }else{ filmeRepository.update( filme ); return Response.status( 200 ).entity(filme).build(); } } ...
Para adicionar suporte as datas do Java 8 ao entity provider do JSON devemos criar uma classe de configuração anotada com a anotação @Provider como visto abaixo.
@Provider public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { private final ObjectMapper mapper; public ObjectMapperContextResolver() { mapper = new ObjectMapper(); mapper.registerModule(new JSR310Module()); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); } public ObjectMapper getContext(Class<?> arg0) { return mapper; } }
Para adicionar o suporte a datas do Java 8 ao formato XML, devemos criar um adapter estendendo um XmlAdapter e implementando os métodos unmarshal e marshal para converter de String para LocalDate e de LocalDate para String. Para outros tipos de dados devem ser criadas outras classes adapter.
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> { @Override public LocalDate unmarshal(String dateString) throws Exception { return LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE); } @Override public String marshal(LocalDate localDate) throws Exception { return DateTimeFormatter.ISO_DATE.format(localDate); } }
Com o adapter criado devemos informar que o campo nascimento da classe Ator deve utilizar este adapter para a serialização.
@XmlRootElement(name="ator") @XmlAccessorType(XmlAccessType.FIELD) public class Ator { private Long id; private String nome; @XmlJavaTypeAdapter( LocalDateAdapter.class ) private LocalDate nascimento; ... }
Para baixar o exemplo clique aqui
Bom era isso o básico de uso de JAX-RS, te vejo numa pŕoxima.
0sem comentários ainda