Добрый вечер всем!
Не так давно (в феврале этого года), я решил заняться программированием. В качестве языка был выбран Java. И с того момента я упорно изучаю все возможности данного языка. Недавно наткнулся на статью про RESTful на хабре. Прочитал и понял, что надо осветить альтернативный способ создания данных сервисов. Ещё меня поразило, насколько некоторые статьи пишутся непонятно для новичков. Я решил написать статью в которой расскажу и о прикладной части создания веб сервиса.
Я не претендую на истину. Я просто хочу показать простой и быстрый способ создать веб сервис.
Самое главное это IDE. Я предпочитаю Eclipse. Данный проект я написал в Eclipse for Java EE Developers Juno. Четно говоря, предыдущий релиз был стабильнее, но этот внешне приятнее. В качестве фреймворка для REST я выбрал Jersey. Его легко найти и скачать. Точно так же как и саму IDE. В качестве сервера я установил GlassFish плагин для эклипса. Инструкцию по его установке точно так же легко найти. Ну на этом и всё.
Итак. У нас есть Eclipse, папка с JAR-файлами Jersey и установленный GlassFish плагин. Теперь запускаем Eclipse и создаем пустой Dynamic Web Project, на последней вкладке не забываем отметить чек-бокс, который отвечает за генерацию web.xml файла.
Eclipse создаст нам пустой проект, который можно уже попробовать запустить на нашем сервере (Run As -> Run On Server).
После запуска появится встроенный браузер и покажет страничку Hello World. Теперь на надо скопировать JAR-файлы Jersey в папку [имя проекта]/ WebContent / WEB-INF / lib. Таким образом мы подключим все нужные библиотеки. Теперь очередь за web.xml файлом. Он находится в [имя проекта]/ WebContent / WEB-INF.
Вот листинг этого файла
Параметр com.sun.jersey.config.property.packages должен указывать у нас тот пакет в котором будут храниться наши файлы ресуры. А в тэге url-pattern хранится путь к нашему веб-сервису. Думаю не стоит рассказывать, что это основополагающий момент RESTful веб сервисов, ведь его сама идеология говорит о том, что каждый ресурс должен иметь свой адрес. Теперь наш веб сервис доступен по адресу localhost:8080/[имя нашего проекта]/rest/. Это будет являться нашим базовым URI.
В качестве примера я хотел привести простой веб-севрис, который представляет из себя такой мини дневничок. Т.е. у нас есть один тип сущностей Message и с ним мы и будем работать.
Вот класс Message
Единственное что тут может вызывать вопрос это аннотация @XmlRootElement. Она нам нужна чтобы Java сама преобразовывала данный класс в XML формат для передачи через HTTP. В остальном это обычный класс с тремя полями. Id сообщения, Title сообщения и Text сообщения.
В качестве контент-провайдера я решил использовать обычный List. На данном этапе мне не хотелось бы углубляться в ORM и хранение данных в таблицах БД, потому что эта тема заслуживает отельной статьи, которая возможно будет следующей. И так я создал служебный класс который будет хранить в себе List наших сообщений и предоставлять методы для работы с этим листом.
Вот листинг класса для работы с данными
Тут мы просто создаем List и заполняем его несколькими записями. Далее метод getData() возвращает нам ссылку на сам List. Что делают остальные методы легко догадаться по их названиям.
Сам сервис это ресурс-класс с аннотациями в котором указывается по какому URI какой ресурс или какое действие будет происходить в зависимости от типа запроса. Основные аннотации это
Path(«путь к данному ресурсу») данная аннотация указывает на конкретный адрес класса или метода.
так же есть разновидность Path("{id}") которая нам говорит о том что id это некая переменная которую мы сможем передать в наш метод.
Consumes (MediaType) и Produces(MediaType) будут говорить нам о получаемых и отправляемых данных соответственно. В нашем случае я выбрал APPLICATION_XML. Не спраши��айте почему не JSON, просто по мне так проще. Не зря же придумали JAX-Bind.
GET — говорит что этот метод будет отправлять данные от сервиса клиенту.
PUT/POST — говорит что этот метод будет добавлять/обновлять данные в наш сервис.
Лично я использую PUT для добавления, а POST для обновления.
DELETE — говорит о том что этот метод будет удалять данные из нашего хранилища
Вот листинг класса ресурса
package ru.example.rest.resource;
Думаю стоит прояснить несколько моментов. Наш класс-ресурс будет доступен по адресу localhost:8080/[имя проекта]/rest/message при запросе типа GET нам будет возвращаться список всех записей. при запросе POST с параметром Message у нас будет обновляться элемент Message. При запросе типа PUT c параметром Message у нас будет добавляться новый message в наше хранилище данных. При обращении к адресу методом типа GET localhost:8080/[имя проекта]/rest/message/id нам будет возвращаться message имеющий номер равный id. А если также обратиться методом DELETE то будет удален message с номером id.
Для тестирования данного сервиса я написал небольшой Java клиент. Обычное java приложение которое демонстрирует обращение по всем методам и выводит результаты на консоль.
Вот его листинг
Самые интересные строчки тут это
genericType это грубо говоря тип List, он нужен чтобы передавать и получать более сложные структуры используя JAX-B. И все данные мы передаем как JAXBElement.
В данный клиент необходимо включить так же библиотеки Jersey и включить файл описания класса Message.
В заключении хотелось бы сказать, что я буду очень рад если эта статья кому то поможет. Так же я готов выслушать любые предложения и любую критику. Я ведь только учусь.
Код клиента и код сервиса доступны на моем гитхабе ЗДЕСЬ.
Данные которыми я пользовался это статья Vogella и интернет.
Не так давно (в феврале этого года), я решил заняться программированием. В качестве языка был выбран Java. И с того момента я упорно изучаю все возможности данного языка. Недавно наткнулся на статью про RESTful на хабре. Прочитал и понял, что надо осветить альтернативный способ создания данных сервисов. Ещё меня поразило, насколько некоторые статьи пишутся непонятно для новичков. Я решил написать статью в которой расскажу и о прикладной части создания веб сервиса.
Я не претендую на истину. Я просто хочу показать простой и быстрый способ создать веб сервис.
Начало
Необходимое ПО
Самое главное это IDE. Я предпочитаю Eclipse. Данный проект я написал в Eclipse for Java EE Developers Juno. Четно говоря, предыдущий релиз был стабильнее, но этот внешне приятнее. В качестве фреймворка для REST я выбрал Jersey. Его легко найти и скачать. Точно так же как и саму IDE. В качестве сервера я установил GlassFish плагин для эклипса. Инструкцию по его установке точно так же легко найти. Ну на этом и всё.
Создание проекта
Итак. У нас есть Eclipse, папка с JAR-файлами Jersey и установленный GlassFish плагин. Теперь запускаем Eclipse и создаем пустой Dynamic Web Project, на последней вкладке не забываем отметить чек-бокс, который отвечает за генерацию web.xml файла.
Eclipse создаст нам пустой проект, который можно уже попробовать запустить на нашем сервере (Run As -> Run On Server).
После запуска появится встроенный браузер и покажет страничку Hello World. Теперь на надо скопировать JAR-файлы Jersey в папку [имя проекта]/ WebContent / WEB-INF / lib. Таким образом мы подключим все нужные библиотеки. Теперь очередь за web.xml файлом. Он находится в [имя проекта]/ WebContent / WEB-INF.
Вот листинг этого файла
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>WebRest</display-name> <servlet> <servlet-name>Jersey REST Service</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>ru.example.rest.resource</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Jersey REST Service</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> </web-app>
Параметр com.sun.jersey.config.property.packages должен указывать у нас тот пакет в котором будут храниться наши файлы ресуры. А в тэге url-pattern хранится путь к нашему веб-сервису. Думаю не стоит рассказывать, что это основополагающий момент RESTful веб сервисов, ведь его сама идеология говорит о том, что каждый ресурс должен иметь свой адрес. Теперь наш веб сервис доступен по адресу localhost:8080/[имя нашего проекта]/rest/. Это будет являться нашим базовым URI.
Создание веб-сервиса
Создание сущностих
В качестве примера я хотел привести простой веб-севрис, который представляет из себя такой мини дневничок. Т.е. у нас есть один тип сущностей Message и с ним мы и будем работать.
Вот класс Message
package ru.example.rest.entity; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Message { private long messageId; private String messageTitle; private String messageText; public Message() { } public Message(long messageId, String messageTitle, String messageText) { this.messageId = messageId; this.messageTitle = messageTitle; this.messageText = messageText; } public long getMessageId() { return messageId; } public void setMessageId(long messageId) { this.messageId = messageId; } public String getMessageTitle() { return messageTitle; } public void setMessageTitle(String messageTitle) { this.messageTitle = messageTitle; } public String getMessageText() { return messageText; } public void setMessageText(String messageText) { this.messageText = messageText; } @Override public String toString() { return "Message [messageId=" + messageId + ", messageTitle=" + messageTitle + ", messageText=" + messageText + "]"; } }
Единственное что тут может вызывать вопрос это аннотация @XmlRootElement. Она нам нужна чтобы Java сама преобразовывала данный класс в XML формат для передачи через HTTP. В остальном это обычный класс с тремя полями. Id сообщения, Title сообщения и Text сообщения.
Создание модели для работы с нашим классом
В качестве контент-провайдера я решил использовать обычный List. На данном этапе мне не хотелось бы углубляться в ORM и хранение данных в таблицах БД, потому что эта тема заслуживает отельной статьи, которая возможно будет следующей. И так я создал служебный класс который будет хранить в себе List наших сообщений и предоставлять методы для работы с этим листом.
Вот листинг класса для работы с данными
package ru.example.rest.model; import java.util.ArrayList; import java.util.List; import ru.example.rest.entity.Message; public class Data { private static List<Message> data; private static long count = 5; static { data = new ArrayList<Message>(); data.add(new Message(1L, "Hello", "Hello! I'm first entry!")); data.add(new Message(2L, "2nd", "second messages")); data.add(new Message(3L, "here again!", "some text")); data.add(new Message(4L, "HI!", "pam param")); } public static List<Message> getData() { return data; } public static Message findMessageById(long id) { for (Message message : data) { if (message.getMessageId() == id) { return message; } } return null; } public static boolean deleteMessageById(long id) { boolean result = false; for (Message message : data) { if (message.getMessageId() == id) { result = data.remove(message); return result; } } return result; } public static boolean updateMessage(Message message) { boolean result = false; for (Message temp: data) { if (temp.getMessageId() == message.getMessageId()) { temp.setMessageText(message.getMessageText()); temp.setMessageTitle(message.getMessageTitle()); result = true; } } return result; } public static boolean addMesage(Message message) { message.setMessageId(count); count++; return data.add(message); } }
Тут мы просто создаем List и заполняем его несколькими записями. Далее метод getData() возвращает нам ссылку на сам List. Что делают остальные методы легко догадаться по их названиям.
Теперь сам сервис
Сам сервис это ресурс-класс с аннотациями в котором указывается по какому URI какой ресурс или какое действие будет происходить в зависимости от типа запроса. Основные аннотации это
Path(«путь к данному ресурсу») данная аннотация указывает на конкретный адрес класса или метода.
так же есть разновидность Path("{id}") которая нам говорит о том что id это некая переменная которую мы сможем передать в наш метод.
Consumes (MediaType) и Produces(MediaType) будут говорить нам о получаемых и отправляемых данных соответственно. В нашем случае я выбрал APPLICATION_XML. Не спраши��айте почему не JSON, просто по мне так проще. Не зря же придумали JAX-Bind.
GET — говорит что этот метод будет отправлять данные от сервиса клиенту.
PUT/POST — говорит что этот метод будет добавлять/обновлять данные в наш сервис.
Лично я использую PUT для добавления, а POST для обновления.
DELETE — говорит о том что этот метод будет удалять данные из нашего хранилища
Вот листинг класса ресурса
package ru.example.rest.resource;
import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.xml.bind.JAXBElement; import ru.example.rest.entity.Message; import ru.example.rest.model.Data; @Path("message") public class MessageResource { @GET @Produces(MediaType.APPLICATION_XML) public List<Message> getAllMessages() { List<Message> messages = Data.getData(); if (messages == null) { throw new RuntimeException("Can't load all messages"); } return messages; } @GET @Path("{id}") @Produces(MediaType.APPLICATION_XML) public Message getMessageById(@PathParam("id") long id) { Message message = Data.findMessageById(id); if (message == null) { throw new RuntimeException("can't find mesage with id = " + id); } return message; } @PUT @Consumes(MediaType.APPLICATION_XML) public void addMessage(JAXBElement<Message> message) { if (Data.addMesage(message.getValue()) != true) { throw new RuntimeException("can't add mesage with id = " + message.getValue().getMessageId()); } } @DELETE @Path("{id}") public void deleteMessage(@PathParam("id") long id) { if (Data.deleteMessageById(id) != true) { throw new RuntimeException("can't delete mesage with id = " + id); } } @POST @Consumes(MediaType.APPLICATION_XML) public void updateMessage(JAXBElement<Message> message) { if (Data.updateMessage(message.getValue()) != true) { throw new RuntimeException("can't update mesage with id = " + message.getValue().getMessageId()); } } }
Думаю стоит прояснить несколько моментов. Наш класс-ресурс будет доступен по адресу localhost:8080/[имя проекта]/rest/message при запросе типа GET нам будет возвращаться список всех записей. при запросе POST с параметром Message у нас будет обновляться элемент Message. При запросе типа PUT c параметром Message у нас будет добавляться новый message в наше хранилище данных. При обращении к адресу методом типа GET localhost:8080/[имя проекта]/rest/message/id нам будет возвращаться message имеющий номер равный id. А если также обратиться методом DELETE то будет удален message с номером id.
Заключение
Небольшой пример клиента
Для тестирования данного сервиса я написал небольшой Java клиент. Обычное java приложение которое демонстрирует обращение по всем методам и выводит результаты на консоль.
Вот его листинг
public class RestClient { public static void main(String[] args) { ClientConfig config = new DefaultClientConfig(); Client client = Client.create(config); WebResource service = client.resource(getBaseURI()); /* * Get list of messages */ GenericType<List<Message>> genericType = new GenericType<List<Message>>() { }; List<Message> messages = service.path("rest").path("message") .accept(MediaType.APPLICATION_XML).get(genericType); for (Message temp : messages) { System.out.println(temp); } /* * Get message by ID */ long id = 4; Message message = service.path("rest").path("message") .path(String.valueOf(id)).accept(MediaType.APPLICATION_XML) .get(Message.class); System.out.println("Message with ID = " + id); System.out.println(message); /* * Update message */ message.setMessageTitle("udated title"); message.setMessageText("updated text"); service.path("rest").path("message").post(message); message = service.path("rest").path("message").path(String.valueOf(id)) .accept(MediaType.APPLICATION_XML).get(Message.class); System.out.println("Message with ID = " + id); System.out.println(message); /* * Delete message */ System.out.println("delete message with ID = " + id); service.path("rest").path("message").path(String.valueOf(id)).delete(); messages = service.path("rest").path("message") .accept(MediaType.APPLICATION_XML).get(genericType); for (Message temp : messages) { System.out.println(temp); } /* * Put message */ System.out.println("puttin' message"); message = new Message(); message.setMessageText("PUT MESSAGE!"); message.setMessageTitle("Put message"); service.path("rest").path("message") .accept(MediaType.APPLICATION_XHTML_XML).put(message); messages = service.path("rest").path("message") .accept(MediaType.APPLICATION_XML).get(genericType); for (Message temp : messages) { System.out.println(temp); } } private static URI getBaseURI() { return UriBuilder.fromUri("http://localhost:8080/WebRest").build(); } }
Самые интересные строчки тут это
GenericType<List<Message>> genericType = new GenericType<List<Message>>() {}; List<Message> messages = service.path("rest").path("message") .accept(MediaType.APPLICATION_XML).get(genericType);
genericType это грубо говоря тип List, он нужен чтобы передавать и получать более сложные структуры используя JAX-B. И все данные мы передаем как JAXBElement.
В данный клиент необходимо включить так же библиотеки Jersey и включить файл описания класса Message.
И в заключении
В заключении хотелось бы сказать, что я буду очень рад если эта статья кому то поможет. Так же я готов выслушать любые предложения и любую критику. Я ведь только учусь.
Код клиента и код сервиса доступны на моем гитхабе ЗДЕСЬ.
Данные которыми я пользовался это статья Vogella и интернет.
