Webservices usando JAX-RS 2 e Spring

Neste post será mostrado como é possível criar facilmente um webservice RESTful utilizando Jersey 2 e Spring, a opção pelo Spring é para que seja feito o deploy usando Tomcat como container Web e desta maneira diminuir a complexidade da solução. Toda estrutura de dependências do projeto é gerenciada pelo Maven. Serão cobertos desde a configuração de dependências até testes automáticos da aplicação.

Dependências

Abaixo são mostradas as dependências necessárias para desenvolver um conjunto de serviços e seus testes, usando o Moxy como tradutor de JSon, sendo está a implementação de referência do JSON-Binding Provider para a estrutura deste exemplo, que conta com Jersey 2.7 como implementação de referência de JAX-RS 2 (JSR339) e Spring 3. Para os testes são usados o JUnit, o Jersey-test e o Spring-test.

Propositalmente foram omitidas algumas versões abaixo, deixando explicitas apenas as versões ligadas ao Jersey.

<properties>
<org.springframework.version>3.2.6.RELEASE</org.springframework.version>
<jersey.version>2.7</jersey.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

<!-- Jersey 2.x + Spring -->
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>${jersey.version}</version>
</dependency>

<!-- Jersey 2.x -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
<scope>compile</scope>
</dependency>

<!-- Moxy -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- Fim Moxy -->

<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-bundle</artifactId>
<type>pom</type>
<scope>test</scope>
<version>${jersey.version}</version>
</dependency>

<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>${jersey.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Listagem 1 – Dependências

Web.xml, context.xml e ResourceConfig

Este projeto de exemplo será implementado usando a versão 2.5 do Servlet, caso seja usada a versão 3.0 o arquivo Web.xml se torna desnecessário, veja mais detalhes aqui. Abaixo encontram-se os arquivos Web.xml(configuração da aplicação WEB), o context.xml(configuração do Spring) e a implementação da classe ResourceConfig, neste último faremos o registro explícito das classes que implementam os serviços, outra opção é usar a funcionalidade de descoberta automática, mais informações aqui. Para este exemplo temos registradas as classes HealthCheckWS apenas responsável por emitir uma pequena mensagem de que a aplicação está de pé e MockPessoaWS classe que implementa um pequeno exemplo para cada uma das operações de manipulação de dados.

<?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">

<module-name>samples_service</module-name>

<context-param>
<param-name>webAppRootKey</param-name>
<param-value>samples_ws</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/context.xml</param-value>
</context-param>

<servlet>
<servlet-name>SpringApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>[Pacote].SamplesResourceConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SpringApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>

Listagem 2 – Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:annotation-config />
<context:component-scan base-package="[Pacote de serviços]" />
</beans>

Listagem 3 – context.xml

public class SamplesResourceConfig extends ResourceConfig {

public SamplesResourceConfig() {
register(HealthCheckWS.class);
register(MockPessoaWS.class);
}
}

Listagem 4 – Implementação de ResourceConfig

O serviço HealthCheckWS

Está uma a implementação de um serviço muito simples que devolve apenas a sequencia de caracteres “UP”, apesar de simples, é aconselhável ter uma implementação deste tipo para todo serviço, de maneira que seja possível usá-la em serviços de monitoramento e também para uso com balanceadores de carga, deste modo teremos um serviço leve que compromete muito pouco da capacidade do servidor para ser usado para indicar que uma máquina saiu do ar e também que ela já se encontra operacional.

Implementação

@Component
@Path("/health_check")
public class HealthCheckWS {

@GET
@Produces("text/plain")
public String getIt() {
return "UP";
}
}

Listagem 5 – Classe HealthCheckWS

Teste

Para execução dos testes será usada uma classe responsável por criar um servidor e subir a aplicação para a execução dos testes, seguem abaixo os códigos fontes tanto da classe App, responsável por criar o servidor de testes, e o código da classe de teste para o serviço health_check, esse teste envia uma requisição do tipo Get para o serviço a ser testado.

public class App {
private static final URI BASE_URI = URI.create("http://localhost:8080/mock_pessoas/");
public static void main(String[] args) {
try {
System.out.println("JSON with Jackson Jersey Example App");
final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, createApp());
System.out.println(String.format("Application started.%nHit enter to stop it..."));
System.in.read();
server.shutdownNow();
} catch (IOException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
}
}

public static ResourceConfig createApp() {
return new ResourceConfig().packages("[Pacote contendo os Serviços]").register(createMoxyJsonResolver());
}

public static ContextResolver<MoxyJsonConfig> createMoxyJsonResolver() {
final MoxyJsonConfig moxyJsonConfig = new MoxyJsonConfig();
Map<String, String> namespacePrefixMapper = new HashMap<String, String>(1);
namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
moxyJsonConfig.setNamespacePrefixMapper(namespacePrefixMapper).setNamespaceSeparator(':');
return moxyJsonConfig.resolver();
}
}

Listagem 6 – Classe App.java

public class HealthCheckTest extends JerseyTest {

@Override
protected ResourceConfig configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);

return App.createApp();
}

@Override
protected void configureClient(ClientConfig config) {
}

@Test
public void testHealthCheck() {
WebTarget target = target();
String responseMsg = target.path("health_check").request().get(String.class);
assertTrue(responseMsg.startsWith("UP"));
}
}

Listagem 7 – HealthCheckTest

O serviço MockPessoasWS

@Component
@Path("/mock_pessoas")
public class MockPessoaWS {

private static List<PessoaBean> mockPessoas;
private static AtomicLong idGenerator;

...

}

Listagem 8 – Estrutura da classe MockPessoasWS

GET

Foram implementadas duas possibilidades de saída para requests do tipo GET, quando a URL usada é apenas /samples/mock_pessoas, a saída é a listagem completa de pessoas registradas no momento, quando a URL utilizada é composta por /samples/mock_pessoas/[ID], as informações do registro da pessoa com este ID são devolvidas. As saídas são em JSON, e  foi utilizada  a anotação @JSONP para que as requisições possam vir de domínios diversos, leia sobre JSONP aqui.

@GET
@JSONP
@Produces(MediaType.APPLICATION_JSON)
public List<PessoaBean> getPessoas() {
return mockPessoas;
}

@GET
@JSONP
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public PessoaBean getPessoaEspecifica(@PathParam("id") long id) {

try {
for (PessoaBean p : mockPessoas) {
if (p.getId() == id) {
return p;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

Listagem 9 – Implementação do GET

POST e PUT

As implementações de POST e PUT se confundem, pois ambas consistem na gravação de uma entidade no repositório, pode-se implementar rotinas que caso o registro já exista apenas seja feita uma atualização, independente do tipo de request, ou podemos seguir as seguinte definições e implementar o POST exclusivamente para inserir novos registros, enquanto o PUT fica responsável exclusivamente por atualizar as informações de um registro.

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public PessoaBean createPessoa(PessoaBean pessoa) {

// Lógica para Incluir um registro

}

Listagem 10 – Exemplo POST

@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public PessoaBean updatePessoa(PessoaBean pessoa) {

// Lógica para Atualizar um registro

}

Listagem 11 – Exemplo PUT

DELETE

@DELETE
@Path("{id}")
public void deletePessoa(@PathParam("id") long id) {

// Lógica para excluir um registro

}

Listagem 12 – Exemplo DELETE

Testando o serviço MockPessoas

public class MockPessoasTest extends JerseyTest {

@Override
protected ResourceConfig configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);

return App.createApp();
}

@Override
protected void configureClient(ClientConfig config) {
}

@Test
public void testGet() {
final WebTarget target = target("mock_pessoas/5");
final PessoaBean testBean = target.request(MediaType.APPLICATION_JSON_TYPE).get(PessoaBean.class);

assertNotNull(testBean);
assertEquals((long) 5, (long) testBean.getId());
}

@Test
public void testPost() {
final WebTarget target = target("mock_pessoas");

PessoaBean aEnviar = new PessoaBean();
aEnviar.setNome("enviado");
aEnviar.setCPF("111.111.111-11");
aEnviar.setNascimento(new Date());

final PessoaBean testBean = target.request(MediaType.APPLICATION_JSON_TYPE).post(
Entity.entity(aEnviar, MediaType.APPLICATION_JSON_TYPE), PessoaBean.class);

assertNotNull(testBean);
assertEquals("enviado", testBean.getNome());
assertNotNull(testBean.getId());
}

@Test
public void testDelete() {
final WebTarget delete = target("mock_pessoas/5");
delete.request().delete();

final WebTarget target = target("mock_pessoas/5");
final PessoaBean testBean = target.request(MediaType.APPLICATION_JSON_TYPE).get(PessoaBean.class);

assertNull(testBean);
}

@Test
public void testPut() {
final WebTarget target = target("mock_pessoas/3");

PessoaBean testBean = target.request(MediaType.APPLICATION_JSON_TYPE).get(PessoaBean.class);
testBean.setNome("alterado");

final WebTarget targetPut = target("mock_pessoas");

PessoaBean putBean = targetPut.request(MediaType.APPLICATION_JSON_TYPE).put(
Entity.entity(testBean, MediaType.APPLICATION_JSON_TYPE), PessoaBean.class);

assertEquals(testBean.getId(), putBean.getId());
assertEquals("alterado", putBean.getNome());

testBean = target.request(MediaType.APPLICATION_JSON_TYPE).get(PessoaBean.class);

assertNotNull(testBean);
assertEquals("alterado", testBean.getNome());
}
}

Listagem 13 – Testes do serviço MockPessoas

Conclusão

Com a evolução do Jax-RS pode-se de maneira muito simples e direta construir webservices de acordo com os padrões RESTful, no exemplo mostrado neste post alguns pontos não são abordados, sendo um dos mais importantes a manipulação dos Status Code e a criação de testes que cubram tal tipo de implementação, convido o leitor a implementar seus próprios serviços e a explorar os links das referências, existe um grande volume de conhecimento a ser explorado a cerca do Jersey e demais componentes desse exemplo.

Referências

Documentação do Jersey

Manipulação de Status Code

Informações sobre a implementação usando Servlet 3.0

Deploying a RESTful webservice

Implementação de JSONP com Jersey 2

Por LUIZ COUTO

Postado em: 23 de maio de 2014

Confira outros artigos do nosso blog

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

Three laws that enable agile software development

09 de março de 2017

Celso Gonçalves Junior

Medindo performance de uma API REST

21 de fevereiro de 2017

Monise Costa

Deixe seu comentário