Добрый вечер всем!
Не так давно (в феврале этого года), я решил заняться программированием. В качестве языка был выбран 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 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(Message message) {
if (Data.addMesage(message) != true) {
throw new RuntimeException("can't add mesage with id = "
+ message.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(Message message) {
if (Data.updateMessage(message) != true) {
throw new RuntimeException("can't update mesage with id = "
+ message.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.
В данный клиент необходимо включить так же библиотеки Jersey и включить файл описания класса Message.
И в заключении
В заключении хотелось бы сказать, что я буду очень рад если эта статья кому то поможет. Так же я готов выслушать любые предложения и любую критику. Я ведь только учусь.
Код клиента и код сервиса доступны на моем гитхабе ЗДЕСЬ.
Данные которыми я пользовался это статья Vogella и интернет.
Автор: daron666