Spring Blog
In een eerdere post heb ik geblogd over de REST mogelijkheden die we hebben toegevoegd aan Spring @MVC versie 3.0. Later schreef Alef over het gebruik van de geïntroduceerde functionaliteit om een Atom view toe te voegen aan de Pet Clinic applicatie. In deze post wil ik graag de client-side mogelijkheden introduceren die we in Milestone 2 hebben toegevoegd.
RestTemplate
De RestTemplate is de centrale Spring klasse voor client-side HTTP toegang. Conceptueel is het zeer vergelijkbaar met de JdbcTemplate, JmsTemplate, en de verschillende andere templates gevonden in het Spring Framework en andere portfolio projecten. Dit betekent bijvoorbeeld dat de RestTemplate thread-safe is zodra deze is geconstrueerd, en dat je callbacks kunt gebruiken om de operaties aan te passen.
RestTemplate Methods
De belangrijkste entry points van de template zijn vernoemd naar de zes belangrijkste HTTP methods:
HTTP | RestTemplate |
---|---|
DELETE | delete(String, String…) |
GET | getForObject(String, Class, String…) |
HEAD | headForHeaders(String, String…) |
OPTIONS | optionsForAllow(String, String…) |
POST | postForLocation(String, Object, String…) |
PUT | put(String, Object, String…) |
De namen van deze methoden geven duidelijk aan welke HTTP-methode ze aanroepen, terwijl het tweede deel van de naam aangeeft wat er wordt geretourneerd. Bijvoorbeeld, getForObject() voert een GET uit, converteert het HTTP antwoord in een object type naar keuze, en geeft dat object terug. postForLocation voert een POST uit, converteert het gegeven object in een HTTP verzoek, en geeft de HTTP Location header terug waar het nieuw gemaakte object gevonden kan worden. Zoals je kunt zien, proberen deze methodes de REST best practices te handhaven.
URI Templates
Elke van deze methodes neemt een URI als eerste argument. Die URI kan een URI-sjabloon zijn, en variabelen kunnen worden gebruikt om het sjabloon uit te breiden tot een normale URI. De template variabelen kunnen in twee vormen worden doorgegeven: als een String variabele argumenten array, of als een Map<String, String>. De string varargs variant breidt de gegeven template variabelen in volgorde uit, zodat
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
zal een GET uitvoeren op http://example.com/hotels/42/bookings/21. De map-variant breidt het sjabloon uit op basis van de naam van de variabele, en is daarom nuttiger bij het gebruik van veel variabelen, of wanneer een enkele variabele meerdere keren wordt gebruikt. Bijvoorbeeld:
Map<String, String> vars = new HashMap<String, String>();vars.put("hotel", "42");vars.put("booking", "21");String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);
zal ook een GET uitvoeren op http://example.com/hotels/42/rooms/42.
HttpMessageConverters
Objecten die worden doorgegeven aan en geretourneerd van de methoden getForObject(), postForLocation(), en put() en worden geconverteerd naar HTTP-verzoeken en van HTTP-antwoorden door HttpMessageConverters. Converters voor de belangrijkste mime types en Java types zijn standaard geregistreerd, maar je kan ook je eigen converter schrijven en die in de RestTemplate steken. In het voorbeeld hieronder laat ik zien hoe dat moet.
Het RestTemplate gebruiken om foto’s op te halen uit Flickr
In plaats van de verschillende methodes van het RestTemplate te doorlopen, laat ik zien hoe je het kunt gebruiken om foto’s op te halen uit Flickr, Yahoo!s online foto-sharing applicatie. Deze voorbeeld applicatie doorzoekt Flickr voor foto’s die overeenkomen met een gegeven zoekterm. Vervolgens toont het deze foto’s met behulp van een eenvoudige Swing UI. Om de applicatie zelf te draaien, moet je een Flickr account aanmaken en een API key aanvragen.
Flickr zoekt naar foto’s
Flickr stelt verschillende API’s beschikbaar om zijn enorme bibliotheek van foto’s te manipuleren. De flickr.photos.search methode staat je toe om naar foto’s te zoeken, door een GET verzoek te doen op http://www.flickr.com/services/rest?method=flickr.photos.search&api+key=xxx&tags=penguins, waar je je API sleutel invult en waarnaar je wilt zoeken (pinguins in dit geval). Als resultaat krijg je een XML document terug, dat de foto’s beschrijft die voldoen aan je zoekvraag. Zoiets als:
<photos page="2" pages="89" perpage="10" total="881"><photo owner="" secret="a123456" server="2" title="test_04"ispublic="1" isfriend="0" isfamily="0" /><photo owner=""secret="b123456" server="2" title="test_03"ispublic="0" isfriend="1" isfamily="1" /><photo owner=""secret="c123456" server="2" title="test_01"ispublic="1" isfriend="0" isfamily="0" /><photo owner=""secret="d123456" server="2" title="00_tall"ispublic="1" isfriend="0" isfamily="0" /></photos>
Gebruik makend van het RestTemplate, is het ophalen van zo’n document vrij triviaal:
final String photoSearchUrl = "http://www.flickr.com/services/rest?method=flickr.photos.search&api+key={api-key}&tags={tag}&per_page=10";Source photos = restTemplate.getForObject(photoSearchUrl, Source.class, apiKey, searchTerm);
waarbij apiKey en searchTerm twee Strings zijn die op de opdrachtregel worden gegeven. Deze methode gebruikt de SourceHttpMessageConverter om het HTTP XML antwoord om te zetten in een javax.xml.transform.Source (Merk op dat de SourceHttpMessageConverter kort na de release van Spring 3.0 M2 werd geïntroduceerd, dus je zal een recente snapshot (of de aankomende M3) moeten hebben om het te kunnen gebruiken. Het voorbeeld project hieronder is opgezet om deze op te halen via Maven).
Het ophalen van de foto’s
Volgende, we gaan een XPath expressie gebruiken om alle foto elementen van het document op te halen. Hiervoor gaan we de XPathTemplate van Spring Web Services gebruiken. We gaan de //photo expressies uitvoeren, waarbij alle foto elementen die waar dan ook in het document voorkomen worden teruggegeven. De NodeMapper is een callback interface, waarvan de mapNode() methode zal worden aangeroepen voor elk foto element in het document. In dit geval halen we de server, id, en secret attributen van dit element op, en gebruiken die om een Map te vullen. Tenslotte gebruiken we de RestTemplate weer, om de foto op te halen als een java.awt.image.BufferedImage. Dus wanneer de XPath evaluatie is gedaan, zal de resulterende imageList een afbeelding bevatten voor elke foto in het XML document.
List<BufferedImage> imageList = xpathTemplate.evaluate("//photo", photos, new NodeMapper() { public Object mapNode(Node node, int i) throws DOMException { Element photo = (Element) node; Map<String, String> variables = new HashMap<String, String>(3); variables.put("server", photo.getAttribute("server")); variables.put("id", photo.getAttribute("id")); variables.put("secret", photo.getAttribute("secret")); String photoUrl = "http://static.flickr.com/{server}/{id}_{secret}_m.jpg"; return restTemplate.getForObject(photoUrl, BufferedImage.class, variables); }});
Bijv. het hierboven gegeven XML document, zal de imageList 4 afbeeldingen bevatten. De URL voor de eerste opgehaalde afbeelding is http://static.flickr.com/2/2636_ a123456_m.jpg, de tweede is http://static.flickr.com/2/2635_ b123456_m.jpg, enz.
Omzetten van de afbeeldingen
Er is nog één ding dat moet gebeuren om de code te laten werken: we moeten een HttpMessageConverter schrijven die in staat is om de HTTP response te lezen, en daar een BufferedImagefrom van te maken. Dit doen met de Java Image I/O API is vrij eenvoudig, we hoeven alleen maar de read() methode te implementeren die gedefinieerd is in de HttpMessageConverter interface. Over het geheel genomen ziet onze eenvoudige converter er als volgt uit:
public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> { public List<MediaType> getSupportedMediaTypes() { return Collections.singletonList(new MediaType("image", "jpeg")); } public boolean supports(Class<? extends BufferedImage> clazz) { return BufferedImage.class.equals(clazz); } public BufferedImage read(Class<BufferedImage> clazz, HttpInputMessage inputMessage) throws IOException { return ImageIO.read(inputMessage.getBody()); } public void write(BufferedImage image, HttpOutputMessage message) throws IOException { throw new UnsupportedOperationException("Not implemented"); }}
Merk op dat we write() niet hebben ge¨ımplementeerd omdat we geen afbeeldingen uploaden, maar alleen downloaden. Nu hoeven we alleen nog maar deze converter in te pluggen in de RestTemplate. Dat doen we in de Spring applicatie context:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.springsource.samples.resttemplate.FlickrClient"> <constructor-arg ref="restTemplate"/> <constructor-arg ref="xpathTemplate"/> </bean> <bean class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/> <bean class="com.springsource.samples.resttemplate.BufferedImageHttpMessageConverter"/> </list> </property> </bean> <bean class="org.springframework.xml.xpath.Jaxp13XPathTemplate"/></beans>
Tonen van de foto’s
De laatste stap is het tonen van de foto’s in een eenvoudige GUI. Hiervoor gebruiken we Swing:
JFrame frame = new JFrame(searchTerm + " photos");frame.setLayout(new GridLayout(2, imageList.size() / 2));for (BufferedImage image : imageList) { frame.add(new JLabel(new ImageIcon(image)));}frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.pack();frame.setVisible(true);
wat ons het volgende oplevert:
Al met al hoop ik dat deze post je heeft laten zien hoe eenvoudig het kan zijn om de RestTemplate te gebruiken voor interactie met HTTP servers. In iets minder dan 30 regels Java code hebben we een GUI gemaakt die plaatjes laat zien van ieders favoriete vogel: de pinguïn! Bekijk de RestTemplate en laat ons weten wat je ervan vindt!
Downloads
Een Maven project met de bovenstaande code kan hier gedownload worden. Merk op dat het project gebaseerd is op een nachtelijke snapshot build van Spring. De aankomende Milestone 3 van Spring zal ook de benodigde classes bevatten.