Java EE 7 e JAX-RS 2.0

Por Adam Bien,
Postado em Fevereiro 2014

Este artigo faz parte de uma série na qual são apresentadas as novas características e funcionalidades do Java EE 7. Para obter mais informações sobre a Especificação da plataforma Java EE visite Java.net. 

O Java EE 7 com JAX-RS 2.0 traz diversas características úteis que tornam ainda mais simples o desenvolvimento e permitem a criação de aplicações com a arquitetura RESTful para Java SE/EE ainda mais sofisticadas mas, ao mesmo tempo, leves.

Downloads:

Código de exemplo (ZIP)

A maioria das aplicações do Java EE 6, que precisam de uma API remota e oferecem liberdade de escolha para sua configuração, utilizam uma especificação do JAX-RS 1.0 que se aproxima bastante das características da arquitetura RESTful. O Java EE 7 com JAX-RS 2.0 traz diversas características úteis que tornam ainda mais simples o desenvolvimento e permitem a criação de aplicações com a arquitetura RESTful para Java SE/EE ainda mais sofisticadas mas, ao mesmo tempo, leves.

Roast House

Roast House é um exemplo simples do JAX-RS 2.0 para Java que gerencia e gera coffee beans (componentes do Java). Roast House é representado como um recurso CoffeeBeansResource. O URI "coffeebeans" identifica de forma unívoca o recurso CoffeeBeansResource (ver Lista 1).


  
//...
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
@ApplicationScoped
@Path("coffeebeans")
public class CoffeeBeansResource {
    
    @Context
    ResourceContext rc;
    
     Map<String, Bean> bc;

    @PostConstruct
    public void init() {
        this.bc = new ConcurrentHashMap<>();
    }

    @GET
    public Collection allBeans()
	{   
	return bc.values();  
	}    
	@GET 
    @Path("{id}") 
    public Bean bean(@PathParam("id") String id)
	{        
	return bc.get(id);  
	}     
	@POST   
	public Response add(Bean bean)
	{      
	if (bean != null) 
	{         
    bc.put(bean.getName(), bean);    
	}       
	final URI id = URI.create(bean.getName()); 
	return Response.created(id).build();    
	}    
	@DELETE 
    @Path("{id}")  
	public void remove(@PathParam("id") String id) 
	{     
    bc.remove(id); 
    }      
    @Path("/roaster/{id}") 
    public RoasterResource roaster(){  
	return this.rc.initResource(new RoasterResource());   
	} } Lista 1  

 
 

Como nas especificações prévias do JAX-RS, um recurso pode ser um componente EJB único (@Singleton) ou sem estado (@Stateless). Além disso, todos os provedores, subclasses de Application e recursos raiz podem ser implantados como beans gerenciados ou geridos por CDI (injeção de dependências e contextos). A injeção também está disponível em todas as extensões que incluem a anotação @Provider, simplificando a integração com o código existente. Os componentes específicos para o JAX-RS também podem ser injetados em sub-recursos mediante ResourceContext:


  
 @Context
    ResourceContext rc;

    @Path("/roaster/{id}")
    public RoasterResource roaster(){
        return this.rc.initResource(new RoasterResource());
    }
Lista
 
 

Lista 2 Como dado interessante, javax.ws.rs.container.ResourceContext não só permite injetar informação de JAX-RS em uma instância existente, mas também oferece acesso às classes de recursos com o método ResourceContext#getResource(Class resourceClass). Os pontos de injeção das instâncias passadas para o método ResourceContext#initResource são configurados com valores tomados do contexto pertinente durante a execução do JAX-RS. O campo String id da classe RoasterResource (mostrado na Lista 3) recebe o valor do parâmetro de caminho do recurso do ///elemento primário:


  
public class RoasterResource {

    @PathParam("id")
    private String id;

    @POST
    public void roast(@Suspended AsyncResponse ar, Bean bean) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
        }
        bean.setType(RoastType.DARK);
        bean.setName(id);
        bean.setBlend(bean.getBlend() + ": The dark side of the bean");
        Response response = Response.ok(bean).header("x-roast-id", id).build();
        ar.resume(response);
    }
}
Lista 3
 
 

O parâmetro javax.ws.rs.container.AsyncResponse é similar à classe javax.servlet.AsyncContext do Servlet 3.0 e permite executar uma solicitação assíncrona. No exemplo acima, a solicitação é suspensa durante o processamento a resposta é enviada ao cliente com a invocação do método AsyncResponse#resume. Mesmo assim, o método roast é executado de forma síncrona, de modo que a execução assíncrona não produz nenhum comportamento assíncrono. No entanto, a combinação da anotação @javax.ejb.Asynchronous do EJB e @Suspended AsyncResponse possibilita a execução assíncrona de lógica empresarial com uma notificação eventual para o cliente interessado. Qualquer recurso raiz do JAX-RS pode incluir as anotações @Stateless ou @Singleton e, de fato, funcionar como um EJB (ver Lista 4):


  
import javax.ejb.Asynchronous;
import javax.ejb.Singleton;

@Stateless
@Path("roaster")
public class RoasterResource {

    @POST
    @Asynchronous
    public void roast(@Suspended AsyncResponse ar, Bean bean) {
    //heavy lifting
        Response response = Response.ok(bean).build();
        ar.resume(response);
    }
}
Lista 4
 
 

Um método de recurso @Asynchronous com um parâmetro @Suspended AsyncResponse é executado de forma fire-and-forget (não requer ações posteriores). Embora o subprocesso que se ocupa das solicitações seja imediatamente liberado, AsyncResponse continua sendo um controlador conveniente para o cliente. Ao se completar uma operação muito demorada, o resultado pode ser convenientemente retornado para o cliente. Geralmente, é desejável separar o comportamento específico do JAX-RS da lógica empresarial concreta. Toda a lógica empresarial pode ser facilmente extraída e incorporada em um EJB dedicado com limites, mas a geração de eventos de CDI é ainda mais adequada para considerar os casos fire-and-forget. A classe de evento personalizada RoastRequest leva a carga útil (clase Bean) como entrada de processamento e AsyncResponse para a entrega resultante (ver Lista 5):


  
public class RoastRequest {

    private Bean bean;
    private AsyncResponse ar;

    public RoastRequest(Bean bean, AsyncResponse ar) {
        this.bean = bean;
        this.ar = ar;
    }

    public Bean getBean() {
        return bean;
    }

    public void sendMessage(String result) {
        Response response = Response.ok(result).build();
        ar.resume(response);
    }

    public void errorHappened(Exception ex) {
        ar.resume(ex);
    }
}
Lista 5
 
 

Os eventos de CDI não só separam a lógica empresarial da API do JAX-RS, mas também simplificam em grande medida o código JAX-RS (ver Lista 6):


  
public class RoasterResource {

    @Inject
    Event roastListeners;

    @POST
    public void roast(@Suspended AsyncResponse ar, Bean bean) {
        roastListeners.fire(new RoastRequest(bean, ar));
    }
}
Lista 6
 
 

Qualquer EJB ou bean gerido por CDI pode receber a solicitação RoastRequest mediante publicação e assinatura, e processar de forma síncrona ou assíncrona a carga útil com um método simples de observador: void onRoastRequest(@Observes RoastRequest request){}.

Com a classe AsyncResponse, a especificação do JAX-RS incorpora uma forma simples de envio de informações a HTTP em tempo real. Da perspectiva do cliente, a solicitação assíncrona no servidor continua gerando um bloqueio e, assim, vai se comportar de forma síncrona. Da perspectiva do design REST, todas as tarefas de execução prolongada devem retornar imediatamente o código de estado HTTP 202 junto com informações adicionais sobre como obter o resultado após o processamento ter sido completado.

Retorno de aspectos

As API de REST mais conhecidas com frequência requerem que seus clientes computem um código de identificação (fingerprint) da mensagem e enviem junto com a solicitação. No servidor, o código de identificação é computado e comparado com a informação anexa. Se não houver coincidência, a mensagem é rejeitada. Com JAX-RS e a incorporação de javax.ws.rs.ext.ReaderInterceptor javax.ws.rs.ext.WriterInterceptor, o tráfego pode ser interceptado no servidor e até no cliente. Uma implementação da interface ReaderInterceptor no servidor encapsula MessageBodyReader#readFrom e é executada antes de se efetuar a serialização.

PayloadVerifier recupera a assinatura do cabeçalho, computa o código de identificação do fluxo de dados e, depois, invoca o método ReaderInterceptorContext#proceed, que por sua vez invoca o seguinte interceptor na cadeia ou a instância MessageBodyReader (ver Lista 7).


  
public class PayloadVerifier implements ReaderInterceptor{

    public static final String SIGNATURE_HEADER = "x-signature";

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext ric) throws IOException, 
WebApplicationException {
        MultivaluedMap<String, String> headers = ric.getHeaders();
        String headerSignagure = headers.getFirst(SIGNATURE_HEADER);
        InputStream inputStream = ric.getInputStream();
        byte[] content = fetchBytes(inputStream);
        String payload = computeFingerprint(content);
        if (!payload.equals(headerSignagure)) {
            Response response = Response.status(Response.Status.BAD_REQUEST).header(
            SIGNATURE_HEADER, "Modified content").build();
            throw new WebApplicationException(response);
        }
        ByteArrayInputStream buffer = new ByteArrayInputStream(content);
        ric.setInputStream(buffer);
        return ric.proceed();
    }
    //...    
}
Lista 7
 
 

O conteúdo modificado resulta em códigos de identificação diferentes e ativa a exceção WebApplicationException com o código de resposta BAD_REQUEST (400).

Todos os cômputos associados a códigos de identificação ou a solicitações saintes podem ser facilmente automatizados com uma implementação de WriterInterceptor. Uma implementação de WriterInterceptor encapsula MessageBodyWriter#writeTo e é executada antes da serialização da entidade em um fluxo de dados. Para computar o código de identificação, é necessária a representação final da entidade ///"em linha", portanto ByteArrayOutputStream é passado como buffer, o método WriterInterceptorContext#proceed() é invocado, o conteúdo bruto é recuperado e o código de identificação computado. Ver Lista 8.


  
public class PayloadVerifier implements WriterInterceptor {
    public static final String SIGNATURE_HEADER = "x-signature";

   @Override
    public void aroundWriteTo(WriterInterceptorContext wic) throws IOException, 
WebApplicationException {
        OutputStream oos = wic.getOutputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        wic.setOutputStream(baos);
        wic.proceed();
        baos.flush();
        byte[] content = baos.toByteArray();
        MultivaluedMap<String, Object> headers = wic.getHeaders();
        headers.add(SIGNATURE_HEADER, computeFingerprint(content));
        oos.write(content);

    }
    //...
}
 
 

Finalmente, a assinatura computada é adicionada à solicitação como cabeçalho, o buffer é adicionado ao fluxo de dados original, e a solicitação completa é enviada para o cliente. É claro que uma única classe também pode implementar ambas as interfaces ao mesmo tempo.


  
import javax.ws.rs.ext.Provider;
@Provider
public class PayloadVerifier implements ReaderInterceptor, WriterInterceptor {
}
Lista 9
 
 

Como em edições anteriores do JAX-RS, as extensões customizadas serão automaticamente detectadas e registradas com a anotação @Provider. Para que as instâncias MessageBodyWriter e MessageBodyReader sejam interceptadas, somente as implementações de ReaderInterceptor e WriterInterceptor devem incluir a anotação @Provider; chamadas a interfaces API nem configurações adicionais são necessárias.

Interceptação de solicitações

Uma implementação de um ContainerRequestFilter e um ContainerResponseFilter intercepta a solicitação completa, não apenas o processo de leitura e escrita de entidades. A funcionalidade dos dois interceptores é muito mais útil que o registro da informação contida na instância javax.servlet.http.HttpServletRequest em bruto. A classe TrafficLogger não só pode registrar a informação contida em HttpServletRequest, como também pode acompanhar a informação associada aos recursos que se ajustam a uma solicitação particular, como mostrado na Lista 10.

@Provider  public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {        //ContainerRequestFilter      public void filter(ContainerRequestContext requestContext) throws IOException {          log(requestContext);      }      //ContainerResponseFilter      public void filter(ContainerRequestContext requestContext, ContainerResponseContext                                                    responseContext) throws IOException {          log(responseContext);      }        void log(ContainerRequestContext requestContext) {          SecurityContext securityContext = requestContext.getSecurityContext();          String authentication = securityContext.getAuthenticationScheme();          Principal userPrincipal = securityContext.getUserPrincipal();          UriInfo uriInfo = requestContext.getUriInfo();          String method = requestContext.getMethod();          List<Object> matchedResources = uriInfo.getMatchedResources();          //...      }        void log(ContainerResponseContext responseContext) {          MultivaluedMap<String, String> stringHeaders = responseContext.getStringHeaders();          Object entity = responseContext.getEntity();      //...      }  }  Lista 10    

  
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {

    //ContainerRequestFilter
    public void filter(ContainerRequestContext requestContext) throws IOException {
        log(requestContext);
    }
    //ContainerResponseFilter
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext 
                                                 responseContext) throws IOException {
        log(responseContext);
    }

    void log(ContainerRequestContext requestContext) {
        SecurityContext securityContext = requestContext.getSecurityContext();
        String authentication = securityContext.getAuthenticationScheme();
        Principal userPrincipal = securityContext.getUserPrincipal();
        UriInfo uriInfo = requestContext.getUriInfo();
        String method = requestContext.getMethod();
        List<Object> matchedResources = uriInfo.getMatchedResources();
        //...
    }

    void log(ContainerResponseContext responseContext) {
        MultivaluedMap<String, String> stringHeaders = responseContext.getStringHeaders();
        Object entity = responseContext.getEntity();
    //...
    }
}
Lista 10

 
 

Assim, uma implementação registrada de ContainerResponseFilter obtém uma instância do ContainerResponseContext e pode acessar os dados gerados pelo servidor. Os códigos de estado e o conteúdo de cabeçalhos, por exemplo, do cabeçalho Location podem ser facilmente acessados. ContainerRequestContext e ContainerResponseContext são classes mutáveis que podem ser modificadas pelos filtros.

Sem nenhuma configuração adicional, ContainerRequestFilter é executado depois da fase de estabelecimento de coincidências entre HTTP e os recursos. Neste ponto do processo, não é mais possível alterar a solicitação entrante para customizar a vinculação de recursos. Em caso de querer influenciar na vinculação entre a solicitação e um recurso, um filtro ContainerRequestFilter pode ser configurado para se executar antes da fase de vinculação de recursos. Os filtros ContainerRequestFilter que incluírem a anotação javax.ws.rs.container.PreMatching são executados antes da vinculação de recursos, fazendo com que os conteúdos da solicitação HTTP possam ser alterados em função do mapeamento desejado. Um caso frequente de uso de filtros @PreMatching é o ajuste dos verbos HTTP para superar limitações na infraestrutura de rede. É possível que métodos mais extravagantes, como PUT, OPTIONS, HEAD ou DELETE, sejam filtrados por firewalls; além disso, poderiam não ser suportados por alguns clientes HTTP. A implementação de @PreMatching ContainerRequestFilter poderia recuperar a informação do cabeçalho (por exemplo, "X-HTTP-Method-Override") com indicação do verbo HTTP desejado e substituir uma solicitação POST por uma PUT durante o processo (ver Lista 11).


  
@Provider
@PreMatching
public class HttpMethodOverrideEnabler implements ContainerRequestFilter {

    public void filter(ContainerRequestContext requestContext) throws IOException {
        String override = requestContext.getHeaders()
                .getFirst("X-HTTP-Method-Override");
        if (override != null) {
            requestContext.setMethod(override);
        }
    }
}
Lista 11
 
 

Configuração

Todos os interceptores e filtros registrados com a anotação @Provider estão globalmente habilitados para todos os recursos. Na fase de implementação, o servidor revisa as unidades de implementação procurando anotações @Provider e registra automaticamente todas as extensões antes da ativação da aplicação. Todas as extensções podem ser empacotadas em arquivos JAR dedicados e implementadas sob demanda com o arquivo WAR (na pasta WEB-INF/lib). Durante a execução do JAX-RS, os arquivos JAR serão revisados e as extensões serão automaticamente registradas. A implementação "improvisada" de arquivos JAR autocontidos pode parecer prática, mas exige empacotar as extensões com muita atenção. Todas as extensões contidas em um arquivo JAR seriam ativadas ao mesmo tempo.

JAX-RS incorpora o uso de anotações de vinculação para marcar recursos de maneira seletiva. A dinâmica é semelhante à dos qualificadores de CDI. As anotações customizadas indicadas com a meta-anotação javax.ws.rs.NameBinding podem ser usadas para declaração de pontos de interceptação:


  
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Tracked {
}
Lista 12

Todos os interceptores ou filtros indicados com a anotação Tracked podem ser ativados de forma seletiva aplicando a mesma anotação Tracked a classes, métodos ou até subclasses da aplicação:


 
 

Todos os interceptores ou filtros indicados com a anotação Tracked podem ser ativados de forma seletiva aplicando a mesma anotação Tracked a classes, métodos ou até subclasses da aplicação:


  
@Tracked
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {
}
Lista 13
 
 

As anotações NameBinding customizadas podem ser empacotadas junto com o interceptor ou filtro respectivo, e o desenvolvedor da aplicação pode aplicá-las a recursos de maneira seletiva. Embora esta abordagem baseada em anotações aumente significativamente a flexibilidade e permita pacotes de plug-in menos sofisticados, a vinculação continua sendo estática. A aplicação deve ser compilada e reimplantada de forma efetiva para mudar a cadeia de filtros ou interceptores.

Além da configuração global baseada em anotações das funcionalidades transversais, o JAX-RS 2.0 também apresenta uma nova API para registro dinâmico de extensões. O contêiner usa uma implementação da interface javax.ws.rs.container.DynamicFeature incluindo a anotação @Provider para possibilitar o registro dinâmico de interceptores e filtros, dispensando uma compilação. A extensão LoggerRegistration registra de forma condicional o interceptor PayloadVerifier e o filtro TrafficLogger consultando sobre a existência de propriedades de sistema por default, como mostrado na Lista 14:


  
@Provider
public class LoggerRegistration implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        String debug = System.getProperty("jax-rs.traffic");
        if (debug != null) {
            context.register(new TrafficLogger());
        }
        String verification = System.getProperty("jax-rs.verification");
        if (verification != null) {
            context.register(new PayloadVerifier());
        }
    }
}  
Lista 14
 
 

No cliente

A especificação do JAX-RS 1.1 não considerava o cliente. Embora certas implementações proprietárias de uma API REST de cliente, como RESTEasy ou Jersey, pudessem se comunicar com qualquer recurso HTTP (inclusive sem ser implementados com o Java EE), o código de cliente dependia diretamente da implementação particular. O JAX-RS 2.0 incorpora uma nova API de cliente padronizada. Usando uma inicialização padronizada, a Interface de provedor de serviço (SPI) suporta sua substituição. A API é uma interface fluida e similar à maioria das implementações REST proprietárias de cliente (ver Lista 15).


  
import java.util.Collection;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class CoffeeBeansResourceTest {

    Client client;
    WebTarget root;

    @Before
    public void initClient() {
        this.client = ClientBuilder.newClient().register(PayloadVerifier.class);
        this.root = this.client.target("http://localhost:8080/roast-house/api/coffeebeans");
    }

    @Test
    public void crud() {
        Bean origin = new Bean("arabica", RoastType.DARK, "mexico");
        final String mediaType = MediaType.APPLICATION_XML;
        final Entity<Bean> entity = Entity.entity(origin, mediaType);
        Response response = this.root.request().post(entity, Response.class);
        assertThat(response.getStatus(), is(201));

        Bean result = this.root.path(origin.getName()).request(mediaType).get(Bean.class);
        assertThat(result, is(origin));
        Collection<Bean> allBeans = this.root.request().get(
new GenericType<Collection>() {         });         assertThat(allBeans.size(), is(1));         assertThat(allBeans, hasItem(origin)); 
 
 

No teste de integração acima, a instância Client por default se obtém com o método ClientFactory.newClient() sem parâmetros. O processo de ///inicialização é padronizado com o padrão de tipo abstract factory (fábrica abstrata) javax.ws.rs.ext.RuntimeDelegate interno. Uma instância existente de RuntimeDelegate é injetada (por exemplo, mediante um marco de injeção de dependências) em ClientFactory, ou obtida procurando um indício nos arquivos META-INF/services/javax.ws.rs.ext.RuntimeDelegate e ${java.home}/lib/jaxrs.properties, e, finalmente, procurando a propriedade de sistema javax.ws.rs.ext.RuntimeDelegate. Se a detecção falhar, tenta-se inicializar com uma implementação por default (Jersey).

O objetivo principal de javax.ws.rs.client.Client é permitir o acesso fluente às instâncias javax.ws.rs.client.WebTarget ou javax.ws.rs.client.Invocation. Um WebTarget representa um recurso JAX-RS e uma Invocation é uma solicitação pronta para usar à espera da entrega. WebTarget também é um ///método fábrica de Invocation.

No método CoffeBeansResourceTest#crud(), o objeto Bean é passado uma e outra vez entre cliente e servidor. Com a escolha de MediaType.APPLICATION_XML, só são necessárias poucas anotações JAXB para enviar e receber um objeto DTO serializado em um documento XML:


  
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Bean {

    private String name;
    private RoastType type;
    private String blend;

}
Lista 16
 
 

Os nomes da classe e os atributos devem coincidir para uma serialização bem-sucedida com a representação do servidor, mas não é necessário que o objeto DTO tenha compatibilidade binária. No exemplo acima, ambas as classes Bean são contidas em pacotes diferentes e até implementam métodos diferentes. Um MediaType desejado é passado para o método WebTarget#request(), retornando uma instância de um Invocation.Builder síncrono. A invocação final de um método chamado da mesma forma que os verbos HTTP (GET, POST, PUT, DELETE, HEAD, OPTIONS ou TRACE) inicia uma solicitação síncrona.

A nova API de cliente também suporta a invocação assíncrona de recursos. Como já foi mencionado, uma instância Invocation separa a solicitação da entrega. Uma solicitação assíncrona pode ser iniciada com a invocação do método async() encadeado, retornando uma instância AsyncInvoker. Ver Lista 17.


  
@Test
    public void roasterFuture() throws Exception {
    //...
        Future<Response> future = this.root.path("roaster").path("roast-id").request().async().post(entity);
        Response response = future.get(5000, TimeUnit.SECONDS);
        Object result = response.getEntity();
        assertNotNull(result);
        assertThat(roasted.getBlend(),containsString("The dark side of the bean"));
    }
Lista 17
 
 

O estilo de comunicação "pseudo-assíncrona" do exemplo acima não traz muitos benefícios; o cliente ainda deve ser bloqueado e esperar até a resposta chegar. No entanto, a invocação baseada em Future é muito útil para o processamento por lotes: o cliente pode gerar diversas solicitações ao mesmo tempo, reunir as instâncias Future e processá-las depois.

Uma implementação verdadeiramente assíncrona pode ser realizada com um registro de callbacks (retro-chamadas), como mostrado na Lista 18.


  
  @Test
    public void roasterAsync() throws InterruptedException {
    //...
        final Entity<Bean> entity = Entity.entity(origin, mediaType);
        this.root.path("roaster").path("roast-id").request().async().post(
entity, new InvocationCallback<Bean>() {
            public void completed(Bean rspns) {
            }

            public void failed(Throwable thrwbl) {
            }
        });
    }
Lista 18
 
 

Para cada método que retorna um Future, também há um método de callback. Uma implementação da interface InvocationCallback é aceita como último parâmetro do método (post(), no exemplo acima) e há uma notificação assíncrona ao se produzir uma invocação bem-sucedida com a carga útil ou, em caso de erro, com uma exceção.

A geração automática de identificadores URI pode ser agilizada com o mecanismo integrado de criação de templates. Os marcadores de posição por default podem ser substituídos logo antes da execução da solicitação e poupar a criação repetitiva de instâncias WebTarget:


  
@Test
    public void templating() throws Exception {
        String rootPath = this.root.getUri().getPath();
        URI uri = this.root.path("{0}/{last}").
                resolveTemplate("0", "hello").
                resolveTemplate("last", "REST").
                getUri();
        assertThat(uri.getPath(), is(rootPath + "/hello/REST"));
    }
Lista 19
 
 

Um pequeno mas importante detalhe: no cliente, as extensões não são detectadas durante a inicialização; devem ser explicitamente registradas com a instância do cliente: ClientFactory.newClient().register(PayloadVerifier.class). No entanto, as mesmas implementações de interceptores de entidades podem ser compartilhadas entre cliente e servidor, simplificando os testes, reduzindo erros potenciais e aumentando a produtividade. O interceptor PayloadVerifier apresentado também pode ser reutilizado sem alterações no cliente.

Conclusão: Java EE ou não?

Um dado interessante é que o JAX-RS não precisa nem de um servidor de aplicações completo. Sendo satisfeitos os tipos de contextos especificados, qualquer API pode ser compatível com o JAX-RS 2.0. Porém, a combinação com EJB 3.2 traz as vantagens do processamento assíncrono, o agrupamento (e, por conseguinte, a regulação) e o monitoramento. A integração perfeita com Servlet 3+ é acompanhada do eficiente processamento assíncrono de respostas @Suspended mediante a compatibilidade com AsyncContext, e a execução de CDI possibilita a geração de eventos. Além disso, a validação de beans é bem integrada e pode ser usada para validação de parâmetros de recursos. O uso do JAX-RS 2.0 junto com outras API do Java EE 7 oferece a forma mais conveniente (não requer configuração) e mais produtiva (não requer nenhuma reinvenção) de expor objetos a sistemas remotos.

Informações adicionais

Adam Bien, consultor e autor, é membro do grupo de especialistas em Java EE 6/7, EJB 3.X, JAX-RS e JPA 2.X JSRs. Trabalhou com tecnologia Java desde JDK 1.0 e com servlets/EJB 1.0, e agora é designer e desenvolvedor para projetos de Java SE e Java EE. Editou diversos livros sobre JavaFX, J2EE e Java EE, e é autor de Real World Java EE Patterns—Rethinking Best Practices e de Real World Java EE Night Hacks (Ataques informáticos noturnos reais em Java EE, em inglês). Adam também ganhou os reconhecimentos Java Champion, Top Java Ambassador em 2012, e JavaOne Rock Star em 2009, 2011 e 2012. Ocasionalmente, Adam organiza workshops sobre Java (EE) no aeroporto de Munique.