Статьи

Что нового в JAX-RS 2.0

JAX-RS — это фреймворк, разработанный для того, чтобы помочь вам писать приложения RESTful как на стороне клиента, так и на стороне сервера. С выпуском Java EE 7 планируется в следующем году, 2013, JAX-RS является одной из спецификаций, которая подвергается глубокому пересмотру. JAX-RS 2.0 в настоящее время находится на этапе публичного черновика в JCP, поэтому сейчас самое время обсудить некоторые ключевые новые функции, чтобы вы могли начать играть с вашей любимой реализацией JAX-RS и дать ценную обратную связь экспертной группе. необходимо завершить спецификацию.

Ключевые особенности в 2.0:

  • Клиентский API
  • Асинхронный HTTP на стороне сервера
  • Фильтры и перехватчики

Эта статья дает краткий обзор каждой из этих функций.

Клиентская структура

Одна огромная вещь, отсутствующая в JAX-RS 1.0, была клиентским API. В то время как было легко написать переносимый сервис JAX-RS, каждая реализация JAX-RS определяла собственный проприетарный API. JAX-RS 2.0 заполняет этот пробел беглым , низкоуровневым API для создания запросов. Вот простой пример:

Client client = ClientFactory.newClient(); 
WebTarget target = client.target("http://example.com/shop"); 
Form form = new Form().param("customer", "Bill")
                      .param("product", "IPhone 5") 
                      .param("CC", "4444 4444 4444 4444"); 
Response response = target.request().post(Entity.form(form)); 
assert response.getStatus() == 200; 
Order order = response.readEntity(Order.class); 

Давайте разберем этот пример кода. Клиентский интерфейс управляет и настраивает HTTP-соединения. Это также фабрика для веб-целей. WebTargets представляют определенный URI. Вы строите и выполняете запросы от экземпляра WebTarget. Response — это тот же класс, определенный в JAX-RS 1.0, но он был расширен для поддержки клиентской стороны.

Пример клиента, выделяет экземпляр клиента, создает WebTarget, а затем отправляет данные формы в URI, представленный WebTarget. Объект Response проверяется на наличие статуса 200, а затем класс приложения Order извлекается из ответа с использованием метода readEntity ().

Интерфейсы обработчика содержимого MessageBodyReader и MessageBodyWriter, определенные в JAX-RS 1.0, повторно используются на стороне клиента. Когда метод readEntity () вызывается в примере кода, MessageBodyReader сопоставляется с Content-Type ответа и типом Java (Order), передаваемым в качестве параметра методу readEntity ().

Если вы надеетесь, что ваш сервис вернет успешный ответ, есть несколько хороших вспомогательных методов, которые позволяют вам получить объект Java напрямую, без необходимости взаимодействовать и писать дополнительный код вокруг объекта Response.

Customer cust = client.target("http://example.com/customers")
                      .queryParam("name", "Bill Burke")
                      .request().get(Customer.class); 

В этом примере мы нацеливаемся на URI и указываем дополнительный параметр запроса, который мы хотим добавить к URI запроса. Метод get () имеет дополнительный параметр типа Java, на который мы хотим демонтировать HTTP-ответ. Если код ответа HTTP отличается от 200, хорошо , JAX-RS выбирает исключение, которое отображается на код ошибки из определенной иерархии исключений в клиентском API JAX-RS.

API асинхронного клиента

Клиентская среда JAX-RS 2.0 также поддерживает асинхронный API и API обратного вызова. Это позволяет вам выполнять HTTP-запросы в фоновом режиме и либо запрашивать ответ, либо получать обратный вызов по окончании запроса.

Future<Customer>
future = client.target("http://e.com/customers")
               .queryParam("name", "Bill Burke")
               .request()
               .async()
               .get(Customer.class);

try {
   Customer cust = future.get(1, TimeUnit.MINUTES);
} catch (TimeoutException ex) {
  System.err.println("timeout");
}

Интерфейс Future — это интерфейс JDK, который существует с JDK 5.0. Приведенный выше код выполняет HTTP-запрос в фоновом режиме, затем блокируется на одну минуту, пока он ожидает ответа. Вы также можете использовать Future для опроса, чтобы увидеть, завершен ли запрос или нет.

Вот пример использования интерфейса обратного вызова.

InvocationCallback<Response>
callback = new InvocationCallback {

   public void completed(Response res) {
      System.out.println("Request success!");
   }

   public void failed(ClientException e) {
      System.out.println("Request failed!");n
   }
};

client.target("http://example.com/customers")
      .queryParam("name", "Bill Burke")
      .request()
      .async()
      .get(callback);

В этом примере мы создаем реализацию интерфейса InvocationCallback. Мы вызываем запрос GET в фоновом режиме и регистрируем этот экземпляр обратного вызова в запросе. Интерфейс обратного вызова выведет сообщение о том, успешно ли выполнен запрос.

Это основные функции клиентского API. Я предлагаю просмотреть спецификацию и Javadoc, чтобы узнать больше.

Асинхронный HTTP на стороне сервера

On the server-side, JAX-RS 2.0 provides support for asynchronous HTTP. Asynchronous HTTP is generally used to implement long-polling interfaces or server-side push. JAX-RS 2.0 support for Asynchronous HTTP is annotation driven and is very analogous with how the Servlet 3.0 specification handles asynchronous HTTP support through the AsyncContext interface. Here’s an example of writing a crude chat program.

@Path("/listener")
public class ChatListener
{

   List<AsyncResponse> listeners = ...some global list...;


   @GET
   public void listen(@Suspended AsyncResponse res) {
      list.add(res);
   }

}

For those of you who have used the Servlet 3.0 asynchronous interfaces, the above code may look familiar to you. An AsyncResponse is injected into the JAX-RS resource method via the @Suspended annotation. This act disassociates the calling thread to the HTTP socket connection. The example code takes the AsyncResponse instance and adds it to a application-defined global List object. When the JAX-RS method returns, the JAX-RS runtime will do no response processing. A different thread will handle response processing.

@Path("/speaker")
public class ChatSpeaker
{
   List<AsyncResponse> listeners = ...some global list...;
   @POST
   @Consumes("text/plain")
   public void speak(String speech) {
      for (AsyncResponse res : listeners) {
         res.resume(Response.ok(speech, "text/plain").build());n
      }
   }
}

When a client posts text to this ChatSpeaker interface, the speak() method loops through the list of registered AsyncResponses and sends back an 200, OK response with the posted text.

Those are the main features of the asynchronous HTTP interface, check out the Javadocs for a deeper detail.

Filters and Entity Interceptors

JAX-RS 2.0 has an interceptor API that allows framework developers to intercept request and response processing. This powerful API allows framework developers to transparently add orthogonal concerns like authentication, caching, and encoding without polluting application code. Prior to JAX-RS 2.0 many JAX-RS providers like Resteasy, Jersey, and Apache CXF wrote their own proprietary interceptor frameworks to deliver various features in their implementations. So, while JAX-RS 2.0 filters and interceptors can be a bit complex to understand please note that it is very use-case driven based on real-world examples. I wrote a blog on JAX-RS interceptor requirements awhile back to help guide the JAX-RS 2.0 JSR Expert Group on defining such an API. The blog is a bit dated, but hopefully you can get the gist of why we did what we did.

JAX-RS 2.0 has two different concepts for interceptions: Filters and Entity Interceptors. Filters are mainly used to modify or process incoming and outgoing request headers or response headers. They execute before and after request and response processing. Entity Interceptors are concerned with marshaling and unmarshalling of HTTP message bodies. They wrap around the execution of MessageBodyReader and MessageBodyWriter instances.

Server Side Filters

On the server-side you have two different types of filters. ContainerRequestFilters run before your JAX-RS resource method is invoked. ContainerResponseFilters run after your JAX-RS resource method is invoked. As an added caveat, ContainerRequestFilters come in two flavors: pre-match and post-matching. Pre-matching ContainerRequestFilters are designated with the @PreMatching annotation and will execute before the JAX-RS resource method is matched with the incoming HTTP request. Pre-matching filters often are used to modify request attributes to change how it matches to a specific resource. For example, some firewalls do not allow PUT and/or DELETE invocations. To circumvent this limitation many applications tunnel the HTTP method through the HTTP header X-Http-Method-Override. A pre-matching ContainerRequestFilter could implement this behavior.

@Provider
public class HttpOverride implements ContainerRequestFilter {

   public void filter(ContainerRequestContext ctx) {
      String method = ctx.getHeaderString("X-Http-Method-Override");
      if (method != null) ctx.setMethod(method);
   }
}

Post matching ContainerRequestFilters execute after the Java resource method has been matched. These filters can implement a range of features for example, annotation driven security protocols.

After the resource class method is executed, JAX-RS will run all ContainerResponseFilters. These filters allow you to modify the outgoing response before it is marshalled and sent to the client. One example here is a filter that automatically sets a Cache-Control header.

@Provider
public class CacheControlFilter implements ContainerResponseFilter {
   public void filter(ContainerRequestContext req, ContainerResponseContext res) {
      if (req.getMethod().equals("GET")) {
         req.getHeaders().add("Cache-Control", cacheControl);
      }
   }
}

Client Side Filters

On the client side you also have two types of filters: ClientRequestFilter and ClientResponseFilter. ClientRequestFilters run before your HTTP request is sent over the wire to the server. ClientResponseFilters run after a response is received from the server, but before the response body is unmarshalled.

A good example of client request and response filters working together is a client-side cache that supports conditional GETs. The ClientRequestFilter would be responsible for setting the If-None-Match or If-Modified-Since headers if the requested URI is already cached. Here’s what that code might look like.

@Provider
public class ConditionalGetFilter implements ClientRequestFilter {

   public void filter(ClientRequestContext req) {
      if (req.getMethod().equals("GET")) {
         CacheEntry entry = cache.getEntry(req.getURI());
         if (entry != null) {
            req.getHeaders().putSngle("If-Modified-Since", entry.getLastModified());
         }
      }
   }
}

The ClientResponseFilter would be responsible for either buffering and caching the response, or, if a 302, Not Modified response was sent back, to edit the Response object to change its status to 200, set the appropriate headers and buffer to the currently cached entry. This code would be a bit more complicated, so for brevity, we’re not going to illustrate it within this article.

Reader and Writer Interceptors

While filters modify request or response headers, interceptors deal with message bodies. Interceptors are executed in the same call stack as their corresponding reader or writer. ReaderInterceptors wrap around the execution of MessageBodyReaders. WriterInterceptors wrap around the execution of MessageBodyWriters. They can be used to implement a specific content-encoding. They can be used to generate digital signatures or to post or pre-process a Java object model before or after it is marshalled. Here’s an example of a GZIP encoding WriterInterceptor.

@Provider
public class
GZIPEndoer implements WriterInterceptor {

   public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException, WebApplicationException {
      GZIPOutputStream os = new GZIPOutputStream(ctx.getOutputStream());
      try {
         ctx.setOutputStream(os);
         return ctx.proceed();
      } finally {
         os.finish();
      }
   }
}

Resource Method Filters and Interceptors

Sometimes you want a filter or interceptor to only run for a specific resource method. You can do this in two different ways: register an implementation of DynamicFeature or use the @NameBinding annotation. The DynamicFeature interface is executed at deployment time for each resource method. You just use the Configurable interface to register the filters and interceptors you want for the specific resource method.

@Provider
public class ServerCachingFeature implements DynamicFeature {

   public void configure(ResourceInfo resourceInfo, Configurable configurable) {
      if (resourceInfo.getMethod().isAnnotationPresent(GET.class)) {
         configurable.register(ServerCacheFilter.class);
      }
   }
}

On the other hande, @NameBinding works a lot like CDI interceptors. You annotate a custom annotation with @NameBinding and then apply that custom annotation to your filter and resource method

@NameBinding
public @interface DoIt {}


@DoIt
public class MyFilter implements ContainerRequestFilter {...}


@Path
public class MyResource {
   @GET
   @DoIt
   public String get() {...}

Wrapping Up

Well, those are the main features of JAX-RS 2.0. There’s also a bunch of minor features here and there, but youll have to explore them yourselves. If you want to testdrive JAX-RS 2.0 (and hopefully also give feedback to the expert group), Red Hat’s Resteasy 3.0 and Oracle’s Jersey project have implementations you can download and use.

Useful Links

Below are some useful links. I’ve also included links to some features in Resteasy that make use of filters and interceptors. This code might give you a more in-depth look into what you can do with this new JAX-RS 2.0 feature.