Статьи

Spring MVC, Ajax и JSON. Часть 2. Код на стороне сервера

В своем последнем блоге я сказал, что собираюсь поговорить о Spring, Ajax и JSON, но не стал. Причина этого в том, что я хотел установить сцену, используя (едва ли) заслуживающий доверия сценарий веб-сайта для покупок. В этом случае, когда пользователь нажимает на ссылку на странице электронной коммерции, серверное приложение загружает некоторые элементы из каталога и отображает их на странице. Затем пользователь проверяет количество товаров и нажимает «Подтвердить покупку». Теперь, когда приходят Ajax и JSON, после нажатия «Подтвердить покупку» браузер отправляет Ajax-запрос на сервер, отправляя ему идентификаторы товара. Затем сервер извлекает элементы из базы данных и возвращает их как JSON в браузер. Затем браузер обрабатывает JSON, отображая элементы на экране.

Мой последний блог дошел до создания и отображения формы, представляющей список элементов

из воображаемого каталога для пользователя. Этот блог посвящен следующему этапу проекта: созданию JSON.

Последние два года ребята из Spring были заняты работой над Ajax и JSON, и, как и следовало ожидать, они выполняют большую часть работы за вас в фоновом режиме. Это означает, что все, что вам нужно сделать, это определить простой класс bean-компонента, который Spring может превратить в JSON, и написать некоторый код контроллера. В этом случае тот класс, который Spring преобразует в JSON, является классом OrderForm :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class OrderForm {
  
  private final List<Item> items;
  
  private final String purchaseId;
  
  public OrderForm(List<Item> items, String purchaseId) {
    super();
    this.items = items;
    this.purchaseId = purchaseId;
  }
  
  public List<Item> getItems() {
    return items;
  }
  
  public String getPurchaseId() {
    return purchaseId;
  }
}

Класс OrderForm содержит список объектов Item и уникальный идентификатор заказа, используемый для определения заказа.

Создав OrderForm , вам нужно разобраться с кодом контроллера Spring:

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
  public @ResponseBody
  OrderForm confirmPurchases(@ModelAttribute("userSelections") UserSelections userSelections) {
  
    logger.debug("Confirming purchases...");
    OrderForm orderForm = createOrderForm(userSelections.getSelection());
    return orderForm;
  }
  
  private OrderForm createOrderForm(List<String> selections) {
  
    List<Item> items = findItemsInCatalogue(selections);
    String purchaseId = getPurchaseId();
  
    OrderForm orderForm = new OrderForm(items, purchaseId);
    return orderForm;
  }
  
  private List<Item> findItemsInCatalogue(List<String> selections) {
  
    List<Item> items = new ArrayList<Item>();
    for (String selection : selections) {
      Item item = catalogue.findItem(Integer.valueOf(selection));
      items.add(item);
    }
    return items;
  }
  
  private String getPurchaseId() {
    return UUID.randomUUID().toString();
  }

Приведенный выше код — это все, что требуется для возврата некоторого JSON в браузер, и вы можете видеть, что в этом нет ничего особенного. Во-первых, аннотация метода @RequestMapping , использующая значения confirm и RequestMethod.POST , отображает атрибуты формы из моего
предыдущий блог по этому методу.

1
          <form:form modelAttribute="userSelections" action="confirm" method="post">

Аннотация modelAttribute указывает Spring создать и userSelections объект userSelections из опубликованных данных форм и userSelections аргумент userSelections метода confirmPurchases(...) . Класс UserSelections является UserSelections классом, который UserSelections список String . Несмотря на то, что это пример анти-паттерна Lazy Class, этот класс используется для легкой интеграции с <form:checkbox> Spring <form:checkbox> и в реальном приложении содержит больше атрибутов.

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
public class UserSelections {
  
  private List<String> selection = Collections.emptyList();
  
  public List<String> getSelection() {
    return selection;
  }
  
  public void setSelection(List<String> selection) {
    this.selection = selection;
  }
  
  @Override
  public String toString() {
  
    StringBuilder sb = new StringBuilder("Selections are: ");
  
    for (String str : selection) {
      sb.append(str);
      sb.append(",  ");
    }
  
    return sb.toString();
  }
}

Метод confirmPurchases(...) преобразует входной объект OrderForm выходной объект OrderForm который передается обратно в браузер как JSON. Объект OrderForm создается путем циклического просмотра списка идентификаторов Item содержащихся в объекте UserSelection и поиска соответствующих Item с помощью службы поддельного catalogue . Как только у него есть список Предметов, он создает уникальный идентификатор покупки, используя класс UUID Java. Затем он передает список Item и идентификатор OrderForm конструктору OrderForm , а затем форма заказа передается обратно в Spring. Не забудьте аннотацию @ResposeBody , которая сообщает Spring, чтобы привязать OrderForm к OrderForm HTTP-ответа, используя подходящий HttpMessageConverter . В этом и заключается магия. Как вы можете догадаться, тело ответа HTTP должно включать данные, которые имеют правильный тип мультимедиа для отправки через Интернет, и OrderForm определенно не подходит под этот счет. Чтобы решить эту проблему, кажется, что Spring рассматривает конфигурацию проекта для подходящих способов преобразования объекта OrderForm где он находит библиотеки OrderForm jackson-core и jackson-databind которые были добавлены в проект в последнем блоге .

01
02
03
04
05
06
07
08
09
10
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.0.4</version>
  </dependency>
  <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.0.4</version>
  </dependency>

При отсутствии других подходящих кандидатов использует эти библиотеки для преобразования объекта OrderForm в JSON. Все это означает, что вам и мне на самом деле не нужно делать никакого реального кодирования для создания нашего вывода JSON. Довольно умно, да!

Очевидно, что все эти магические покерные игры, которые происходят в фоновом режиме, скрывают фактический вывод JSON, поэтому я считаю полезным создать простой модульный тест, подобный показанному ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
  @Test
  public void testDemonstrateJSON() throws JsonGenerationException, JsonMappingException, IOException {
  
    UserSelections userSelection = new UserSelections();
    String[] selections = { "1", "2" };
    userSelection.setSelection(Arrays.asList(selections));
  
    Item item1 = Item.getInstance(1, "name", "description", new BigDecimal("1.00"));
    when(catalogue.findItem(1)).thenReturn(item1);
    Item item2 = Item.getInstance(2, "name2", "description2", new BigDecimal("2.00"));
    when(catalogue.findItem(2)).thenReturn(item2);
  
    OrderForm orderForm = instance.confirmPurchases(userSelection);
  
    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(orderForm);
  
    System.out.println(result);
  }

Вы можете утверждать, что это не настоящий тест, поскольку он ничего не утверждает. Ценность этого теста состоит в том, чтобы дать визуальное представление выходных данных JSON и убедиться, что объект, который вы присоединяете к телу ответа HTTP, может быть преобразован в JSON анализатором Джексона. Если это невозможно, то при запуске этого теста вы получите исключение.

Итак, это код серверной части. Следующий и, надеюсь, последний блог в этой короткой серии статей посвящен клиентскому коду. Полный исходный код этого блога см. На GitHub — https://github.com/roghughe/captaindebug/tree/master/ajax-json.