Java web service method

Реализуем 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
Читайте также:  Символ переноса строки php код

ПО выбиралось по очень простому принципу — чем проще, тем лучше. Да, вместо 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, а более опытные посоветуют и покритикуют!

Источник

Веб-сервисы. Шаг 1. Что такое веб-сервис и как с ним работать?

Java-университет

Веб-сервисы. Шаг 1. Что такое веб-сервис и как с ним работать? - 1

Заголовок топика – это действительно вопрос, т.к. я сам не знаю, что это и впервые попробую поработать с этим в рамках настоящей статьи. Единственное, что могу гарантировать, что код, представленный ниже, будет работать, однако мои фразы будут лишь предположениями и догадками о том, как я сам все это понимаю. Итак, поехали…

Введение

  1. SOAP. Прежде чем вызвать удаленную процедуру, нужно этот вызов описать в XML файле формата SOAP. SOAP – это просто одна из многочисленных XML разметок, которая используется в веб-сервисах. Все, что мы хотим куда-то отправить через HTTP, сначала превращается в XML описание SOAP, потом засовывается в HTTP пакет и посылается на другой компьютер в сети по TCP/IP.
  2. WSDL. Есть веб-сервис, т.е. программа, методы которой можно удаленно вызывать. Но стандарт требует, чтобы к этой программе прилагалось описание, в котором сказано, что «да, вы не ошиблись – это действительно веб-сервис и можно у него вызвать такие-то такие-то методы». Такое описание представляется еще одним файлом XML, который имеет другой формат, а именно WSDL. Т.е. WSDL – это просто XML файл описания веб-сервиса и больше ничего.

Общий подход

  1. Описать интерфейс нашего веб-сервиса
  2. Реализовать этот интерфейс
  3. Запустить наш веб-сервис
  4. Написать клиента и удаленно вызвать нужный метод веб-сервиса

Сервер

Запустим IDEA и создадим новый проект Create New Project. Укажем имя HelloWebService и нажмем кнопку Next, далее кнопку Finish. В папке src создадим пакет ru.javarush.ws. В этом пакете создадим интерфейс HelloWebService :

 package ru.javarush.ws; // это аннотации, т.е. способ отметить наши классы и методы, // как связанные с веб-сервисной технологией import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; // говорим, что наш интерфейс будет работать как веб-сервис @WebService // говорим, что веб-сервис будет использоваться для вызова методов @SOAPBinding(style = SOAPBinding.Style.RPC) public interface HelloWebService < // говорим, что этот метод можно вызывать удаленно @WebMethod public String getHelloString(String name); >

В этом коде классы WebService и WebMethod являются так называемыми аннотациям и ничего не делают, кроме как помечают наш интерфейс и его метод, как веб-сервис. Это же относится и к классу SOAPBinding . Разница лишь в том, что SOAPBinding – это аннотация с параметрами. В данном случае используется параметр style со значением, говорящим, что веб-сервис будет работать не через сообщения-документы, а как классический RPC, т.е. для вызова метода. Давайте реализуем логику нашего интерфейса и создадим в нашем пакете класс HelloWebServiceImpl . Кстати, замечу, что окончание класса на Impl – это соглашение в Java, по которому так обозначают реализацию интерфейсов (Impl – от слова implementation, т.е. реализация). Это не требование и вы вольны назвать класс как хотите, но правила хорошего тона того требуют:

 package ru.javarush.ws; // таже аннотация, что и при описании интерфейса, import javax.jws.WebService; // но здесь используется с параметром endpointInterface, // указывающим полное имя класса интерфейса нашего веб-сервиса @WebService(endpointInterface = "ru.javarush.ws.HelloWebService") public class HelloWebServiceImpl implements HelloWebService < @Override public String getHelloString(String name) < // просто возвращаем приветствие return "Hello, " + name + "!"; >> 

Запустим наш веб-сервис как самостоятельный сервер, т.е. без участия всяких Tomcat и серверов приложений (это тема отдельного разговора). Для этого в структуре проекта в папке src создадим пакет ru.javarush.endpoint , а в нем создадим класс HelloWebServicePublisher с методом main :

 package ru.javarush.endpoint; // класс, для запуска веб-сервера с веб-сервисами import javax.xml.ws.Endpoint; // класс нашего веб-сервиса import ru.javarush.ws.HelloWebServiceImpl; public class HelloWebServicePublisher < public static void main(String. args) < // запускаем веб-сервер на порту 1986 // и по адресу, указанному в первом аргументе, // запускаем веб-сервис, передаваемый во втором аргументе Endpoint.publish("http://localhost:1986/wss/hello", new HelloWebServiceImpl()); >> 

Теперь запустим этот класс, нажав Shift+F10. В консоли ничего не появится, но сервер запущен. В этом можно убедиться набрав в браузере строку http://localhost:1986/wss/hello?wsdl. Открывшаяся страница, с одной стороны, доказывает, что у нас на компьютере (localhost) запустился веб-сервер (http://) на порту 1986, а, с другой стороны, показывает WSDL описание нашего веб-сервиса. Если вы остановите приложение, то описание станет недоступно, как и сам веб-сервис, поэтому делать этого не будем, а перейдем к написанию клиента.

Клиент

В папке проекта src создадим пакет ru.javarush.client , а в нем класс HelloWebServiceClient с методом main :

 package ru.javarush.client; // нужно, чтобы получить wsdl описание и через него // дотянуться до самого веб-сервиса import java.net.URL; // такой эксепшн возникнет при работе с объектом URL import java.net.MalformedURLException; // классы, чтобы пропарсить xml-ку c wsdl описанием // и дотянуться до тега service в нем import javax.xml.namespace.QName; import javax.xml.ws.Service; // интерфейс нашего веб-сервиса (нам больше и нужно) import ru.javarush.ws.HelloWebService; public class HelloWebServiceClient < public static void main(String[] args) throws MalformedURLException < // создаем ссылку на wsdl описание URL url = new URL("http://localhost:1986/wss/hello?wsdl"); // Параметры следующего конструктора смотрим в самом первом теге WSDL описания - definitions // 1-ый аргумент смотрим в атрибуте targetNamespace // 2-ой аргумент смотрим в атрибуте name QName qname = new QName("http://ws.javarush.ru/", "HelloWebServiceImplService"); // Теперь мы можем дотянуться до тега service в wsdl описании, Service service = Service.create(url, qname); // а далее и до вложенного в него тега port, чтобы // получить ссылку на удаленный от нас объект веб-сервиса HelloWebService hello = service.getPort(HelloWebService.class); // Ура! Теперь можно вызывать удаленный метод System.out.println(hello.getHelloString("JavaRush")); >> 

Максимум комментариев по коду я дал в листинге. Добавить мне нечего, поэтому запускаем (Shift+F10). Мы должны в консоли увидеть текст: Hello, JavaRush! Если не увидели, то видимо забыли запустить веб-сервис.

Заключение

В данном топике был представлен краткий экскурс в веб-сервисы. Еще раз скажу, что многое из того, что я написал – это мои догадки по поводу того, как это работает, и поэтому мне не стоит сильно доверять. Буду признателен, если знающие люди меня поправят, ведь тогда я чему-нибудь научусь. UPD. Продолжаем разговор

Источник

Оцените статью