Spring Blog
Num post anterior, bloguei sobre as capacidades REST que adicionámos ao Spring @MVC versão 3.0. Mais tarde, Alef escreveu sobre a utilização da funcionalidade introduzida para adicionar uma vista Atom à aplicação Pet Clinic. Neste post, gostaria de introduzir as capacidades do lado do cliente que adicionámos em Milestone 2.
RestTemplate
O RestTemplate é a classe central Spring para acesso HTTP do lado do cliente. Conceptualmente, é muito semelhante ao JdbcTemplate, JmsTemplate, e aos vários outros modelos encontrados no Spring Framework e outros projectos de portfólio. Isto significa, por exemplo, que o RestTemplate é seguro para os fios uma vez construído, e que se pode usar callbacks para personalizar as suas operações.
Métodos RestTemplate
Os principais pontos de entrada do modelo têm o nome dos seis principais métodos HTTP:
HTTP | RestTemplate |
---|---|
DELETE | delete(String, String….) |
getForObject(String, String, Class, String…) | |
headForHeaders(String, String…) | |
optionsForAllow(String, String…)td…) | |
POST | postForLocation(String, String, Object, String…) |
PUT | put(String, Object, String…) |
Os nomes destes métodos indicam claramente qual o método HTTP que invocam, enquanto a segunda parte do nome indica o que é devolvido. Por exemplo, getForObject() realizará um GET, converterá a resposta HTTP num tipo de objecto à sua escolha, e retornará esse objecto. postForLocation realizará um POST, convertendo o objecto dado num pedido HTTP, e retornará o cabeçalho HTTP Location da resposta onde o objecto recém-criado pode ser encontrado. Como pode ver, estes métodos tentam aplicar as melhores práticas REST.
URI Templates
Cada um destes métodos toma um URI como primeiro argumento. Que o URI pode ser um modelo de URI, e as variáveis podem ser usadas para expandir o modelo para um URI normal. As variáveis do modelo podem ser passadas de duas formas: como uma matriz de argumentos de variáveis String, ou como um Mapa<String, String>. A variante string varargs expande as variáveis do modelo dado em ordem, de modo que
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
irá realizar um GET em http://example.com/hotels/42/bookings/21. A variante do mapa expande o modelo com base no nome da variável, e é portanto mais útil quando se utilizam muitas variáveis, ou quando uma única variável é utilizada várias vezes. Por exemplo:
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);
também realizará um GET em http://example.com/hotels/42/rooms/42.
HttpMessageConverters
Objectos passados e devolvidos pelos métodos getForObject(), postForLocation(), e put(), e são convertidos para pedidos HTTP e de respostas HTTP por HttpMessageConverters. Os conversores para os principais tipos de mímica e tipos Java são registados por defeito, mas também se pode escrever o seu próprio conversor e ligá-lo ao RestTemplate. No exemplo abaixo, mostrar-lhe-ei como isso é feito.
Usando o RestTemplate para recuperar fotos do Flickr
Rather do que passando pelos vários métodos do RestTemplate, mostrar-lhe-ei como utilizá-lo para recuperar fotos do Flickr, aplicação online de partilha de fotos do Yahoo!s. Esta aplicação de amostra procura no Flickr fotografias que correspondam a um determinado termo de pesquisa. Em seguida, mostra estas fotos usando uma simples IU Swing. Para executar a aplicação você mesmo, terá de criar uma conta Flickr e solicitar uma chave API.
Procura de fotos
Flickr expõe várias APIs para manipular a sua vasta biblioteca de fotos. O método flickr.photos.search permite pesquisar fotos, emitindo um pedido GET em http://www.flickr.com/services/rest?method=flickr.photos.search&api+key=xxx&tags=penguins, onde introduz a sua chave API e a coisa a pesquisar (pinguins neste caso). Como resultado, recebe de volta um documento XML, descrevendo as fotos que estão em conformidade com a sua consulta. Algo como:
<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>
Utilizando o RestTemplate, a recuperação de tal documento é bastante trivial:
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);
onde apiKey e searchTerm são duas Strings dadas na linha de comando. Este método utiliza o SourceHttpMessageConverter para converter a resposta HTTP XML em javax.xml.transform.Source (Note que o SourceHttpMessageConverter foi introduzido pouco depois de lançarmos o Spring 3.0 M2, pelo que terá de obter um instantâneo recente (ou o próximo M3) para o utilizar. O projecto de amostra disponível abaixo está configurado para os recuperar via Maven).
Retrieving the photos
Next, vamos usar uma expressão XPath para recuperar todos os elementos fotográficos do documento. Para isso, vamos utilizar a XPathTemplate dos Spring Web Services. Vamos executar as expressões //photo, devolvendo todos os elementos fotográficos que ocorram em qualquer parte do documento. O NodeMapper é uma interface de chamada de retorno, cujo método mapNode() será invocado para cada elemento fotográfico do documento. Neste caso, estamos a recuperar o servidor, id, e atributos secretos deste elemento, e utilizá-los para preencher um Mapa. Finalmente, utilizamos novamente o RestTemplate, para recuperar a foto como java.awt.image.BufferedImage. Assim, quando a avaliação XPath for feita, a imageList resultante conterá uma imagem para cada foto no documento XML.
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); }});
Por exemplo, dado o documento XML dado acima, a imageList conterá 4 imagens. A URL da primeira imagem recuperada será http://static.flickr.com/2/2636_ a123456_m.jpg, a segunda é http://static.flickr.com/2/2635_ b123456_m.jpg, etc.
Convertendo as imagens
Há mais uma coisa a fazer para que o código funcione: teremos de escrever um HttpMessageConverter que seja capaz de ler a partir da resposta HTTP, e criar um BufferedImagefrom que funcione. Fazê-lo com a API Java Image I/O é bastante simples, só precisamos de implementar o método read() definido na interface HttpMessageConverter. No geral, o nosso conversor simples é assim:
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"); }}
Nota que não implementamos write() porque não estamos a carregar imagens, apenas a descarregá-las. Agora só temos de ligar este conversor ao RestTemplate. Fazemos isso no contexto da aplicação Spring:
<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>
Mostrar as fotos
A fase final é mostrar as fotos numa GUI simples. Para isso, usamos 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);
que nos dá o seguinte:
Penguins
Overall, espero que este post tenha mostrado como pode ser simples usar o RestTemplate para interagir com servidores HTTP. Em pouco menos de 30 linhas de código Java, criámos uma GUI que mostra imagens do pássaro favorito de todos: o pinguim! Veja o RestTemplate e diga-nos o que pensa!
Downloads
Um projecto Maven contendo o código acima pode ser descarregado aqui. Note que o projecto é baseado numa construção nocturna da Primavera. O próximo Marco 3 da Primavera conterá também as classes necessárias.