Статьи

Spring 3 RESTful веб-сервисы

Spring 3 обеспечивает поддержку веб-сервисов RESTful . В этом руководстве мы покажем вам, как реализовать веб-сервис RESTful в Spring или, если вы хотите, представить существующий сервис Spring как веб-сервис RESTful . Чтобы сделать вещи более интересными, мы продолжим с того места, которое мы оставили в предыдущей статье об интеграции Spring GWT Hibernate JPA и Infinispan HornetQ . Мы собираемся использовать наш проект GWTSpringInfinispanHornetQ и представить нашу функциональность CRUD «employeeService» в качестве веб-службы RESTful . Конечно, вы можете следовать этой статье, чтобы быть в курсе того, как представить свои сервисы Spring в качестве веб-сервисов RESTful .

Наиболее популярным подходом для реализации веб-сервисов RESTful является спецификация Sun JAX-RS . Доступно несколько проектов, поддерживающих JAX-RS, таких как CXF , Jersey , RESTEasy и Restlet . Большинство из них также поддерживают Spring . Spring напрямую не поддерживает JAX-RS , вместо этого добавлена ​​функциональность RESTful для самой функции Spring MVC. Если вы не знакомы со средой Spring MVC, пожалуйста, обратитесь к соответствующей главе документации Spring здесь . Для нетерпеливых краткий обзор следует.

Spring MVC расшифровывается как Model View Controller. Это помогает в создании гибких и слабо связанных веб-приложений. Шаблон проектирования Модель — Представление — Контроллер обеспечивает разделение задач (бизнес-логика, логика представления и логика навигации) в многоуровневом веб-приложении. «Контроллеры» отвечают за получение запроса от пользователя и вызов внутренних сервисов. Модели отвечают за инкапсуляцию данных приложения. Представления возвращают ответ пользователю с использованием объекта модели. Вкратце :

Когда запрос отправляется в Spring MVC Framework, происходит следующая последовательность событий.

  • «ДиспетчерСервлет» первым получает запрос
  • «DispatcherServlet» обращается к «HandlerMapping» и вызывает «Controller», связанный с запросом
  • «Контроллер» обрабатывает запрос, вызывая соответствующие методы службы и возвращает объект «ModeAndView» в «DispatcherServlet». Объект «ModeAndView» содержит данные модели и имя вида
  • «DispatcherServlet» отправляет имя представления «ViewResolver», чтобы найти фактическое «представление» для вызова
  • «DispatcherServlet» передает объект модели в «View» для визуализации результата.
  • «Просмотр» с помощью данных модели отображает результат и возвращает его пользователю.

Хватит говорить! Давайте запачкаем руки!

Нам понадобится библиотека генерации байт-кода «cglib» и среда манипулирования байт-кодом «asm», чтобы Spring правильно применил аспекты AOP к объектам «Controller». Мы будем использовать версию 2.2 «cglib», которую вы можете скачать отсюда, и бинарный дистрибутив «asm», который вы можете скачать отсюда . Найдите asm-all-3.3.jar в папке / lib / all бинарного дистрибутива «asm» и поместите оба файла asm-all-3.3.jar и cglib-2.2.jar в папку / war / WEB-INF / lib вашего проекта. ,

Наконец нам понадобится JSON- процессор Джексона. Мы будем использовать версию 1.5.3 дистрибутивов «core» и «mapper», которую вы можете скачать здесь . Разместите оба jackson-core-asl-1.5.3.jar
и jackson-mapper-asl-1.5.3.jar в папке / war / WEB-INF / lib вашего проекта.

Мы должны позаботиться о зависимостях для нашего проекта Eclipse . Следующие jar-файлы должны быть включены в путь сборки Java проекта:

  • org.springframework.web-3.0.1.RELEASE-a.jar

Как указано выше, «DispatcherServlet» — это один сервлет, который управляет всем процессом обработки запросов. Как и любой другой сервлет, его необходимо настроить в дескрипторе веб-развертывания или в нашем приложении. Найдите файл «web.xml» в папке / war / WEB-INF и добавьте следующее:

01
02
03
04
05
06
07
08
09
10
<servlet>
 <servlet-name>dispatcher</servlet-name>
 <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
 <load-on-startup>2</load-on-startup>
</servlet>
 
<servlet-mapping>
 <servlet-name>dispatcher</servlet-name>
 <url-pattern>/restServices/*</url-pattern>
</servlet-mapping>

По умолчанию «DispatcherServlet» будет искать файл с именем «{servlet-name} –servlet.xml» для загрузки конфигурации Spring MVC. В нашем случае «dispatcher-servlet.xml». Здесь мы используем шаблон url как «/ restServices / *», чтобы «DispatcherServlet» обрабатывал все входящие запросы только по указанному шаблону. Создайте файл «dispatcher – servlet.xml» и поместите его в папку / war / WEB-INF, как показано ниже:

Что следует отметить здесь:

  • Мы устанавливаем атрибут base-package элемента конфигурации «context: component-scan», где будут находиться наши аннотированные классы Spring MVC
  • Мы используем элемент конфигурации «tx: annotation-driven», чтобы иметь возможность внедрить транзакционное поведение в наши классы MVC
  • Mvc: annotation-driven — это элемент конфигурации Spring 3, который значительно упрощает настройку Spring MVC. Этот тег регистрирует «HandlerMapping» и «HandlerAdapter», необходимые для отправки запросов вашим аннотированным классам @Controller. Кроме того, он применяет разумные значения по умолчанию, основанные на том, что присутствует в вашем classpath. Такие значения по умолчанию включают (среди прочих):
    • Поддержка форматирования числовых полей с аннотацией @NumberFormat
    • Поддержка форматирования полей Date, Calendar и Joda Time с аннотацией @DateTimeFormat, если Joda Time находится в пути к классам
    • Поддержка проверки входных данных аннотированного класса @Controller с аннотацией @Valid, если поставщик JSR-303 находится в пути к классам
    • Поддержка чтения и записи XML, если JAXB находится на пути к классам
    • Поддержка чтения и записи JSON , если Джексон находится на пути к классам

Создайте подпакет «конечные точки» в пакете «сервер» вашего проекта. Что касается GWT , то конечные точки службы являются компонентами на стороне сервера, поэтому все классы должны быть помещены в «серверный» пакет. Под подпакетом «endpoints» поместите класс «EmployeeServiceController», как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.javacodegeeks.gwtspring.server.endpoints;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;
import com.javacodegeeks.gwtspring.shared.services.EmployeeService;
 
@Controller
@RequestMapping("/employeeService")
public class EmployeeServiceController {
 
 @Autowired
 EmployeeService employeeService;
 
 @RequestMapping(value = "/{id}", method = RequestMethod.GET)
 @ResponseBody
 public EmployeeDTO findEmployee(@PathVariable("id") long employeeId) {
  return employeeService.findEmployee(employeeId);
 }
 
 @RequestMapping(value = "/{id}/{name}/{surname}/{job}", method = RequestMethod.POST)
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public String saveEmployee(@PathVariable("id") long employeeId, @PathVariable String name, @PathVariable String surname, @PathVariable("job") String jobDescription) throws Exception {
  employeeService.saveEmployee(employeeId, name, surname, jobDescription);
  return "redirect:/restServices/employeeService/" + employeeId;
 }
 
 @RequestMapping(value = "/{id}/{name}/{surname}/{job}", method = RequestMethod.PUT)
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public String updateEmployee(@PathVariable("id") long employeeId, @PathVariable String name, @PathVariable String surname,  @PathVariable("job") String jobDescription) throws Exception {
  employeeService.updateEmployee(employeeId, name, surname, jobDescription);
  return "redirect:/restServices/employeeService/" + employeeId;
 }
 
 @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 @ResponseBody
 public String deleteEmployee(@PathVariable("id") long employeeId) throws Exception {
  employeeService.deleteEmployee(employeeId);
  return "OK";
 }
 
}

Для справки мы предоставляем реализации EmployeeService и EmployeeDTO:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.javacodegeeks.gwtspring.server.services;
 
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
import com.javacodegeeks.gwtspring.server.dao.EmployeeDAO;
import com.javacodegeeks.gwtspring.server.utils.NotificationsProducer;
import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;
import com.javacodegeeks.gwtspring.shared.services.EmployeeService;
 
@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
 
 @Autowired
 private EmployeeDAO employeeDAO;
 
 @Autowired
 NotificationsProducer notificationsProducer;
 
 @PostConstruct
 public void init() throws Exception {
 }
 
 @PreDestroy
 public void destroy() {
 }
 
 public EmployeeDTO findEmployee(long employeeId) {
  return employeeDAO.findById(employeeId);
 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
 
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
 
  if(employeeDTO == null) {
   employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
   employeeDAO.persist(employeeDTO);
  }
 
 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
 
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
 
  if(employeeDTO != null) {
   employeeDTO.setEmployeeName(name);
   employeeDTO.setEmployeeSurname(surname);
   employeeDTO.setJob(jobDescription);
  }
 
}
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void deleteEmployee(long employeeId) throws Exception {
 
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
 
  if(employeeDTO != null)
   employeeDAO.remove(employeeDTO);
 
 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
 
  EmployeeDTO employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
 
  employeeDAO.merge(employeeDTO);
 
  notificationsProducer.sendNotification("Save Or Update Employee with values : \nID : " + employeeId + "\nName : " + name + "\nSurname : " + surname + "\nJob description : " + jobDescription);
 
 }
 
}

Как видите, «EmployeeServiceController» действует как класс-оболочка для класса employeeService. Мы берем ссылку на реальный сервис и реализуем методы, которые напрямую вызывают функциональность сервиса. Чтобы внедрить класс «EmployeeServiceController» с функциональностью «Controller», нам просто нужно аннотировать его как таковой. Аннотация @Controller обозначает, что аннотированный класс является классом «Controller», доступным для «DispatcherServlet» для делегирования ему запросов.

В начале этого урока мы говорили о том, как «DispatcherServlet» использует «HandlerMappings» для выбора соответствующих «контроллеров», чтобы делегировать пользовательские запросы. Аннотация @RequestMapping используется для отображения веб-запросов на определенные классы обработчиков и / или методы обработчиков. Поэтому, используя аннотацию @RequestMapping («/ employeeService») на уровне типа, мы инструктируем «DispatcherServlet» делегировать все веб-запросы с URI ресурса «/ employeeService» экземпляру «EmployeeServiceController». Кроме того, мы используем аннотацию @RequestMapping на уровне метода, чтобы сузить делегирование «DispatcherServlet» для определенных операций на основе запрошенного URI ресурса. Вы должны были заметить использование аннотации @PathVariable. В Spring 3 введено использование шаблонов URI через аннотацию @PathVariable. Шаблон URI — это URI-подобная строка, содержащая одно или несколько имен переменных. Когда эти переменные заменяются значениями, шаблон становится URI. Таким образом, клиентский HTTP-запрос GET для URI ресурса «/ employeeService / 1» будет делегирован операции «findEmployee» нашего экземпляра «EmployeeServiceController», а значение параметра employeeId будет равно 1.

ПРИМЕЧАНИЕ. @RequestMapping будет обрабатываться только в том случае, если в диспетчере присутствует соответствующий «HandlerMapping» (для аннотаций на уровне типа) и / или «HandlerAdapter» (для аннотаций на уровне метода). Благодаря упрощениям Spring 3 MVC, использование элемента конфигурации «mvc: annotation-based» в dispatcher-servlet.xml отвечает всем нашим требованиям к конфигурации.

В начале этого урока мы говорили о том, как «Контроллер» обрабатывает каждый запрос, вызывая соответствующие методы службы и возвращает объект «ModeAndView» в «DispatcherServlet». Объект «ModeAndView» содержит данные модели и имя представления для правильной визуализации обратно клиенту. Такое поведение не всегда желательно. Например, в нашем примере мы хотим сериализовать ответы службы прямо в тело ответа HTTP. Аннотация @ResponseBody может быть помещена в метод и указывает, что возвращаемый тип должен быть записан прямо в тело ответа HTTP (а не помещен в модель или интерпретирован как имя представления). В соответствии с принятым клиентом типом (ами) контента (информация, полученная из поля «Accept» HTTP-заголовка клиентского запроса), мы будем возвращать XML или JSON представления сервисных ответов. Для этого мы используем маршаллер и демаршаллер JAXB через модуль Spring OXM и процессор JSON Джексона.

Наконец, вы должны заметить операции «saveEmployee» и «updateEmployee». Эти два элемента не аннотированы @ResponseBody и возвращают строку «redirect: / restServices / employeeService / + employeeId». Метод «Контроллер» может возвращать «специальное» строковое значение, которое выдает команды для «DispatcherServlet». С помощью вышеупомянутой команды redirect «DispatcherServlet» перенаправит вызов методу «Controller», связанному с указанным ресурсом URI (в нашем случае операция «findEmployee»). Таким образом, когда клиент запускает команду «saveEmployee» или «updateEmployee», он получит в ответ XML или JSON- представление объекта «employeeDTO», только что вставленного или обновленного.

Ниже мы представляем класс DTO.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.javacodegeeks.gwtspring.shared.dto;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
 
@Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@Entity
@XmlRootElement
@Table(name = "EMPLOYEE")
public class EmployeeDTO implements java.io.Serializable {
 
 private static final long serialVersionUID = 7440297955003302414L;
 
 @Id
 @Column(name="employee_id")
 private long employeeId;
 
 @Column(name="employee_name", nullable = false, length=30)
 private String employeeName;
 
 @Column(name="employee_surname", nullable = false, length=30)
 private String employeeSurname;
 
 @Column(name="job", length=50)
 private String job;
 
 public EmployeeDTO() {
 }
 
 public EmployeeDTO(int employeeId) {
  this.employeeId = employeeId;       
 }
 
 public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,
 String job) {
  this.employeeId = employeeId;
  this.employeeName = employeeName;
  this.employeeSurname = employeeSurname;
  this.job = job;
 }
 
 public long getEmployeeId() {
  return employeeId;
 }
 
 public void setEmployeeId(long employeeId) {
  this.employeeId = employeeId;
 }
 
 public String getEmployeeName() {
  return employeeName;
 }
 
 public void setEmployeeName(String employeeName) {
  this.employeeName = employeeName;
 }
 
 public String getEmployeeSurname() {
  return employeeSurname;
 }
 
 public void setEmployeeSurname(String employeeSurname) {
  this.employeeSurname = employeeSurname;
 }
 
 public String getJob() {
  return job;
 }
 
 public void setJob(String job) {
  this.job = job;
 }
}

Единственное, на что следует обратить внимание, это то, что мы аннотировали класс DTO аннотацией @XmlRootElement, чтобы маршаллер JAXB должным образом маршалировал.

Это оно! Для развертывания веб-приложения просто скопируйте папку / war в папку «webapps» Apache — Tomcat . Вы можете изменить имя папки war на любое другое, желательно переименовать его после имени проекта, например, GWTSpringInfinispanHornetQRemoting.

Перед запуском приложения не забудьте создать схему базы данных, здесь «javacodegeeks».

Для этого примера мы использовали Apache Derby Database версии 10.6.1.0. Вы можете скачать бинарный дистрибутив здесь . Найдите derby.jar в каталоге / lib дистрибутива и поместите его в каталог / war / WEB-INF / lib вашего проекта. Чтобы настроить встроенную базу данных с помощью Spring , добавьте следующий код конфигурации в ваш applicationContext.xml в каталоге / war / WEB-INF:

01
02
03
04
05
06
07
08
09
10
11
12
13
<bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
 <property name="uniqueResourceName" value="javacodegeeks" />
 <property name="xaDataSourceClassName" value="org.apache.derby.jdbc.EmbeddedXADataSource" />
 <property name="xaProperties">
  <props>
   <prop key="databaseName">javacodegeeks</prop>
   <prop key="createDatabase">create</prop>
  </props>
 </property>
 <property name="maxPoolSize" value="50" />
 <property name="minPoolSize" value="20" />
</bean>

Вы можете скачать RESTClient и протестировать REST-интерфейс вашего «employeeService», как описано ниже:

  • Выполните запрос POST для «http: // localhost: 8080 / GWTSpringInfinispanHornetQRemoting / restServices / employeeService / 1 / myName / mySurname / myJob», и вы должны получить представление XML вновь созданного «employeeDTO», как показано ниже:
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" standalone="yes">
<employeeDTO>
 <employeeId>1</employeeId>
 <employeeName>myName</employeeName>
 <employeeSurname>mySurname</employeeSurname>
 <job>myJob</job>
</employeeDTO>
  • Выполните запрос GET для http: // localhost: 8080 / GWTSpringInfinispanHornetQRemoting / restServices / employeeService / 1, и вы получите тот же результат с операцией POST
  • Выполните запрос PUT и обновите любое поле, которое вам нравится, из объекта employeeDTO. Служба ответит обновленным XML-представлением employeeDTO
  • Выполните запрос DELETE для http: // localhost: 8080 / GWTSpringInfinispanHornetQRemoting / restServices / employeeService / 1, и вы должны получить ответ «ОК»!
  • Выполните вышеупомянутые команды после добавления директивы заголовка HTTP, чтобы определить принятый тип контента для «application / json». Возвращенное представление employeeDTO должно быть таким, как показано ниже:
1
{"employeeId":1,"employeeName":"myName","employeeSurname":"mySurname","job":"myJob"}

Вы можете скачать проект отсюда (необходимые сторонние библиотеки, как описано в начале и предыдущие статьи не включены)

Повеселись!

Джастин

Статьи по Теме :