Spring MVC, Ajax and JSON Part 2 – The Server Side Code
In my last blog I said that I was going to talk about Spring, Ajax and JSON, but didn’t. The reason for this is that I wanted to set the scene using a (barely) credible shopping web site scenario. In this scenario when the user clicks on the eCommerce page link, the server app loads some the items from a catalogue and displays them on the page. The user then checks a number of items and presses ‘Confirm Purchase’. Now, this is where Ajax and JSON come in, on pressing ‘Confirm Purchase’ the browser makes an Ajax request to the server sending it the item ids. The server then retrieves the items from the database returns them as JSON to the browser. The browser then processes the JSON, displaying the items on he screen.
My last blog got as far as creating and displaying a form that presented a list of items
from the imaginary catalogue to the user. This blog takes a look at the next step in the project: creating some JSON.
The Guys at Spring have been busy working on Ajax and JSON over the last couple of years and, as you’d expect, they do a lot of the work for you in the background. This means that all you need to do is to define a simple bean class that Spring can turn into JSON and write some controller code. In this case that class that Spring will convert to JSON is the OrderForm
class:
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; } }
The OrderForm
class contains a list of Item
objects and a unique order ID used to define an order.
Having created the OrderForm
, the next thing to do is to sort out the Spring controller code:
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(); }
The code above is all that’s required to return some JSON to the browser and you can see that there’s not that much to it. Firstly, the method’s @RequestMapping
annotation, using the confirm
and RequestMethod.POST
values, maps the form attributes from my
previous blog to this method.
<form:form modelAttribute="userSelections" action="confirm" method="post">
The modelAttribute
annotation tells Spring to create and map a userSelections
object from the forms posted data and inject it into the confirmPurchases(...)
method’s userSelections
argument. The UserSelections
class is a convenience class that wraps a list of String
s. Although an example of the Lazy Class anti-pattern, this class is used to effortlessly integrate with Spring’s <form:checkbox>
tag and in a real world application would contain more attributes.
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(); } }
The confirmPurchases(...)
method converts a UserSelections
input object into an OrderForm
output object that’s passed back to the browser as JSON. The OrderForm
object is created by looping through the list of Item
ids held in the UserSelection
object and looking up the corresponding Item
s using the fake catalogue
service. Once it has the list of Item
s it then creates a unique purchase id using Java’s UUID
class. It then passes the Item
s list and the purchase ID to the OrderForm
‘s constructor and the order form is then passed back to Spring. Don’t forget the @ResposeBody
annotation, which tells Spring to bind the OrderForm
to the HTTP response body using a suitable HttpMessageConverter
. This is where the magic comes in. As you may guess, a HTTP response body needs to include data that is of the correct media type to send over the internet and OrderForm
definitely doesn’t fit that bill. To fix the problem it seems that Spring takes a look at the project config for suitable ways of converting the OrderForm
object where it finds the jackson-core
and jackson-databind
libraries that were added to the project in the last blog.
<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>
In the absence of any other suitable candidates, uses these libraries to convert the OrderForm
object to JSON. All of which means that you and I don’t actually have to do any real coding to produce our JSON output. Pretty clever huh!
Obviously, all this magical jiggery-pokery that’s going on in the background hides away the actual JSON output so, I find it useful to create a simple unit test similar to the one shown below:
@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); }
You may argue that this isn’t a real test as it doesn’t assert anything. The value of this test is to give a visual representation of the JSON output and to ensure that the object you’re attaching to the HTTP response body can be converted into JSON by the Jackson parser. If it can’t then when you run this test you’ll get an exception.
So, that’s the server side code covered. The next, and hopefully last, blog in this short series takes a look at the client side code. For the full source code to this blog, see GitHub – https://github.com/roghughe/captaindebug/tree/master/ajax-json