Основы JAX-RS

    Введение


    Выросло данное API из JSR 311: JAX-RS: The Java API for RESTful Web Services и вошло в Java EE 6 (планировалось в Java EE 5). Как видно из названия, предназначено оно для разработки RESTful веб-сервисов.

    Основная цель данной статьи — познакомить читателя с основами JAX-RS API. Изначально я планировал написать о некоторых проблемах работы форм при использование JAX-RS сервиса. Не обнаружив на Хабре почти ничего, касающегося данной технологии понял, что введением к статье отделаться не удастся.

    Будут представлены основы JAX-RS API, реализация от JBoss и дано небольшое введение в клиентскую часть фреймворка Resteasy.


    Представлено оно набором классов в javax.ws.rs.*, в случае современных AppServer'ов предоставляемое окружением (можно использовать <scope>provided</scope> в maven. Для приложений в сервлет-контейнерах есть несколько реализаций:


    Эта технология позволяет экспортировать методы произвольного бина, используя специальный сервлет, предоставляемый реализаций JAX-RS. Приведу пример. Для использования в клиентской части выделим интерфейс:
    package com.example;
    
    import javax.ws.rs.*;
    
    @Path("/")
    public interface RestService {
      @GET
      @Path("echo")
      String echo(@QueryParam("q") String original);
    }


    И реализацию:
    package com.example;
    
    import javax.enterprise.ApplicationScoped;
    import javax.ws.rs.*;
    
    @ApplicationScoped
    @Path("/")
    public class Rest implements RestService{
      @GET
      @Path("echo")
      @Override
      public String echo(@QueryParam("q") String original) {
        return original;
      }
    }


    Как к самому бину, так к его методам добавляющтся аннотации, описывающие его поведение: @Path, например, указывает относительный или абсолютный путь к данному ресурсу; группа аннотаций @GET, @POST, @PUT, @DELETE отвечает за тип HTTP запроса релевантный этому методу.

    Сериализация


    Очевидно, что для передачи проивольных данных, необходима сериализация — перевод данных из объектного представления в набор байт. Дальнейшее повествование я буду вести с оглядкой на Resteasy.
    В этом случае для сериализации/десериализации используются специальные провайдеры. Аннотации @Produces и @Consumes используются для указания MIME-type содержимого результата/данных. Соответственно класс данных должен быть аннотирован JAXB. Стандартными провайдерами в JBoss AS 7 являются resteasy-jaxb-provider (xml-маршаллер/анмаршаллер) и resteasy-jettison-provider (json). Эти два провайдера позволяют интергрироваться с большим количество внешних сервисов, предоставлять XML и JSON API наружу.

    Нестандартные ответы


    Что делать, если мы хотим вернуть нестандартный ответ или HTTP код? При Exception'е из аннотированного JAX-RS метода результатом будет 500 ошибка. Для кастомизации достаточно возвращаемым типом указать javax.ws.rs.core.Response:
    @GET
    @Path("file/get/{name}")
    Response getFile(@PathParam("file") String fileName) {
      if(!Files.exists(Paths.get(fileName)) {
        return Response.status(422).entity("I'm a teapot");
      } else {
        Response.Builder response = Response.ok();
        response.header("X-Some-Server-Header", "value");
    
        response.entity(new StreamingOutput() {
          @Override
          public void write(OutputStream outputStream) throws IOException, WebApplicationException {
            Files.copy(Paths.get(fileName), outputStream);
          }
        });
    
        return response.build();
      }
    }


    Клиентский фреймворк


    Вторым приятным моментом JBoss Resteasy является наличие удобного клиентского фреймворка. Работа с ним возможна на разных уровнях абстракции: начиная от низкоуровневых ClientRequest, ClientResponse<T> и заканчивая генерацией прокси-объектов по аннотированному JAX-RS интерфейсу. Для примера, используя интерфейс RestService из первого примера:
    RestService service = ProxyFactory.create(RestService.class, "http://localhost:8080/example");
    log.info(service.echo("test message"));


    Но иногда этого недостаточно. Например в этом случае при ошибке на стороне сервера прокси генерирует исключение. Для более низкоуровневой работы может использоваться ClientRequest/ClientResponse<T>, использующие apache httpcomponents (в Resteasy 2.3.x.GA) или apache-httpclient (в 2.2.x.GA). Пример использования выглядит следующим образом:
    ClientRequest request = new ClientRequest(url);
    request.header("X-Additional-Header", "header value");
    
    // варьируется в зависимости от метода: GET, POST, PUT, DELETE
    ClientResponse<String> response = request.get(String.class);
    if(response.getCode() == 200) {
      String result = response.getEntity();
      log.info(result);
    }


    UPD: В случае использования клиенткого фреймворка необходимо один раз при запуске программы его инициализировать следующим образом:
    RegisterBuiltin.register(ResteasyProviderFactory.getInstance());


    Такой подход позволяет установить произвольные поля, произвольно тело запроса и т. п.
    Одной возникающей в этом случае проблеме будет посвящена следующая статья.

    UPD: Продолжение см. тут
    • +29
    • 71,6k
    • 8
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 8
    • +1
      Если бы не два часа ночи — прямо сел бы и попробовал сделать. А так в избранное, явно пригодиться в будущем. Будем ждать описание граблей, на которые нарвались.
      • 0
        У вас и интерфейс и реализация его проаннотированы. Это специально так сделано? Без этого бин не создатся?

        Аннотации @Produces и @Consumes, в которых указывается MIME-type содержимого результата/данных.

        По-моему, это предложение неполное.
      • +3
        Проверил, что это необязательно в Resteasy. Написал так, потому что у аннотаций типа @GET, @Path нет мета-аннотации @Inherited, т. е. по логике они не наследуются. Как к этому отнесутся другие реализации — не знаю. Resteasy ищет и в интерфейсе тоже.

        Бин создается при инжекции его в другой бин или при обращении к нему через сервлет Resteasy. Наличие JAX-RS аннотаций не является необходимым. Например, могут присутствовать аннотации JAX-WS.

        Как правило в реальных задачах этой проблемы не возникает: аннотируется класс-реализация, интерфейс для нее не экстрагируется, но создается независимый.

        В методах этого класса и методах интерфейса для прокси могут быть разные сигнатуры: метод реализации возвращает javax.ws.rs.core.Response, а интерфейса InputStream или String
        • +3
          Есть пару замечательных презентаций по JAX-RS:

          www.slideshare.net/bjarlestam/introduction-to-jaxrs
          matthewturland.com/slides/jersey/
          • 0
            … и вошло в Java SE 5

            JAX-RS не является частью Java SE.
            Оно является частью Java EE 6.
            • +1
              Спасибо за уточнение, исправил.

              В SE вошло JAXP (включая StAX), вот и ошибся =)
            • 0
              Как то написали сервисочек на JBoss RestEasy,
              потом оказалось, что надо быстро закрыть наш рест oauth'ом.
              Вот тут то мы и огребли, ребята просто предоставляют интерфейсы, а всю ответственность по разработке перекладывают на сочувствующих.
              В контексте oauth — это оказалось корманным адом, в итоге воспользовались spring-oauth.

              А так да, очень быстро и удобно.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.