колледже Доусон в Монреале, Канада. Он также является программным консультантом и по совместительству преподавателем в Школе расширенного обучения при Институте вычислительной техники Университета Конкордия . Он ведет блог на omniprogrammer.com и пишет в Твиттере @omniprof .
Есть много хороших учебных пособий по созданию веб-сервисов REST с NetBeans, которые откроет веб. Есть две проблемы с учебными пособиями. Во-первых, они не объясняют, для чего предназначены различные варианты или почему вы выбираете один из них. Во-вторых, они не решают конкретную проблему получения коллекции из службы или, если они это делают, то нет никаких объяснений. В этой статье мы рассмотрим эти проблемы.
POJO против сессионного компонента без гражданства
Когда мои студенты проходят курс веб-служб, они уже прошли курс по программированию на стороне сервера с использованием JSF, JPA и CDI с использованием NetBeans в качестве IDE, GlassFish в качестве сервера и MySQL в качестве СУБД. Когда мы используем NetBeans для создания веб-службы, мы видим следующие варианты:
Если вы выберете Web Services из классов Entity, он сгенерирует компонент Sessionless без состояния в качестве фасада абстрактного класса. Абстрактный класс является реализацией стандартного контроллера JPA, который не совместим с CDI без некоторого переписывания. Моих учеников учат создавать контроллер JPA, который содержит конкретные методы, необходимые приложению, не более или не менее.
Затем этот контроллер можно проверить с помощью Arquillian, чтобы убедиться в его правильной работе. Поэтому мои ученики используют мастера после написания контроллера.
Третий вариант почти такой же, как и первый, за исключением того, что теперь он генерирует классы сущностей, а также абстрактный контроллер и компонент сеанса без сохранения состояния. В этих волшебниках нет ничего плохого. Они просто не работают так, как я хочу, чтобы мои ученики работали.
Второй выбор, веб-сервисы из шаблонов, — это выбор, который делают мои ученики. Можно выбрать три типа шаблонов, и мои ученики всегда выбирают простой корневой ресурс.
Затем мастер позволяет выбрать пакет, в котором будет создан класс службы, путь к службе на сервере, имя класса, тип MIME и класс представления.
Все это может быть легко изменено в сгенерированном коде. Для программы, которая доставляет записи из базы данных аквариумных рыб, для которых сущности и классы контроллеров уже закодированы, мой выбор:
Вот файл, который он генерирует:
package com.kenfogel.rest3; import com.kenfogel.entities.Fish; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import javax.ws.rs.PathParam; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PUT; /** * REST Web Service * * @author Ken */ @Path("fishies") public class FishiesRS { @Context private UriInfo context; /** * Creates a new instance of FishiesRS */ public FishiesRS() { } /** * Retrieves representation of an instance of com.kenfogel.rest3.FishiesRS * @return an instance of com.kenfogel.entities.Fish */ @GET @Produces("application/xml") public Fish getXml() { //TODO return proper representation object throw new UnsupportedOperationException(); } /** * PUT method for updating or creating an instance of FishiesRS * @param content representation for the resource * @return an HTTP response with content of the updated or created resource. */ @PUT @Consumes("application/xml") public void putXml(Fish content) { } }
Теперь мои ученики могут реализовывать любые методы GET, POST, PUT или DELETE, которые соответствуют их контроллеру JPA или другим частям их службы, которые они хотят представить. Это POJO и может работать в самых простых контейнерах.
Вот как выглядит мой последний класс обслуживания после того, как я отредактировал его, чтобы получить либо всю
рыбу, либо только одну рыбу. Служба использует CDI для доступа к классу контроллера JPA.
package com.kenfogel.rest; import com.kenfogel.beans.FishJpaController; import com.kenfogel.entities.Fish; import java.util.List; import javax.inject.Inject; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import javax.ws.rs.PathParam; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PUT; /** * REST Web Service * * @author Ken */ @Path("fishies") public class FishRest { @Inject private FishJpaController fishjpa; @Context private UriInfo context; /** * Creates a new instance of FishRest */ public FishRest() { } /** * Retrieves all the fish * * @return an instance of com.kenfogel.entities.Fish */ @GET @Produces("application/xml") public List getAllFishXml() { return fishjpa.findFishEntities(); } /** * Retrieves a specific fish * * @param id * @return an instance of com.kenfogel.entities.Fish */ @GET @Produces("application/xml") @Path("{id}/") public Fish getOneFishXml(@PathParam("id") int id) { return fishjpa.findFish(id); } /** * PUT method for updating or creating an instance of FishRest * * @param content representation for the resource */ @PUT @Consumes("application/xml") public void putXml(Fish content) throws Exception { fishjpa.create(content); } }
Когда мастер создает службу REST, он также создает второй класс с именем ApplicationConfig.java. Вы не хотите ничего менять в этом классе, кроме случаев, когда вы хотите изменить путь к ресурсу. Вот где это заявлено.
@javax.ws.rs.ApplicationPath("webresources") public class ApplicationConfig extends Application {
Значением аннотации ApplicationPath является имя пути к ресурсу, поэтому вы можете легко изменить его здесь.
@javax.ws.rs.ApplicationPath("awesomewebservice")
Выбор бина сеанса без гражданства
Сессионные компоненты без сохранения состояния объединяются сервером Java Enterprise. Если вы хотите, чтобы контейнер объединял сервисные объекты, вы можете изменить POJO на сессионные компоненты без сохранения состояния с помощью всего нескольких аннотаций.
REST Client
Когда услуга завершена, пришло время создать клиента. Мастер прост, и есть только один диалог выбора. Выбрав службу REST из вашего проекта, NetBeans создаст клиента с методами для всего, что находится в классе обслуживания сервера.
Вот код, сгенерированный мастером:
package com.kenfogel.fishfxtable.restclient2; import javax.ws.rs.ClientErrorException; import javax.ws.rs.client.Client; import javax.ws.rs.client.WebTarget; /** * Jersey REST client generated for REST resource:FishRest [fishies]<br> * USAGE: * <pre> * FishRSClient2 client = new FishRSClient2(); * Object response = client.XXX(...); * // do whatever with response * client.close(); * </pre> * * @author Ken */ public class FishRSClient2 { private WebTarget webTarget; private Client client; private static final String BASE_URI = "http://localhost:8080/RestServerS15/webresources"; public FishRSClient2() { client = javax.ws.rs.client.ClientBuilder.newClient(); webTarget = client.target(BASE_URI).path("fishies"); } public <T> T getOneFishXml(Class<T> responseType, String id) throws ClientErrorException { WebTarget resource = webTarget; resource = resource.path(java.text.MessageFormat. format("{0}", new Object[]{id})); return resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML). get(responseType); } public <T> T getAllFishXml(Class<T> responseType) throws ClientErrorException { WebTarget resource = webTarget; return resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML). get(responseType); } public void putXml(Object requestEntity) throws ClientErrorException { webTarget.request(javax.ws.rs.core.MediaType.APPLICATION_XML). put(javax.ws.rs.client.Entity. entity(requestEntity, javax.ws.rs.core.MediaType.APPLICATION_XML)); } public void close() { client.close(); } }
Чтобы получить одну рыбку на основе поля первичного ключа, я теперь могу просто добавить следующий метод в мою клиентскую программу:
/** * Just for fun we show a single fish */ public void showAFish() { FishRSClient fishRSClient = new FishRSClient(); Fish aFish = fishRSClient.getFishByIdXml(Fish.class, "1"); fishRSClient.close(); System.out.println(aFish); }
Как говорится в комментарии в сгенерированном классе FishRSClient, создайте клиентский объект REST, вызовите соответствующий метод и затем закройте клиент. Посмотрите на метод, который я вызываю, и сравните его с его сигнатурой:
Fish aFish = fishRSClient.getFishByIdXml(Fish.class, "1"); public <T> T getOneFishXml(Class<T> responseType, String id) throws ClientErrorException {
Обратите внимание, что вы должны передать в качестве параметра класс Class в качестве responseType. Это позволяет универсально закодированному getOneFishXML возвращать правильный класс. Дела идут с рельсов, когда я хочу список рыбы. Сигнатура метода:
public <T> T getAllFishXml(Class<T> responseType) throws ClientErrorException {
Поэтому я пишу по шаблону первого примера:
List<Fish> list = fishRSClient.findAllXml(List<Fish>.class);
Это не сработает. Проблема в том, что List.class недействителен. Там нет такого синтаксиса. Как тогда вы указываете, что типом ответа является любой тип класса коллекции? Решением является использование класса GenericType. Здесь учебники либо просто не показывают коллекцию, возвращаемую из службы, либо показывают ее, но не объясняют, что происходит.
GenericType происходит от javax.ws.rs.core. Он существует для решения этой конкретной проблемы, когда сам тип ответа содержит общий компонент, такой как List. Чтобы использовать его, вы должны изменить responseType в сервисном клиенте.
public <T> T getAllFishXml(GenericType<T> responseType) throws ClientErrorException { WebTarget resource = webTarget; return resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML). get(responseType); }
Теперь нам нужно изменить способ вызова этого метода. Мы должны объявить объект типа GenericType и в то же время мы должны определить класс GenericType. Это немного странно, но это говорит о том, что GenericType является решением этой проблемы. Если вы знаете лучше, чем, пожалуйста, оставьте комментарий. Вот метод работы:
public void displayTheTable() throws SQLException { FishRSClient fishRSClient = new FishRSClient(); // genericType is used when the return is a list GenericType<List<Fish>> gType = new GenericType<List<Fish>>() {}; List<Fish> list = fishRSClient.findAllXml(gType); fishRSClient.close(); fishDataTable.setItems(FXCollections.observableArrayList(list)); }
На строке, которая объявляет объект GenericType с именем gType, вы увидите, что с левой стороны есть открывающая и закрывающая скобки. Это обязательно.
Если вам интересно, это вывод приложения JavaFX, которое использует сервис:
Отказ от мастера на стороне клиента
Если вы хотите закодировать свои запросы к службе напрямую без класса, сгенерированного мастером, прочитайте отличный пост Адама Бина по адресу:
http://www.adam-bien.com/roller/abien/entry/the_executable_feel_of_jax .
Финальный кредит
Когда я побежал к этой проблеме, я искал долго и трудно , и здесь был вопрос переполнения стека и ответ , который показал мне решение:
http://stackoverflow.com/questions/15275431/rest-listentity-return-causing-errors