Реализуем RESTful Web Service на java
Поводом к написанию статьи послужило, то что к моему большому удивлению на хабре я не нашёл статьи о реализации RESTful Web Service на Java, может, конечно, плохо искал. Да написано про RESTful web services очень много, но как то вот так, чтобы простенько с примерами кода, рабочий сервис, не так уж и легко найти и не только на хабре…
Вообще с REST я познакомился совсем недавно, не больше месяца назад. Так что буду очень благодарен за советы, поправки и критику!
Разобраться было и так вообщем то не сложно, но я думаю аналогичный пост мне бы очень помог и сильно бы ускорил процесс обучения! Тем более, если вы начинающий разработчик и о многом только слышали, а руками никогда не трогали.
По моему первому впечатлению: действительно вещь очень удобная, а главное очень простая, ещё и если использовать JSON, а не XML, ну по крайней мере мне так показалось после опыта работы с SOAP и WSDL. Ну, да об этом я думаю и так все знают, кто хоть немного работал с веб сервисами.
Так что, кто заинтересовался реализацией, прошу под кат
Сразу оговоримся:
1. весь код, конечно, в статье не выложишь и о нём не расскажешь;
2. версия проектика, конечно, не финальная и, как я уже говорил выше — буду очень благодарен за замечания и советы;
3. конечно же, есть баги.
- JDK 1.6
- Apache_CXF
- Spring 3 Framework JDBC
- Apache Tomcat 7.0
- MySQL 5.1
- Eclipse 4.2 Juno
- Maven 3.0
ПО выбиралось по очень простому принципу — чем проще, тем лучше. Да, вместо MySQL для нагруженных сервисов без необходимости делать сложные запросы в базу, очень хорошо использовать MongoDB, ну, по крайней мере по этому поводу много написанно, да и опять же удобнее её использовать так как на входе тот же JSON.
2. В принципе что будет делать наш сервис — тут всё очень банально: сервис будет работать с одной табличкой в БД — сосбственно вставлять, апдейтать, удалять, ну и, конечно же, получать записи списком или по Id. Конечно же, хотелось бы иметь возможность параметризированного запроса на получение списка записей, не плохо было бы сделать «красивый» урл к сервису, прикрутить какой-нибудь интерсептор, чтобы, например, проверять права пользователя на доступ к сервису, или что-нибудь другое делать перед запуском сервиса, ну и как-то централизованно управлять кодами ошибок в ответах от сервера.
CREATE TABLE `customer` ( `id` varchar(45) NOT NULL, `first_name` varchar(45) DEFAULT NULL, `last_name` varchar(45) DEFAULT NULL, `phone` varchar(45) DEFAULT NULL, `mail` varchar(45) DEFAULT NULL, `adress` varchar(45) DEFAULT NULL, `contract_id` varchar(45) DEFAULT NULL, `contract_expire_date` date DEFAULT NULL )
1. http://mysite.com/service/customer
2. http://mysite.com/service/customer/
4 стандартных статуса, которые мы будем дополнительно обрабатывать (например, добавлять версию наших веб сервисов в ответ и при ошибке — наш код ошибки):
200 — Successful;
401 — Not Authorized;
404 — Not Found;
500 — Server error during operation.
3. Реализация (код на гитхабе тут):
Да, код, минимально комментировал, описание аннотаций тут.
public class CustomersServiceJSON implements ICustomersService < // link to our dao object private ICustomersDAO customersDAO; // for customersDAO bean property injection public ICustomersDAO getCustomersDAO() < return customersDAO; >public void setCustomersDAO(ICustomersDAO customersDAO) < this.customersDAO = customersDAO; >// for retrieving request headers from context // an injectable interface that provides access to HTTP header information. @Context private HttpHeaders requestHeaders; private String getHeaderVersion() < return requestHeaders.getRequestHeader("version").get(0); >// get by id service @GET @Path("/") public Response getCustomer(@PathParam("id") String id) < Customer customer = customersDAO.getCustomer(id); if (customer != null) < return ResponseCreator.success(getHeaderVersion(), customer); >else < return ResponseCreator.error(404, Error.NOT_FOUND.getCode(), getHeaderVersion()); >> // remove row from the customers table according with passed id and returned // status message in body @DELETE @Path("/") public Response removeCustomer(@PathParam("id") String id) < if (customersDAO.removeCustomer(id)) < return ResponseCreator.success(getHeaderVersion(), "removed"); >else < return ResponseCreator.success(getHeaderVersion(), "no such id"); >> // create row representing customer and returns created customer as // object->JSON structure @POST @Consumes(MediaType.APPLICATION_JSON) public Response createCustomer(Customer customer) < System.out.println("POST"); Customer creCustomer = customersDAO.createCustomer(customer); if (creCustomer != null) < return ResponseCreator.success(getHeaderVersion(), creCustomer); >else < return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >> // update row and return previous version of row representing customer as // object->JSON structure @PUT @Consumes(MediaType.APPLICATION_JSON) public Response updateCustomer(Customer customer) < Customer updCustomer = customersDAO.updateCustomer(customer); if (updCustomer != null) < return ResponseCreator.success(getHeaderVersion(), updCustomer); >else < return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >> // returns list of customers meeting query params @GET //@Produces(MediaType.APPLICATION_JSON) public Response getCustomers(@QueryParam("keyword") String keyword, @QueryParam("orderby") String orderBy, @QueryParam("order") String order, @QueryParam("pagenum") Integer pageNum, @QueryParam("pagesize") Integer pageSize) < CustomerListParameters parameters = new CustomerListParameters(); parameters.setKeyword(keyword); parameters.setPageNum(pageNum); parameters.setPageSize(pageSize); parameters.setOrderBy(orderBy); parameters.setOrder(Order.fromString(order)); ListlistCust = customersDAO.getCustomersList(parameters); if (listCust != null) < GenericEntity entity = new GenericEntity( listCust) < >; return ResponseCreator.success(getHeaderVersion(), entity); > else < return ResponseCreator.error(404, Error.NOT_FOUND.getCode(), getHeaderVersion()); >> >
Всё достаточно просто — 4 веб сервиса в зависимости от URI и метода которым этот URI дёргается, есть объект DAO, который подключается в beans.xml и доступ к заголовкам запроса, чтобы доставать для примера кастомный заголовок «version».
Штука, которая отрабатывает перед тем как вызывается сервис:
public class PreInvokeHandler implements RequestHandler < // just for test int count = 0; private boolean validate(String ss_id) < // just for test // needs to implement count++; System.out.println("SessionID: " + ss_id); if (count == 1) < return false; >else < return true; >> public Response handleRequest(Message message, ClassResourceInfo arg1) < Map> headers = CastUtils.cast((Map) message .get(Message.PROTOCOL_HEADERS)); if (headers.get("ss_id") != null && validate(headers.get("ss_id").get(0))) < // let request to continue return null; >else < // authentication failed, request the authentication, add the realm return ResponseCreator.error(401, Error.NOT_AUTHORIZED.getCode(), headers.get("version").get(0)); >> >
Здесь в методе validate() можно проверять какие-то пред условия, чисто для теста добавлена проверка кастомного заголовка в запросе идентификатор сессии «ss_id», ну, и с первого раза даже с этим заголовком будет падать 401.
Общий обработчик exceptions:
public class CustomExceptionMapper implements ExceptionMapper < @Context private HttpHeaders requestHeaders; private String getHeaderVersion() < return requestHeaders.getRequestHeader("version").get(0); >public Response toResponse(Exception ex) < System.out.println(ex.getMessage() + ex.getCause()); return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >>
Что-то уже многовато кода для поста, есть ещё вспомогательный класс для формирования ответа серверу и глобальный enum для хранения наших кодов ошибок. Да, дескриптор развёртывания и beans.xml всё таки приведу тут:
. service contextConfigLocation WEB-INF/beans.xml org.springframework.web.context.ContextLoaderListener CXFServlet CXF Servlet org.apache.cxf.transport.servlet.CXFServlet 1 CXFServlet /*
Тут основной интерес представляет подключение экшн сервлета от Apache — CXFServlet и стандартного спрингового ContextLoaderListener.
Здесь, собственно, задали нужные конфигурационные файлики для CXF, подключили DAO объект, наш предобработчик и обработчик исключительных ситуаций, конечно же, сам бин с сервисами и задали корень для сервисов.
Для того чтобы подёргать сервисы я использовал REST Console 4.0.2 плагин для хрома — штука достаточно простая, главное задать нужные ендпоинт, кастомные заголовки (как я уже говорил без «ss_id» всегда будет падать 401) и контент тип. Для примера:
Request Url: http://localhost:8080/service/customer Request Method: GET Status Code: 200
Accept: application/json Content-Type: application/json ss_id: 12312.111 version: 12312.111 .
Status Code: 200 Date: Tue, 21 Aug 2012 13:09:45 GMT Content-Length: 877 Server: Apache-Coyote/1.1 Content-Type: application/json version: 12312.111
И последнее, хотелось иметь «красивый», лучше скажем нужный нам урл к вебсервисам. Кнечно, можно поправить server.xml или использовать какой-нибудь тул для urlRewrite, но по моему самый простой способ это запаковать наш веб архив в ear и задать другой рут для наших веб-сервисов в application.xml, но в рамках данного поста я этого уже делать не буду.
P.S.: Надеюсь, что данный пост будет полезен тем кто хочет познакомиться с Java RESTful web services, а более опытные посоветуют и покритикуют!
Java JSON RESTful Web Service Example
In this example we shall learn implementing Restful Web Service in Java where the data interchange format shall be JSON.
1. Introduction to RESTful Web Services
RESTful Web Services follow REST architecture which stands for REpresentational State Transfer. RESTful web services are light weight and highly scalable is one of the most common way to create APIs on web. In the REST architectural style, data and functionality are considered resources and are accessed using Uniform Resource Identifiers (URIs). REST uses various representations to represent a resource like text, JSON and XML.
2. HTTP Methods
- GET – Retrieves current state of a resource.
- PUT – Creates a new resource.
- DELETE – Deletes a resource.
- POST – Updates an existing resource or creates a new resource if it doesn’t exist.
3. HTTP Methods
Following is the sample usage of HTTP methods with RESTful Web Service. We shall be implementing the GET HTTP method in our example where data interchange format shall be JSON.
S.No. | HTTP Method | URI | Operation |
---|---|---|---|
1 | GET | /JavaCodeGeeks/AuthorService/authors | Get list of all authors |
2 | GET | /JavaCodeGeeks/AuthorService/authors/1 | Get author with id 1 |
3 | PUT | /JavaCodeGeeks/AuthorService/authors/2 | Insert author with id 2 |
4 | POST | /JavaCodeGeeks/AuthorService/authors/2 | Update author with id 2 |
5 | DELETE | /JavaCodeGeeks/AuthorService/authors/1 | Delete author with id 1 |
4. Requirements
- Eclipse for Java EE.
- Apache Tomcat.
- Jersey Library can be downloaded from here.
- Google Chrome with any REST Client extension like Advanced REST client installed.
- Genson library for JSON conversion can be downloaded from here.
5. Project Setup
To start with the project setup, first create Dynamic Web Project in Eclipse.
Configure a web server like Apache Tomcat in your Eclipse environment. In my case, I had already configured the server, for which I also got an option against Target Runtime to choose while creating new Dynamic Web Project as shown above.
Now copy all libraries downloaded form Jersey‘s and Genson‘s website to WEB-INF/lib folder.
With all this, project setup is complete and now we shall learn implementing RESTful Web Services.
6. Implementing RESTful Web Service
First we shall configure Jersey to serve as servlet dispatcher for servlet requests. To do this we shall modify web.xml as follows.
index.html index.htm index.jsp default.html default.htm default.jsp JavaJsonRestWebServiceExample org.glassfish.jersey.servlet.ServletContainer jersey.config.server.provider.packages com.javacodegeeks.examples.jersey, com.jersey.jaxb, com.fasterxml.jackson.jaxrs.json com.sun.jersey.api.json.POJOMappingFeature true 1 JavaJsonRestWebServiceExample /JavaCodeGeeks/*
In web.xml, notice the configuration of ServletContainer and how packages folder has been configured that shall be searched for web service implementation.
Next we shall implement a POJO class whose object we shall return as a JSON via REST GET api.
package com.javacodegeeks.examples.jersey; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Person < private int id; private String name; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public Person(int id, String name) < super(); this.id = id; this.name = name; >public Person() < super(); >>
Now comes the last step in implementing web service.
package com.javacodegeeks.examples.jersey; import java.util.ArrayList; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/AuthorService") public class AuthorService < @GET @Path("/authors") @Produces(MediaType.APPLICATION_JSON) public List getTrackInJSON() < List listPerson = new ArrayList(); Person p1 = new Person(); p1.setId(1);; p1.setName("name1"); Person p2 = new Person(); p2.setId(2); p2.setName("name2"); listPerson.add(p1); listPerson.add(p2); return listPerson; >>
Notice the line of code @Produces(MediaType.APPLICATION_JSON) above. With this we are telling the web service that response shall be of type JSON.
Now that implementation of REST Web Service with GET HTTP Method is complete, we shall start the Tomcat server which we configured in Target Runtime above.
7. Testing RESTful Web Service
Now that implementation of web service is complete, we shall be testing the same now.
To test the web service open Google Chrome, open Developer Tools, and go to Network tab.
Now enter following URL in Address Bar and press Enter:
Notice that in Developer Tools Network tab, a new request will appear. Click on it and then click on Response tab within.
Here we can see a JSON response as returned from web service.
8. Download the Source Code
This was an example of Java JSON RESTful Web Service.